settings: fix accents
settings_screen: use *ListTile instead of having it into "trailing" lyrics: add the possibility to scroll freely
This commit is contained in:
parent
648a8ff7e8
commit
e30e2bebf1
|
|
@ -20,7 +20,6 @@ Settings settings;
|
|||
|
||||
@JsonSerializable()
|
||||
class Settings {
|
||||
|
||||
//Language
|
||||
@JsonKey(defaultValue: null)
|
||||
String language;
|
||||
|
|
@ -46,7 +45,6 @@ class Settings {
|
|||
@JsonKey(defaultValue: AudioQuality.FLAC)
|
||||
AudioQuality downloadQuality;
|
||||
|
||||
|
||||
//Download options
|
||||
String downloadPath;
|
||||
|
||||
|
|
@ -78,12 +76,26 @@ class Settings {
|
|||
String singletonFilename;
|
||||
@JsonKey(defaultValue: 1400)
|
||||
int albumArtResolution;
|
||||
@JsonKey(defaultValue: ["title", "album", "artist", "track", "disc",
|
||||
"albumArtist", "date", "label", "isrc", "upc", "trackTotal", "bpm",
|
||||
"lyrics", "genre", "contributors", "art"])
|
||||
@JsonKey(defaultValue: [
|
||||
"title",
|
||||
"album",
|
||||
"artist",
|
||||
"track",
|
||||
"disc",
|
||||
"albumArtist",
|
||||
"date",
|
||||
"label",
|
||||
"isrc",
|
||||
"upc",
|
||||
"trackTotal",
|
||||
"bpm",
|
||||
"lyrics",
|
||||
"genre",
|
||||
"contributors",
|
||||
"art"
|
||||
])
|
||||
List<String> tags;
|
||||
|
||||
|
||||
//Appearance
|
||||
@JsonKey(defaultValue: Themes.Dark)
|
||||
Themes theme;
|
||||
|
|
@ -127,13 +139,13 @@ class Settings {
|
|||
@JsonKey(defaultValue: null)
|
||||
String lastFMPassword;
|
||||
|
||||
|
||||
Settings({this.downloadPath, this.arl});
|
||||
|
||||
ThemeData get themeData {
|
||||
//System theme
|
||||
if (useSystemTheme) {
|
||||
if (SchedulerBinding.instance.window.platformBrightness == Brightness.light) {
|
||||
if (SchedulerBinding.instance.window.platformBrightness ==
|
||||
Brightness.light) {
|
||||
return _themeData[Themes.Light];
|
||||
} else {
|
||||
if (theme == Themes.Light) return _themeData[Themes.Dark];
|
||||
|
|
@ -158,7 +170,8 @@ class Settings {
|
|||
useArtColor = v;
|
||||
if (v) {
|
||||
//On media item change set color
|
||||
_useArtColorSub = AudioService.currentMediaItemStream.listen((event) async {
|
||||
_useArtColorSub =
|
||||
AudioService.currentMediaItemStream.listen((event) async {
|
||||
if (event == null || event.artUri == null) return;
|
||||
this.primaryColor = await imagesDatabase.getPrimaryColor(event.artUri);
|
||||
updateTheme();
|
||||
|
|
@ -175,8 +188,7 @@ class Settings {
|
|||
SliderThemeData get _sliderTheme => SliderThemeData(
|
||||
thumbColor: primaryColor,
|
||||
activeTrackColor: primaryColor,
|
||||
inactiveTrackColor: primaryColor.withOpacity(0.2)
|
||||
);
|
||||
inactiveTrackColor: primaryColor.withOpacity(0.2));
|
||||
|
||||
//Load settings/init
|
||||
Future<Settings> loadSettings() async {
|
||||
|
|
@ -188,7 +200,8 @@ class Settings {
|
|||
}
|
||||
Settings s = Settings.fromJson({});
|
||||
//Set default path, because async
|
||||
s.downloadPath = (await ExtStorage.getExternalStoragePublicDirectory(ExtStorage.DIRECTORY_MUSIC));
|
||||
s.downloadPath = (await ExtStorage.getExternalStoragePublicDirectory(
|
||||
ExtStorage.DIRECTORY_MUSIC));
|
||||
s.save();
|
||||
return s;
|
||||
}
|
||||
|
|
@ -210,17 +223,22 @@ class Settings {
|
|||
//AudioQuality to deezer int
|
||||
int getQualityInt(AudioQuality q) {
|
||||
switch (q) {
|
||||
case AudioQuality.MP3_128: return 1;
|
||||
case AudioQuality.MP3_320: return 3;
|
||||
case AudioQuality.FLAC: return 9;
|
||||
default: return 8;
|
||||
case AudioQuality.MP3_128:
|
||||
return 1;
|
||||
case AudioQuality.MP3_320:
|
||||
return 3;
|
||||
case AudioQuality.FLAC:
|
||||
return 9;
|
||||
default:
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if is dark, can't use theme directly, because of system themes, and Theme.of(context).brightness broke
|
||||
bool get isDark {
|
||||
if (useSystemTheme) {
|
||||
if (SchedulerBinding.instance.window.platformBrightness == Brightness.light) return false;
|
||||
if (SchedulerBinding.instance.window.platformBrightness ==
|
||||
Brightness.light) return false;
|
||||
return true;
|
||||
}
|
||||
if (theme == Themes.Light) return false;
|
||||
|
|
@ -229,15 +247,31 @@ class Settings {
|
|||
|
||||
static const deezerBg = Color(0xFF1F1A16);
|
||||
static const deezerBottom = Color(0xFF1b1714);
|
||||
TextTheme get _textTheme => (font == 'Deezer') ? null : GoogleFonts.getTextTheme(font);
|
||||
TextTheme get _textTheme =>
|
||||
(font == 'Deezer') ? null : GoogleFonts.getTextTheme(font);
|
||||
String get _fontFamily => (font == 'Deezer') ? 'MabryPro' : null;
|
||||
|
||||
MaterialColor get _primarySwatch =>
|
||||
MaterialColor(primaryColor.value, <int, Color>{
|
||||
50: primaryColor.withOpacity(.1),
|
||||
100: primaryColor.withOpacity(.2),
|
||||
200: primaryColor.withOpacity(.3),
|
||||
300: primaryColor.withOpacity(.4),
|
||||
400: primaryColor.withOpacity(.5),
|
||||
500: primaryColor.withOpacity(.6),
|
||||
600: primaryColor.withOpacity(.7),
|
||||
700: primaryColor.withOpacity(.8),
|
||||
800: primaryColor.withOpacity(.9),
|
||||
900: primaryColor.withOpacity(1),
|
||||
});
|
||||
|
||||
Map<Themes, ThemeData> get _themeData => {
|
||||
Themes.Light: ThemeData(
|
||||
textTheme: _textTheme,
|
||||
fontFamily: _fontFamily,
|
||||
brightness: Brightness.light,
|
||||
primaryColor: primaryColor,
|
||||
primarySwatch: _primarySwatch,
|
||||
accentColor: primaryColor,
|
||||
sliderTheme: _sliderTheme,
|
||||
toggleableActiveColor: primaryColor,
|
||||
|
|
@ -249,6 +283,7 @@ class Settings {
|
|||
fontFamily: _fontFamily,
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: primaryColor,
|
||||
primarySwatch: _primarySwatch,
|
||||
accentColor: primaryColor,
|
||||
sliderTheme: _sliderTheme,
|
||||
toggleableActiveColor: primaryColor,
|
||||
|
|
@ -259,6 +294,7 @@ class Settings {
|
|||
fontFamily: _fontFamily,
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: primaryColor,
|
||||
primarySwatch: _primarySwatch,
|
||||
accentColor: primaryColor,
|
||||
sliderTheme: _sliderTheme,
|
||||
toggleableActiveColor: primaryColor,
|
||||
|
|
@ -266,17 +302,16 @@ class Settings {
|
|||
scaffoldBackgroundColor: deezerBg,
|
||||
bottomAppBarColor: deezerBottom,
|
||||
dialogBackgroundColor: deezerBottom,
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: deezerBottom
|
||||
),
|
||||
bottomSheetTheme:
|
||||
BottomSheetThemeData(backgroundColor: deezerBottom),
|
||||
appBarTheme: AppBarTheme(brightness: Brightness.dark),
|
||||
cardColor: deezerBg
|
||||
),
|
||||
cardColor: deezerBg),
|
||||
Themes.Black: ThemeData(
|
||||
textTheme: _textTheme,
|
||||
fontFamily: _fontFamily,
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: primaryColor,
|
||||
primarySwatch: _primarySwatch,
|
||||
accentColor: primaryColor,
|
||||
backgroundColor: Colors.black,
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
|
|
@ -291,23 +326,15 @@ class Settings {
|
|||
)
|
||||
};
|
||||
|
||||
Future<String> getPath() async => p.join((await getApplicationDocumentsDirectory()).path, 'settings.json');
|
||||
Future<String> getPath() async =>
|
||||
p.join((await getApplicationDocumentsDirectory()).path, 'settings.json');
|
||||
|
||||
//JSON
|
||||
factory Settings.fromJson(Map<String, dynamic> json) => _$SettingsFromJson(json);
|
||||
factory Settings.fromJson(Map<String, dynamic> json) =>
|
||||
_$SettingsFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$SettingsToJson(this);
|
||||
}
|
||||
|
||||
enum AudioQuality {
|
||||
MP3_128,
|
||||
MP3_320,
|
||||
FLAC,
|
||||
ASK
|
||||
}
|
||||
enum AudioQuality { MP3_128, MP3_320, FLAC, ASK }
|
||||
|
||||
enum Themes {
|
||||
Light,
|
||||
Dark,
|
||||
Deezer,
|
||||
Black
|
||||
}
|
||||
enum Themes { Light, Dark, Deezer, Black }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import 'package:freezer/translations.i18n.dart';
|
|||
import 'package:freezer/ui/error.dart';
|
||||
|
||||
class LyricsScreen extends StatefulWidget {
|
||||
|
||||
final Lyrics lyrics;
|
||||
final String trackId;
|
||||
|
||||
|
|
@ -22,7 +21,6 @@ class LyricsScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _LyricsScreenState extends State<LyricsScreen> {
|
||||
|
||||
Lyrics lyrics;
|
||||
bool _loading = true;
|
||||
bool _error = false;
|
||||
|
|
@ -33,6 +31,8 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
StreamSubscription _mediaItemSub;
|
||||
final double height = 90;
|
||||
|
||||
bool _freeScroll = false;
|
||||
|
||||
Future _load() async {
|
||||
//Already available
|
||||
if (this.lyrics != null) return;
|
||||
|
|
@ -59,17 +59,27 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_load();
|
||||
|
||||
//Enable visualizer
|
||||
if (settings.lyricsVisualizer)
|
||||
playerHelper.startVisualizer();
|
||||
|
||||
if (settings.lyricsVisualizer) playerHelper.startVisualizer();
|
||||
Timer.periodic(Duration(milliseconds: 350), (timer) {
|
||||
_timer = timer;
|
||||
_currentIndex = lyrics?.lyrics?.lastIndexWhere((l) => l.offset <= AudioService.playbackState.currentPosition);
|
||||
_currentIndex = lyrics?.lyrics?.lastIndexWhere(
|
||||
(l) => l.offset <= AudioService.playbackState.currentPosition);
|
||||
if (_loading) return;
|
||||
//Scroll to current lyric
|
||||
if (_currentIndex <= 0) return;
|
||||
|
|
@ -77,20 +87,13 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
//Update current lyric index
|
||||
setState(() => null);
|
||||
_prevIndex = _currentIndex;
|
||||
//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
|
||||
);
|
||||
if (_freeScroll) return;
|
||||
_scrollToLyric();
|
||||
});
|
||||
|
||||
//Track change = exit lyrics
|
||||
_mediaItemSub = AudioService.currentMediaItemStream.listen((event) {
|
||||
if (event.id != widget.trackId)
|
||||
Navigator.of(context).pop();
|
||||
if (event.id != widget.trackId) Navigator.of(context).pop();
|
||||
});
|
||||
|
||||
super.initState();
|
||||
|
|
@ -98,21 +101,50 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_timer != null)
|
||||
_timer.cancel();
|
||||
if (_mediaItemSub != null)
|
||||
_mediaItemSub.cancel();
|
||||
if (_timer != null) _timer.cancel();
|
||||
if (_mediaItemSub != null) _mediaItemSub.cancel();
|
||||
//Stop visualizer
|
||||
if (settings.lyricsVisualizer)
|
||||
playerHelper.stopVisualizer();
|
||||
if (settings.lyricsVisualizer) playerHelper.stopVisualizer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: FreezerAppBar('Lyrics'.i18n),
|
||||
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
|
||||
|
|
@ -123,68 +155,97 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
stream: playerHelper.visualizerStream,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
List<double> data = snapshot.data ?? [];
|
||||
double width = MediaQuery.of(context).size.width / data.length - 0.25;
|
||||
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(
|
||||
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 ?
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0, 0, 0, settings.lyricsVisualizer ? 100 : 0),
|
||||
child: _error
|
||||
?
|
||||
//Shouldn't really happen, empty lyrics have own text
|
||||
ErrorScreen() :
|
||||
ErrorScreen()
|
||||
:
|
||||
// Loading
|
||||
_loading ? Padding(
|
||||
_loading
|
||||
? Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator()
|
||||
],
|
||||
children: [CircularProgressIndicator()],
|
||||
),
|
||||
) : ListView.builder(
|
||||
)
|
||||
: 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),
|
||||
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,
|
||||
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,
|
||||
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
|
||||
fontWeight:
|
||||
(_currentIndex == i)
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal),
|
||||
),
|
||||
),
|
||||
))
|
||||
)
|
||||
);
|
||||
))));
|
||||
},
|
||||
),
|
||||
)),
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -141,13 +141,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
clipboard:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: clipboard
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.2+8"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
Loading…
Reference in a new issue