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:
pato05 2021-04-05 17:35:51 +02:00
parent 648a8ff7e8
commit e30e2bebf1
4 changed files with 946 additions and 924 deletions

View file

@ -20,7 +20,6 @@ Settings settings;
@JsonSerializable() @JsonSerializable()
class Settings { class Settings {
//Language //Language
@JsonKey(defaultValue: null) @JsonKey(defaultValue: null)
String language; String language;
@ -46,7 +45,6 @@ class Settings {
@JsonKey(defaultValue: AudioQuality.FLAC) @JsonKey(defaultValue: AudioQuality.FLAC)
AudioQuality downloadQuality; AudioQuality downloadQuality;
//Download options //Download options
String downloadPath; String downloadPath;
@ -78,12 +76,26 @@ class Settings {
String singletonFilename; String singletonFilename;
@JsonKey(defaultValue: 1400) @JsonKey(defaultValue: 1400)
int albumArtResolution; int albumArtResolution;
@JsonKey(defaultValue: ["title", "album", "artist", "track", "disc", @JsonKey(defaultValue: [
"albumArtist", "date", "label", "isrc", "upc", "trackTotal", "bpm", "title",
"lyrics", "genre", "contributors", "art"]) "album",
"artist",
"track",
"disc",
"albumArtist",
"date",
"label",
"isrc",
"upc",
"trackTotal",
"bpm",
"lyrics",
"genre",
"contributors",
"art"
])
List<String> tags; List<String> tags;
//Appearance //Appearance
@JsonKey(defaultValue: Themes.Dark) @JsonKey(defaultValue: Themes.Dark)
Themes theme; Themes theme;
@ -105,7 +117,7 @@ class Settings {
Color primaryColor = Colors.blue; Color primaryColor = Colors.blue;
static _colorToJson(Color c) => c.value; static _colorToJson(Color c) => c.value;
static _colorFromJson(int v) => Color(v??Colors.blue.value); static _colorFromJson(int v) => Color(v ?? Colors.blue.value);
@JsonKey(defaultValue: false) @JsonKey(defaultValue: false)
bool useArtColor = false; bool useArtColor = false;
@ -127,13 +139,13 @@ class Settings {
@JsonKey(defaultValue: null) @JsonKey(defaultValue: null)
String lastFMPassword; String lastFMPassword;
Settings({this.downloadPath, this.arl}); Settings({this.downloadPath, this.arl});
ThemeData get themeData { ThemeData get themeData {
//System theme //System theme
if (useSystemTheme) { if (useSystemTheme) {
if (SchedulerBinding.instance.window.platformBrightness == Brightness.light) { if (SchedulerBinding.instance.window.platformBrightness ==
Brightness.light) {
return _themeData[Themes.Light]; return _themeData[Themes.Light];
} else { } else {
if (theme == Themes.Light) return _themeData[Themes.Dark]; if (theme == Themes.Light) return _themeData[Themes.Dark];
@ -141,7 +153,7 @@ class Settings {
} }
} }
//Theme //Theme
return _themeData[theme]??ThemeData(); return _themeData[theme] ?? ThemeData();
} }
//Get all available fonts //Get all available fonts
@ -158,7 +170,8 @@ class Settings {
useArtColor = v; useArtColor = v;
if (v) { if (v) {
//On media item change set color //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; if (event == null || event.artUri == null) return;
this.primaryColor = await imagesDatabase.getPrimaryColor(event.artUri); this.primaryColor = await imagesDatabase.getPrimaryColor(event.artUri);
updateTheme(); updateTheme();
@ -175,8 +188,7 @@ class Settings {
SliderThemeData get _sliderTheme => SliderThemeData( SliderThemeData get _sliderTheme => SliderThemeData(
thumbColor: primaryColor, thumbColor: primaryColor,
activeTrackColor: primaryColor, activeTrackColor: primaryColor,
inactiveTrackColor: primaryColor.withOpacity(0.2) inactiveTrackColor: primaryColor.withOpacity(0.2));
);
//Load settings/init //Load settings/init
Future<Settings> loadSettings() async { Future<Settings> loadSettings() async {
@ -188,7 +200,8 @@ class Settings {
} }
Settings s = Settings.fromJson({}); Settings s = Settings.fromJson({});
//Set default path, because async //Set default path, because async
s.downloadPath = (await ExtStorage.getExternalStoragePublicDirectory(ExtStorage.DIRECTORY_MUSIC)); s.downloadPath = (await ExtStorage.getExternalStoragePublicDirectory(
ExtStorage.DIRECTORY_MUSIC));
s.save(); s.save();
return s; return s;
} }
@ -210,17 +223,22 @@ class Settings {
//AudioQuality to deezer int //AudioQuality to deezer int
int getQualityInt(AudioQuality q) { int getQualityInt(AudioQuality q) {
switch (q) { switch (q) {
case AudioQuality.MP3_128: return 1; case AudioQuality.MP3_128:
case AudioQuality.MP3_320: return 3; return 1;
case AudioQuality.FLAC: return 9; case AudioQuality.MP3_320:
default: return 8; 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 //Check if is dark, can't use theme directly, because of system themes, and Theme.of(context).brightness broke
bool get isDark { bool get isDark {
if (useSystemTheme) { if (useSystemTheme) {
if (SchedulerBinding.instance.window.platformBrightness == Brightness.light) return false; if (SchedulerBinding.instance.window.platformBrightness ==
Brightness.light) return false;
return true; return true;
} }
if (theme == Themes.Light) return false; if (theme == Themes.Light) return false;
@ -229,15 +247,31 @@ class Settings {
static const deezerBg = Color(0xFF1F1A16); static const deezerBg = Color(0xFF1F1A16);
static const deezerBottom = Color(0xFF1b1714); 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; 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 => { Map<Themes, ThemeData> get _themeData => {
Themes.Light: ThemeData( Themes.Light: ThemeData(
textTheme: _textTheme, textTheme: _textTheme,
fontFamily: _fontFamily, fontFamily: _fontFamily,
brightness: Brightness.light, brightness: Brightness.light,
primaryColor: primaryColor, primaryColor: primaryColor,
primarySwatch: _primarySwatch,
accentColor: primaryColor, accentColor: primaryColor,
sliderTheme: _sliderTheme, sliderTheme: _sliderTheme,
toggleableActiveColor: primaryColor, toggleableActiveColor: primaryColor,
@ -249,6 +283,7 @@ class Settings {
fontFamily: _fontFamily, fontFamily: _fontFamily,
brightness: Brightness.dark, brightness: Brightness.dark,
primaryColor: primaryColor, primaryColor: primaryColor,
primarySwatch: _primarySwatch,
accentColor: primaryColor, accentColor: primaryColor,
sliderTheme: _sliderTheme, sliderTheme: _sliderTheme,
toggleableActiveColor: primaryColor, toggleableActiveColor: primaryColor,
@ -259,6 +294,7 @@ class Settings {
fontFamily: _fontFamily, fontFamily: _fontFamily,
brightness: Brightness.dark, brightness: Brightness.dark,
primaryColor: primaryColor, primaryColor: primaryColor,
primarySwatch: _primarySwatch,
accentColor: primaryColor, accentColor: primaryColor,
sliderTheme: _sliderTheme, sliderTheme: _sliderTheme,
toggleableActiveColor: primaryColor, toggleableActiveColor: primaryColor,
@ -266,17 +302,16 @@ class Settings {
scaffoldBackgroundColor: deezerBg, scaffoldBackgroundColor: deezerBg,
bottomAppBarColor: deezerBottom, bottomAppBarColor: deezerBottom,
dialogBackgroundColor: deezerBottom, dialogBackgroundColor: deezerBottom,
bottomSheetTheme: BottomSheetThemeData( bottomSheetTheme:
backgroundColor: deezerBottom BottomSheetThemeData(backgroundColor: deezerBottom),
),
appBarTheme: AppBarTheme(brightness: Brightness.dark), appBarTheme: AppBarTheme(brightness: Brightness.dark),
cardColor: deezerBg cardColor: deezerBg),
),
Themes.Black: ThemeData( Themes.Black: ThemeData(
textTheme: _textTheme, textTheme: _textTheme,
fontFamily: _fontFamily, fontFamily: _fontFamily,
brightness: Brightness.dark, brightness: Brightness.dark,
primaryColor: primaryColor, primaryColor: primaryColor,
primarySwatch: _primarySwatch,
accentColor: primaryColor, accentColor: primaryColor,
backgroundColor: Colors.black, backgroundColor: Colors.black,
scaffoldBackgroundColor: 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 //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); Map<String, dynamic> toJson() => _$SettingsToJson(this);
} }
enum AudioQuality { enum AudioQuality { MP3_128, MP3_320, FLAC, ASK }
MP3_128,
MP3_320,
FLAC,
ASK
}
enum Themes { enum Themes { Light, Dark, Deezer, Black }
Light,
Dark,
Deezer,
Black
}

View file

@ -11,18 +11,16 @@ import 'package:freezer/translations.i18n.dart';
import 'package:freezer/ui/error.dart'; import 'package:freezer/ui/error.dart';
class LyricsScreen extends StatefulWidget { class LyricsScreen extends StatefulWidget {
final Lyrics lyrics; final Lyrics lyrics;
final String trackId; final String trackId;
LyricsScreen({this.lyrics, this.trackId, Key key}): super(key: key); LyricsScreen({this.lyrics, this.trackId, Key key}) : super(key: key);
@override @override
_LyricsScreenState createState() => _LyricsScreenState(); _LyricsScreenState createState() => _LyricsScreenState();
} }
class _LyricsScreenState extends State<LyricsScreen> { class _LyricsScreenState extends State<LyricsScreen> {
Lyrics lyrics; Lyrics lyrics;
bool _loading = true; bool _loading = true;
bool _error = false; bool _error = false;
@ -33,6 +31,8 @@ class _LyricsScreenState extends State<LyricsScreen> {
StreamSubscription _mediaItemSub; StreamSubscription _mediaItemSub;
final double height = 90; final double height = 90;
bool _freeScroll = false;
Future _load() async { Future _load() async {
//Already available //Already available
if (this.lyrics != null) return; 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 @override
void initState() { void initState() {
_load(); _load();
//Enable visualizer //Enable visualizer
if (settings.lyricsVisualizer) if (settings.lyricsVisualizer) playerHelper.startVisualizer();
playerHelper.startVisualizer();
Timer.periodic(Duration(milliseconds: 350), (timer) { Timer.periodic(Duration(milliseconds: 350), (timer) {
_timer = 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; if (_loading) return;
//Scroll to current lyric //Scroll to current lyric
if (_currentIndex <= 0) return; if (_currentIndex <= 0) return;
@ -77,20 +87,13 @@ class _LyricsScreenState extends State<LyricsScreen> {
//Update current lyric index //Update current lyric index
setState(() => null); setState(() => null);
_prevIndex = _currentIndex; _prevIndex = _currentIndex;
//Lyric height, screen height, appbar height if (_freeScroll) return;
double _scrollTo = (height * _currentIndex) - (MediaQuery.of(context).size.height / 2) + (height / 2) + 56; _scrollToLyric();
if (0 > _scrollTo) return;
_controller.animateTo(
_scrollTo,
duration: Duration(milliseconds: 250),
curve: Curves.ease
);
}); });
//Track change = exit lyrics //Track change = exit lyrics
_mediaItemSub = AudioService.currentMediaItemStream.listen((event) { _mediaItemSub = AudioService.currentMediaItemStream.listen((event) {
if (event.id != widget.trackId) if (event.id != widget.trackId) Navigator.of(context).pop();
Navigator.of(context).pop();
}); });
super.initState(); super.initState();
@ -98,21 +101,50 @@ class _LyricsScreenState extends State<LyricsScreen> {
@override @override
void dispose() { void dispose() {
if (_timer != null) if (_timer != null) _timer.cancel();
_timer.cancel(); if (_mediaItemSub != null) _mediaItemSub.cancel();
if (_mediaItemSub != null)
_mediaItemSub.cancel();
//Stop visualizer //Stop visualizer
if (settings.lyricsVisualizer) if (settings.lyricsVisualizer) playerHelper.stopVisualizer();
playerHelper.stopVisualizer();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( 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( body: Stack(
children: [ children: [
//Visualizer //Visualizer
@ -122,69 +154,98 @@ class _LyricsScreenState extends State<LyricsScreen> {
child: StreamBuilder( child: StreamBuilder(
stream: playerHelper.visualizerStream, stream: playerHelper.visualizerStream,
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
List<double> data = snapshot.data??[]; 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( return Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(data.length, (i) => AnimatedContainer( children: List.generate(
data.length,
(i) => AnimatedContainer(
duration: Duration(milliseconds: 130), duration: Duration(milliseconds: 130),
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
height: data[i] * 100, height: data[i] * 100,
width: width, width: width,
)), )),
); );
} }),
),
), ),
//Lyrics //Lyrics
Padding( Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, settings.lyricsVisualizer ? 100 : 0), padding: EdgeInsets.fromLTRB(
child: _error ? 0, 0, 0, settings.lyricsVisualizer ? 100 : 0),
child: _error
?
//Shouldn't really happen, empty lyrics have own text //Shouldn't really happen, empty lyrics have own text
ErrorScreen() : ErrorScreen()
:
// Loading // Loading
_loading ? Padding( _loading
? Padding(
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [CircularProgressIndicator()],
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, controller: _controller,
itemCount: lyrics.lyrics.length, itemCount: lyrics.lyrics.length,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
return Padding( return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0), padding:
EdgeInsets.symmetric(horizontal: 8.0),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0), borderRadius:
color: _currentIndex == i ? Colors.grey.withOpacity(0.25) : Colors.transparent, BorderRadius.circular(8.0),
color: _currentIndex == i
? Colors.grey.withOpacity(0.25)
: Colors.transparent,
), ),
height: height, height: height,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(8.0), borderRadius:
onTap: lyrics.id != null ? () => AudioService.seekTo(lyrics.lyrics[i].offset) : null, BorderRadius.circular(8.0),
onTap: lyrics.id != null
? () => AudioService.seekTo(
lyrics.lyrics[i].offset)
: null,
child: Center( child: Center(
child: Text( child: Text(
lyrics.lyrics[i].text, lyrics.lyrics[i].text,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 26.0, 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

View file

@ -141,13 +141,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.2.0"
clipboard:
dependency: "direct main"
description:
name: clipboard
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2+8"
clock: clock:
dependency: transitive dependency: transitive
description: description: