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();
@ -173,10 +186,9 @@ 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,85 +247,94 @@ 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;
Map<Themes, ThemeData> get _themeData => { MaterialColor get _primarySwatch =>
Themes.Light: ThemeData( MaterialColor(primaryColor.value, <int, Color>{
textTheme: _textTheme, 50: primaryColor.withOpacity(.1),
fontFamily: _fontFamily, 100: primaryColor.withOpacity(.2),
brightness: Brightness.light, 200: primaryColor.withOpacity(.3),
primaryColor: primaryColor, 300: primaryColor.withOpacity(.4),
accentColor: primaryColor, 400: primaryColor.withOpacity(.5),
sliderTheme: _sliderTheme, 500: primaryColor.withOpacity(.6),
toggleableActiveColor: primaryColor, 600: primaryColor.withOpacity(.7),
bottomAppBarColor: Color(0xfff5f5f5), 700: primaryColor.withOpacity(.8),
appBarTheme: AppBarTheme(brightness: Brightness.light), 800: primaryColor.withOpacity(.9),
), 900: primaryColor.withOpacity(1),
Themes.Dark: ThemeData( });
textTheme: _textTheme,
fontFamily: _fontFamily,
brightness: Brightness.dark,
primaryColor: primaryColor,
accentColor: primaryColor,
sliderTheme: _sliderTheme,
toggleableActiveColor: primaryColor,
appBarTheme: AppBarTheme(brightness: Brightness.dark),
),
Themes.Deezer: ThemeData(
textTheme: _textTheme,
fontFamily: _fontFamily,
brightness: Brightness.dark,
primaryColor: primaryColor,
accentColor: primaryColor,
sliderTheme: _sliderTheme,
toggleableActiveColor: primaryColor,
backgroundColor: deezerBg,
scaffoldBackgroundColor: deezerBg,
bottomAppBarColor: deezerBottom,
dialogBackgroundColor: deezerBottom,
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: deezerBottom
),
appBarTheme: AppBarTheme(brightness: Brightness.dark),
cardColor: deezerBg
),
Themes.Black: ThemeData(
textTheme: _textTheme,
fontFamily: _fontFamily,
brightness: Brightness.dark,
primaryColor: primaryColor,
accentColor: primaryColor,
backgroundColor: Colors.black,
scaffoldBackgroundColor: Colors.black,
bottomAppBarColor: Colors.black,
dialogBackgroundColor: Colors.black,
sliderTheme: _sliderTheme,
toggleableActiveColor: primaryColor,
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: Colors.black,
),
appBarTheme: AppBarTheme(brightness: Brightness.dark),
)
};
Future<String> getPath() async => p.join((await getApplicationDocumentsDirectory()).path, 'settings.json'); 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,
bottomAppBarColor: Color(0xfff5f5f5),
appBarTheme: AppBarTheme(brightness: Brightness.light),
),
Themes.Dark: ThemeData(
textTheme: _textTheme,
fontFamily: _fontFamily,
brightness: Brightness.dark,
primaryColor: primaryColor,
primarySwatch: _primarySwatch,
accentColor: primaryColor,
sliderTheme: _sliderTheme,
toggleableActiveColor: primaryColor,
appBarTheme: AppBarTheme(brightness: Brightness.dark),
),
Themes.Deezer: ThemeData(
textTheme: _textTheme,
fontFamily: _fontFamily,
brightness: Brightness.dark,
primaryColor: primaryColor,
primarySwatch: _primarySwatch,
accentColor: primaryColor,
sliderTheme: _sliderTheme,
toggleableActiveColor: primaryColor,
backgroundColor: deezerBg,
scaffoldBackgroundColor: deezerBg,
bottomAppBarColor: deezerBottom,
dialogBackgroundColor: deezerBottom,
bottomSheetTheme:
BottomSheetThemeData(backgroundColor: deezerBottom),
appBarTheme: AppBarTheme(brightness: Brightness.dark),
cardColor: deezerBg),
Themes.Black: ThemeData(
textTheme: _textTheme,
fontFamily: _fontFamily,
brightness: Brightness.dark,
primaryColor: primaryColor,
primarySwatch: _primarySwatch,
accentColor: primaryColor,
backgroundColor: Colors.black,
scaffoldBackgroundColor: Colors.black,
bottomAppBarColor: Colors.black,
dialogBackgroundColor: Colors.black,
sliderTheme: _sliderTheme,
toggleableActiveColor: primaryColor,
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: Colors.black,
),
appBarTheme: AppBarTheme(brightness: Brightness.dark),
)
};
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,93 +101,151 @@ 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,
body: Stack( height: _freeScroll ? 100 : 56,
children: [ bottom: _freeScroll
//Visualizer ? PreferredSize(
if (settings.lyricsVisualizer) preferredSize: Size.fromHeight(46),
Align( child: Theme(
alignment: Alignment.bottomCenter, data: settings.themeData.copyWith(
child: StreamBuilder( textButtonTheme: TextButtonThemeData(
stream: playerHelper.visualizerStream, style: ButtonStyle(
builder: (BuildContext context, AsyncSnapshot snapshot) { foregroundColor: MaterialStateProperty.all(
List<double> data = snapshot.data??[]; Colors.white)))),
double width = MediaQuery.of(context).size.width / data.length - 0.25; child: Container(
return Row( child: Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(data.length, (i) => AnimatedContainer( children: [
duration: Duration(milliseconds: 130), TextButton(
color: Theme.of(context).primaryColor, onPressed: () {
height: data[i] * 100, setState(() => _freeScroll = false);
width: width, _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,
)),
);
}),
), ),
),
//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),
//Shouldn't really happen, empty lyrics have own text child: _error
ErrorScreen() : ?
// Loading //Shouldn't really happen, empty lyrics have own text
_loading ? Padding( ErrorScreen()
padding: EdgeInsets.all(8.0), :
child: Row( // Loading
mainAxisAlignment: MainAxisAlignment.center, _loading
children: [ ? Padding(
CircularProgressIndicator() padding: EdgeInsets.all(8.0),
], child: Row(
), mainAxisAlignment: MainAxisAlignment.center,
) : ListView.builder( children: [CircularProgressIndicator()],
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
),
),
))
) )
); : 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),
),
))));
},
)),
)
],
));
} }
} }

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: