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()
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;
@ -105,7 +117,7 @@ class Settings {
Color primaryColor = Colors.blue;
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)
bool useArtColor = false;
@ -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];
@ -141,7 +153,7 @@ class Settings {
}
}
//Theme
return _themeData[theme]??ThemeData();
return _themeData[theme] ?? ThemeData();
}
//Get all available fonts
@ -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 }

View file

@ -11,18 +11,16 @@ 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);
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;
@ -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
@ -122,69 +154,98 @@ class _LyricsScreenState extends State<LyricsScreen> {
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;
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(
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

View file

@ -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: