diff --git a/.gitignore b/.gitignore index 775807d..e2b0a6b 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ app.*.map.json # Envied .env lib/utils/env.g.dart +lib/.env # Possible translations remnants translations/refreezer.zip diff --git a/android/app/src/main/res/drawable-hdpi/ic_logo.png b/android/app/src/main/res/drawable-hdpi/ic_logo.png index 691dd95..360ef00 100644 Binary files a/android/app/src/main/res/drawable-hdpi/ic_logo.png and b/android/app/src/main/res/drawable-hdpi/ic_logo.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_logo.png b/android/app/src/main/res/drawable-mdpi/ic_logo.png index 32d0fd5..be8d608 100644 Binary files a/android/app/src/main/res/drawable-mdpi/ic_logo.png and b/android/app/src/main/res/drawable-mdpi/ic_logo.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_logo.png b/android/app/src/main/res/drawable-xhdpi/ic_logo.png index 5da585d..ae86f2e 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/ic_logo.png and b/android/app/src/main/res/drawable-xhdpi/ic_logo.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_logo.png b/android/app/src/main/res/drawable-xxhdpi/ic_logo.png index 1611d13..ec0bf82 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/ic_logo.png and b/android/app/src/main/res/drawable-xxhdpi/ic_logo.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_logo.png b/android/app/src/main/res/drawable-xxxhdpi/ic_logo.png index 78a8ea4..e1fd8c3 100644 Binary files a/android/app/src/main/res/drawable-xxxhdpi/ic_logo.png and b/android/app/src/main/res/drawable-xxxhdpi/ic_logo.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 80254ed..4b42b70 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index 7bfd6f7..b5a2e54 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index bb82d93..4b42b70 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 134334d..11ada26 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index ba1669c..c79e842 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index ce5e2ef..11ada26 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 967e08a..d7ff0c0 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index 72be683..231914e 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 37abb73..d7ff0c0 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 79a2b3d..e9d29c9 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 02a80a5..455ffcd 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 6be04b4..e9d29c9 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 801d216..5a6d35c 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index 1fc6edf..705648b 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 8d56068..5a6d35c 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 06952be..0277e8f 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -13,6 +13,6 @@ This Theme is only used starting with V2 of Flutter's Android embedding. --> diff --git a/android/app/src/main/res/values/ic_favorites_background.xml b/android/app/src/main/res/values/ic_favorites_background.xml index e5d934a..0069995 100644 --- a/android/app/src/main/res/values/ic_favorites_background.xml +++ b/android/app/src/main/res/values/ic_favorites_background.xml @@ -1,4 +1,4 @@ - #3DDC84 + #0F0D13 \ No newline at end of file diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml index e2f223d..2f48864 100644 --- a/android/app/src/main/res/values/ic_launcher_background.xml +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #1C1C14 + #0F0D13 \ No newline at end of file diff --git a/assets/app_icon.png b/assets/app_icon.png new file mode 100644 index 0000000..05d36de Binary files /dev/null and b/assets/app_icon.png differ diff --git a/assets/banner.png b/assets/banner.png index fac6d23..8bee473 100644 Binary files a/assets/banner.png and b/assets/banner.png differ diff --git a/assets/fonts/Deezer.ttf b/assets/fonts/Deezer.ttf new file mode 100644 index 0000000..6c0c625 Binary files /dev/null and b/assets/fonts/Deezer.ttf differ diff --git a/assets/icon.png b/assets/icon.png index 42ba89e..18debb9 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/assets/icon_legacy.png b/assets/icon_legacy.png index 62b09ec..934a983 100644 Binary files a/assets/icon_legacy.png and b/assets/icon_legacy.png differ diff --git a/custom_navigator b/custom_navigator deleted file mode 160000 index bef1bad..0000000 --- a/custom_navigator +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bef1badfa66f5d1aa265555ef675e65e453bfd5d diff --git a/equalizer_flutter b/equalizer_flutter deleted file mode 160000 index 2fd74d8..0000000 --- a/equalizer_flutter +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2fd74d8a6a95d0e6a07a07210f051c4ead283597 diff --git a/external_path b/external_path deleted file mode 160000 index c753b1a..0000000 --- a/external_path +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c753b1a154bbdcac297682ead6c9cae16c6441fd diff --git a/lib/fonts/deezer_icons.dart b/lib/fonts/deezer_icons.dart new file mode 100644 index 0000000..5891cf5 --- /dev/null +++ b/lib/fonts/deezer_icons.dart @@ -0,0 +1,160 @@ +/// Flutter icons Deezer +/// Copyright (C) 2024 by original authors @ fluttericon.com, fontello.com +/// This font was generated by FlutterIcon.com, which is derived from Fontello. +/// +/// To use this font, place it in your fonts/ directory and include the +/// following in your pubspec.yaml +/// +/// flutter: +/// fonts: +/// - family: Deezer +/// fonts: +/// - asset: fonts/Deezer.ttf +/// +/// +/// +import 'package:flutter/widgets.dart'; + +class DeezerIcons { + DeezerIcons._(); + + static const _kFontFam = 'Deezer'; + static const String? _kFontPkg = null; + + static const IconData skip_back = + IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData shuffle_small = + IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData share_android_small = + IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData share_android = + IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData shuffle = + IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData settings_with_badge = + IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData settings_small = + IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData settings = + IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData search = + IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData search_small = + IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData search_fill_small = + IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData search_fill = + IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData repeat_small = + IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData repeat_one_small = + IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData repeat_one = + IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData repeat = + IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData queue_small = + IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData queue = + IconData(0xe812, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData play_before = + IconData(0xe813, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData play_before_small = + IconData(0xe814, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData pen_small = + IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData pen = + IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData offline_small = + IconData(0xe817, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData offline = + IconData(0xe818, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData microphone_small = + IconData(0xe819, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData microphone_show_small = + IconData(0xe81a, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData microphone_show = + IconData(0xe81b, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData microphone_show_fill_small = + IconData(0xe81c, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData microphone_show_fill = + IconData(0xe81d, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData microphone = + IconData(0xe81e, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData house = + IconData(0xe81f, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData house_small = + IconData(0xe820, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData house_fill = + IconData(0xe821, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData house_fill_small = + IconData(0xe822, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData heart_small = + IconData(0xe823, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData heart_fill_small = + IconData(0xe824, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData heart = + IconData(0xe825, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData heart_fill = + IconData(0xe826, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData download_small = + IconData(0xe827, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData download_fill_small = + IconData(0xe828, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData download = + IconData(0xe829, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData download_fill = + IconData(0xe82a, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData cross_small = + IconData(0xe82b, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData compass_small = + IconData(0xe82c, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData cross = + IconData(0xe82d, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData compass = + IconData(0xe82e, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData compass_fill_small = + IconData(0xe82f, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData compass_fill = + IconData(0xe830, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData angry_face_small = + IconData(0xe833, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData angry_face = + IconData(0xe834, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData album = + IconData(0xe835, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData album_small = + IconData(0xe836, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData trash = + IconData(0xe837, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData trash_small = + IconData(0xe838, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData skip_next_fill = + IconData(0xe839, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData queue_1 = + IconData(0xe84e, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData queue_small_2 = + IconData(0xe84f, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData play = + IconData(0xe850, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData play_small = + IconData(0xe851, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData play_fill_small = + IconData(0xe852, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData pause_small = + IconData(0xe857, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData pause = + IconData(0xe858, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData pause_fill_small = + IconData(0xe859, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData album_small_1 = + IconData(0xe877, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData album_1 = + IconData(0xe879, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData queue_small_1 = + IconData(0xe890, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData sort_small = + IconData(0xe8bf, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData sort = + IconData(0xe8c1, fontFamily: _kFontFam, fontPackage: _kFontPkg); +} diff --git a/lib/main.dart b/lib/main.dart index f1e9684..44913f8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,9 @@ import 'dart:async'; import 'package:app_links/app_links.dart'; import 'package:custom_navigator/custom_navigator.dart'; +import 'package:refreezer/fonts/deezer_icons.dart'; +import 'package:refreezer/ui/restartable.dart'; +import 'package:refreezer/ui/settings_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; @@ -12,7 +15,6 @@ import 'package:logging/logging.dart'; import 'package:move_to_background/move_to_background.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:quick_actions/quick_actions.dart'; -import 'package:refreezer/ui/restartable.dart'; //import 'package:restart_app/restart_app.dart'; import 'api/cache.dart'; @@ -97,7 +99,7 @@ class _ReFreezerAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - title: 'ReFreezer', + title: 'Deezer', shortcuts: { ...WidgetsApp.defaultShortcuts, LogicalKeySet(LogicalKeyboardKey.select): @@ -202,8 +204,10 @@ class _MainScreenState extends State late final AppLifecycleListener _lifeCycleListener; final List _screens = [ const HomeScreen(), + const LibraryScreen(), + const LibraryPlaylists(), const SearchScreen(), - const LibraryScreen() + const SettingsScreen() ]; Future? _initialization; int _selected = 0; @@ -427,15 +431,26 @@ class _MainScreenState extends State onKeyEvent: (event) => _handleKey(event, navigationBarFocusNode, screenFocusNode), child: Scaffold( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + floatingActionButtonLocation: + FloatingActionButtonLocation.centerFloat, + floatingActionButton: Container( + margin: EdgeInsets.fromLTRB(6, 0, 6, 0), + decoration: BoxDecoration( + color: Colors.transparent, + ), + child: const PlayerBar(), + ), bottomNavigationBar: FocusScope( node: navigationBarFocusNode, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const PlayerBar(), - BottomNavigationBar( - backgroundColor: - Theme.of(context).bottomAppBarTheme.color, + child: Theme( + data: Theme.of(context).copyWith( + canvasColor: + Theme.of(context).scaffoldBackgroundColor), + child: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + unselectedItemColor: + Theme.of(context).unselectedWidgetColor, currentIndex: _selected, onTap: (int index) async { //Pop all routes until home screen @@ -448,29 +463,44 @@ class _MainScreenState extends State setState(() { _selected = index; }); - //Fix statusbar SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.transparent, )); }, - selectedItemColor: Theme.of(context).primaryColor, + selectedItemColor: + settings.primaryColor.withOpacity(0.8), + showUnselectedLabels: true, + selectedLabelStyle: + TextStyle(color: settings.primaryColor), + unselectedLabelStyle: + TextStyle(color: Settings.secondaryText), items: [ BottomNavigationBarItem( - icon: const Icon(Icons.home), + activeIcon: const Icon(DeezerIcons.house_fill), + icon: const Icon(DeezerIcons.house), label: 'Home'.i18n), BottomNavigationBarItem( - icon: const Icon(Icons.search), + activeIcon: + const Icon(DeezerIcons.compass_fill), + icon: const Icon(DeezerIcons.compass), + label: 'Explore'.i18n), + BottomNavigationBarItem( + activeIcon: const Icon(DeezerIcons.heart_fill), + icon: const Icon(DeezerIcons.heart), + label: 'Favorites'.i18n), + BottomNavigationBarItem( + activeIcon: const Icon(DeezerIcons.search_fill), + icon: const Icon(DeezerIcons.search), label: 'Search'.i18n, ), BottomNavigationBarItem( - icon: const Icon(Icons.library_music), - label: 'Library'.i18n) + activeIcon: const Icon(DeezerIcons.settings), + icon: const Icon(DeezerIcons.settings), + label: 'Settings'.i18n) ], - ) - ], - )), + ))), body: CustomNavigator( navigatorKey: customNavigatorKey, home: Focus( @@ -482,9 +512,11 @@ class _MainScreenState extends State )); } else { // While audio_service is initializing - return const Scaffold( + return Scaffold( body: Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator( + color: Theme.of(context).primaryColor, + ), ), ); } diff --git a/lib/settings.dart b/lib/settings.dart index fb9f1a3..b0b5970 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -117,10 +117,13 @@ class Settings { //Colors @JsonKey(toJson: _colorToJson, fromJson: _colorFromJson) - Color primaryColor = Colors.blue; + Color primaryColor = Color(0xFFA238FF); + static const deezerBg = Color(0xFF0F0D13); + static const deezerBottom = Color(0xFF1B191F); + static const secondaryText = Color(0xFFA9A6AA); static _colorToJson(Color c) => c.value; - static _colorFromJson(int? v) => v == null ? Colors.blue : Color(v); + static _colorFromJson(int? v) => v == null ? Color(0xFFA238FF) : Color(v); @JsonKey(defaultValue: false) bool useArtColor = false; @@ -255,8 +258,6 @@ class Settings { return true; } - static const deezerBg = Color(0xFF1F1A16); - static const deezerBottom = Color(0xFF1b1714); TextTheme? get textTheme => (font == 'Deezer') ? null : GoogleFonts.getTextTheme(font, @@ -394,18 +395,24 @@ class Settings { }), ), bottomAppBarTheme: - const BottomAppBarTheme(color: Color(0xff424242))), + const BottomAppBarTheme(color: Color(0xFF0F0D13))), Themes.Deezer: ThemeData( useMaterial3: false, brightness: Brightness.dark, textTheme: textTheme, fontFamily: _fontFamily, primaryColor: primaryColor, + unselectedWidgetColor: secondaryText, sliderTheme: _sliderTheme, scaffoldBackgroundColor: deezerBg, - dialogBackgroundColor: deezerBottom, + dialogBackgroundColor: deezerBg, + hintColor: secondaryText, + inputDecorationTheme: const InputDecorationTheme( + hintStyle: TextStyle(color: secondaryText), + labelStyle: TextStyle(color: secondaryText), + ), bottomSheetTheme: - const BottomSheetThemeData(backgroundColor: deezerBottom), + const BottomSheetThemeData(backgroundColor: deezerBg), cardColor: deezerBg, outlinedButtonTheme: outlinedButtonTheme, textButtonTheme: textButtonTheme, @@ -459,15 +466,15 @@ class Settings { return null; }), ), - bottomAppBarTheme: const BottomAppBarTheme(color: deezerBottom)), + bottomAppBarTheme: const BottomAppBarTheme(color: deezerBg)), Themes.Black: ThemeData( useMaterial3: false, brightness: Brightness.dark, textTheme: textTheme, fontFamily: _fontFamily, primaryColor: primaryColor, - scaffoldBackgroundColor: Colors.black, - dialogBackgroundColor: Colors.black, + scaffoldBackgroundColor: deezerBg, + dialogBackgroundColor: deezerBg, sliderTheme: _sliderTheme, bottomSheetTheme: const BottomSheetThemeData( backgroundColor: Colors.black, diff --git a/lib/ui/elements.dart b/lib/ui/elements.dart index 03028b0..da84d06 100644 --- a/lib/ui/elements.dart +++ b/lib/ui/elements.dart @@ -13,8 +13,9 @@ class LeadingIcon extends StatelessWidget { return Container( width: 42.0, height: 42.0, - decoration: - BoxDecoration(color: (color ?? Theme.of(context).primaryColor).withOpacity(1.0), shape: BoxShape.circle), + decoration: BoxDecoration( + color: (color ?? Theme.of(context).primaryColor).withOpacity(1.0), + shape: BoxShape.circle), child: Icon( icon, color: Colors.white, @@ -40,7 +41,8 @@ class FreezerAppBar extends StatelessWidget implements PreferredSizeWidget { //Should be specified if bottom is specified final double height; - const FreezerAppBar(this.title, {super.key, this.actions = const [], this.bottom, this.height = 56.0}); + const FreezerAppBar(this.title, + {super.key, this.actions = const [], this.bottom, this.height = 64.0}); @override Size get preferredSize => Size.fromHeight(height); @@ -48,18 +50,26 @@ class FreezerAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { return Theme( - data: ThemeData(primaryColor: (Theme.of(context).brightness == Brightness.light) ? Colors.white : Colors.black), + data: ThemeData( + primaryColor: (Theme.of(context).brightness == Brightness.light) + ? Colors.white + : Colors.black), child: AppBar( - systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Theme.of(context).brightness), + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Theme.of(context).brightness), elevation: 0.0, backgroundColor: Theme.of(context).scaffoldBackgroundColor, - foregroundColor: (Theme.of(context).brightness == Brightness.light) ? Colors.black : Colors.white, - title: Text( + foregroundColor: (Theme.of(context).brightness == Brightness.light) + ? Colors.black + : Colors.white, + title: Container( + child: Text( title, style: const TextStyle( fontWeight: FontWeight.w900, + fontSize: 48, ), - ), + )), actions: actions, bottom: bottom as PreferredSizeWidget?, ), diff --git a/lib/ui/home_screen.dart b/lib/ui/home_screen.dart index c2b1434..1c071e0 100644 --- a/lib/ui/home_screen.dart +++ b/lib/ui/home_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; +import 'package:refreezer/fonts/deezer_icons.dart'; import '../api/deezer.dart'; import '../api/definitions.dart'; @@ -23,6 +24,8 @@ class HomeScreen extends StatelessWidget { return Scaffold( appBar: const HomeAppBar(), body: SingleChildScrollView( + child: Container( + padding: EdgeInsets.only(bottom: 72.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -32,7 +35,7 @@ class HomeScreen extends StatelessWidget { ) ], ), - )); + ))); } } @@ -49,7 +52,7 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget { actions: [ IconButton( icon: Icon( - Icons.file_download, + DeezerIcons.download_fill, semanticLabel: 'Download'.i18n, ), onPressed: () { @@ -59,7 +62,7 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget { ), IconButton( icon: Icon( - Icons.settings, + DeezerIcons.settings, semanticLabel: 'Settings'.i18n, ), onPressed: () { diff --git a/lib/ui/library.dart b/lib/ui/library.dart index 4d5b7e1..37a01b9 100644 --- a/lib/ui/library.dart +++ b/lib/ui/library.dart @@ -6,6 +6,7 @@ import 'package:fluttericon/font_awesome5_icons.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get_it/get_it.dart'; import 'package:logging/logging.dart'; +import 'package:refreezer/fonts/deezer_icons.dart'; import '../api/cache.dart'; import '../api/deezer.dart'; @@ -37,7 +38,7 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget { actions: [ IconButton( icon: Icon( - Icons.file_download, + DeezerIcons.download, semanticLabel: 'Download'.i18n, ), onPressed: () { @@ -47,7 +48,7 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget { ), IconButton( icon: Icon( - Icons.settings, + DeezerIcons.settings, semanticLabel: 'Settings'.i18n, ), onPressed: () { @@ -76,7 +77,7 @@ class LibraryScreen extends StatelessWidget { ListTile( title: Text('Downloads'.i18n), leading: - const LeadingIcon(Icons.file_download, color: Colors.grey), + const LeadingIcon(DeezerIcons.download, color: Colors.grey), subtitle: Text( 'Downloading is currently stopped, click here to resume.' .i18n), @@ -88,7 +89,8 @@ class LibraryScreen extends StatelessWidget { ), ListTile( title: Text('Shuffle'.i18n), - leading: const LeadingIcon(Icons.shuffle, color: Color(0xffeca704)), + leading: const LeadingIcon(DeezerIcons.shuffle, + color: Color(0xffeca704)), onTap: () async { List tracks = await deezerAPI.libraryShuffle(); GetIt.I().playFromTrackList( @@ -112,7 +114,8 @@ class LibraryScreen extends StatelessWidget { ), ListTile( title: Text('Albums'.i18n), - leading: const LeadingIcon(Icons.album, color: Color(0xff4b2e7e)), + leading: + const LeadingIcon(DeezerIcons.album, color: Color(0xff4b2e7e)), onTap: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => const LibraryAlbums())); @@ -224,7 +227,7 @@ class LibraryScreen extends StatelessWidget { ), ListTile( title: Text('Offline albums'.i18n), - leading: const Icon(Icons.album), + leading: const Icon(DeezerIcons.album), trailing: Text(data[1]), ), ListTile( @@ -715,7 +718,7 @@ class _LibraryAlbumsState extends State { ), PopupMenuButton( color: Theme.of(context).scaffoldBackgroundColor, - child: const Icon(Icons.sort, size: 32.0), + child: const Icon(DeezerIcons.sort, size: 32.0), onSelected: (SortType s) async { setState(() => _sort.type = s); //Save to cache @@ -962,7 +965,7 @@ class _LibraryArtistsState extends State { child: Text('Popularity'.i18n, style: popupMenuTextStyle()), ), ], - child: const Icon(Icons.sort, size: 32.0), + child: const Icon(DeezerIcons.sort, size: 32.0), ), Container(width: 8.0), ], @@ -1138,7 +1141,7 @@ class _LibraryPlaylistsState extends State { child: Text('Alphabetic'.i18n, style: popupMenuTextStyle()), ), ], - child: const Icon(Icons.sort, size: 32.0), + child: const Icon(DeezerIcons.sort, size: 32.0), ), Container(width: 8.0), ], diff --git a/lib/ui/menu.dart b/lib/ui/menu.dart index 545b2f1..c8599e1 100644 --- a/lib/ui/menu.dart +++ b/lib/ui/menu.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get_it/get_it.dart'; import 'package:numberpicker/numberpicker.dart'; +import 'package:refreezer/fonts/deezer_icons.dart'; import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @@ -23,7 +24,8 @@ class MenuSheet { Function navigateCallback; // Use no-op callback if not provided - MenuSheet({Function? navigateCallback}) : navigateCallback = navigateCallback ?? (() {}); + MenuSheet({Function? navigateCallback}) + : navigateCallback = navigateCallback ?? (() {}); //=================== // DEFAULT @@ -36,7 +38,10 @@ class MenuSheet { builder: (BuildContext context) { return ConstrainedBox( constraints: BoxConstraints( - maxHeight: (MediaQuery.of(context).orientation == Orientation.landscape) ? 220 : 350, + maxHeight: + (MediaQuery.of(context).orientation == Orientation.landscape) + ? 220 + : 350, ), child: SingleChildScrollView( child: Column(children: options), @@ -83,7 +88,8 @@ class MenuSheet { maxLines: 1, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold), + style: const TextStyle( + fontSize: 22.0, fontWeight: FontWeight.bold), ), Text( track.artistString ?? '', @@ -112,7 +118,10 @@ class MenuSheet { ), ConstrainedBox( constraints: BoxConstraints( - maxHeight: (MediaQuery.of(context).orientation == Orientation.landscape) ? 200 : 350, + maxHeight: (MediaQuery.of(context).orientation == + Orientation.landscape) + ? 200 + : 350, ), child: SingleChildScrollView( child: Column(children: options), @@ -125,7 +134,9 @@ class MenuSheet { //Default track options void defaultTrackMenu(Track track, - {required BuildContext context, List options = const [], Function? onRemove}) { + {required BuildContext context, + List options = const [], + Function? onRemove}) { showWithTrack(context, track, [ addToQueueNext(track, context), addToQueue(track, context), @@ -138,7 +149,8 @@ class MenuSheet { shareTile('track', track.id ?? ''), playMix(track, context), showAlbum(track.album!, context), - ...List.generate(track.artists?.length ?? 0, (i) => showArtist(track.artists![i], context)), + ...List.generate(track.artists?.length ?? 0, + (i) => showArtist(track.artists![i], context)), ...options ]); } @@ -152,7 +164,8 @@ class MenuSheet { leading: const Icon(Icons.playlist_play), onTap: () async { //-1 = next - await GetIt.I().insertQueueItem(-1, t.toMediaItem()); + await GetIt.I() + .insertQueueItem(-1, t.toMediaItem()); if (context.mounted) _close(context); }); @@ -166,7 +179,7 @@ class MenuSheet { Widget addTrackFavorite(Track t, BuildContext context) => ListTile( title: Text('Add track to favorites'.i18n), - leading: const Icon(Icons.favorite), + leading: const Icon(DeezerIcons.heart_fill), onTap: () async { await deezerAPI.addFavoriteTrack(t.id!); //Make track offline, if favorites are offline @@ -175,7 +188,9 @@ class MenuSheet { downloadManager.addOfflinePlaylist(p); } Fluttertoast.showToast( - msg: 'Added to library'.i18n, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT); + msg: 'Added to library'.i18n, + gravity: ToastGravity.BOTTOM, + toastLength: Toast.LENGTH_SHORT); //Add to cache cache.libraryTracks ??= []; cache.libraryTracks?.add(t.id!); @@ -185,9 +200,11 @@ class MenuSheet { Widget downloadTrack(Track t, BuildContext context) => ListTile( title: Text('Download'.i18n), - leading: const Icon(Icons.file_download), + leading: const Icon(DeezerIcons.download), onTap: () async { - if (await downloadManager.addOfflineTrack(t, private: false, isSingleton: true) != false) { + if (await downloadManager.addOfflineTrack(t, + private: false, isSingleton: true) != + false) { showDownloadStartedToast(); } if (context.mounted) _close(context); @@ -221,9 +238,10 @@ class MenuSheet { }, ); - Widget removeFromPlaylist(Track t, Playlist p, BuildContext context) => ListTile( + Widget removeFromPlaylist(Track t, Playlist p, BuildContext context) => + ListTile( title: Text('Remove from playlist'.i18n), - leading: const Icon(Icons.delete), + leading: const Icon(DeezerIcons.trash), onTap: () async { await deezerAPI.removeFromPlaylist(t.id!, p.id!); Fluttertoast.showToast( @@ -235,9 +253,10 @@ class MenuSheet { }, ); - Widget removeFavoriteTrack(Track t, BuildContext context, {onUpdate}) => ListTile( + Widget removeFavoriteTrack(Track t, BuildContext context, {onUpdate}) => + ListTile( title: Text('Remove favorite'.i18n), - leading: const Icon(Icons.delete), + leading: const Icon(DeezerIcons.trash), onTap: () async { await deezerAPI.removeFavorite(t.id!); //Check if favorites playlist is offline, update it @@ -248,7 +267,9 @@ class MenuSheet { //Remove from cache cache.libraryTracks?.removeWhere((i) => i == t.id); Fluttertoast.showToast( - msg: 'Track removed from library'.i18n, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM); + msg: 'Track removed from library'.i18n, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM); if (onUpdate != null) onUpdate(); if (context.mounted) _close(context); }, @@ -264,7 +285,8 @@ class MenuSheet { leading: const Icon(Icons.recent_actors), onTap: () { if (context.mounted) _close(context); - customNavigatorKey.currentState?.push(MaterialPageRoute(builder: (context) => ArtistDetails(a))); + customNavigatorKey.currentState + ?.push(MaterialPageRoute(builder: (context) => ArtistDetails(a))); navigateCallback(); }, @@ -279,7 +301,8 @@ class MenuSheet { leading: const Icon(Icons.album), onTap: () { if (context.mounted) _close(context); - customNavigatorKey.currentState?.push(MaterialPageRoute(builder: (context) => AlbumDetails(a))); + customNavigatorKey.currentState + ?.push(MaterialPageRoute(builder: (context) => AlbumDetails(a))); navigateCallback(); }, @@ -323,7 +346,9 @@ class MenuSheet { //Default album options void defaultAlbumMenu(Album album, - {required BuildContext context, List options = const [], Function? onRemove}) { + {required BuildContext context, + List options = const [], + Function? onRemove}) { show(context, [ (album.library != null && onRemove != null) ? removeAlbum(album, context, onRemove: onRemove) @@ -341,7 +366,7 @@ class MenuSheet { Widget downloadAlbum(Album a, BuildContext context) => ListTile( title: Text('Download'.i18n), - leading: const Icon(Icons.file_download), + leading: const Icon(DeezerIcons.download), onTap: () async { if (context.mounted) _close(context); if (await downloadManager.addOfflineAlbum(a, private: false) != false) { @@ -365,15 +390,18 @@ class MenuSheet { leading: const Icon(Icons.library_music), onTap: () async { await deezerAPI.addFavoriteAlbum(a.id!); - Fluttertoast.showToast(msg: 'Added to library'.i18n, gravity: ToastGravity.BOTTOM); + Fluttertoast.showToast( + msg: 'Added to library'.i18n, gravity: ToastGravity.BOTTOM); if (context.mounted) _close(context); }, ); //Remove album from favorites - Widget removeAlbum(Album a, BuildContext context, {required Function onRemove}) => ListTile( + Widget removeAlbum(Album a, BuildContext context, + {required Function onRemove}) => + ListTile( title: Text('Remove album'.i18n), - leading: const Icon(Icons.delete), + leading: const Icon(DeezerIcons.trash), onTap: () async { await deezerAPI.removeAlbum(a.id!); await downloadManager.removeOfflineAlbum(a.id!); @@ -392,9 +420,13 @@ class MenuSheet { //=================== void defaultArtistMenu(Artist artist, - {required BuildContext context, List options = const [], Function? onRemove}) { + {required BuildContext context, + List options = const [], + Function? onRemove}) { show(context, [ - (artist.library != null) ? removeArtist(artist, context, onRemove: onRemove) : favoriteArtist(artist, context), + (artist.library != null) + ? removeArtist(artist, context, onRemove: onRemove) + : favoriteArtist(artist, context), shareTile('artist', artist.id!), ...options ]); @@ -404,13 +436,16 @@ class MenuSheet { // ARTIST OPTIONS //=================== - Widget removeArtist(Artist a, BuildContext context, {Function? onRemove}) => ListTile( + Widget removeArtist(Artist a, BuildContext context, {Function? onRemove}) => + ListTile( title: Text('Remove from favorites'.i18n), - leading: const Icon(Icons.delete), + leading: const Icon(DeezerIcons.trash), onTap: () async { await deezerAPI.removeArtist(a.id!); Fluttertoast.showToast( - msg: 'Artist removed from library'.i18n, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM); + msg: 'Artist removed from library'.i18n, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM); if (onRemove != null) onRemove(); if (context.mounted) _close(context); }, @@ -418,11 +453,13 @@ class MenuSheet { Widget favoriteArtist(Artist a, BuildContext context) => ListTile( title: Text('Add to favorites'.i18n), - leading: const Icon(Icons.favorite), + leading: const Icon(DeezerIcons.heart_fill), onTap: () async { await deezerAPI.addFavoriteArtist(a.id!); Fluttertoast.showToast( - msg: 'Added to library'.i18n, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM); + msg: 'Added to library'.i18n, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM); if (context.mounted) _close(context); }, ); @@ -432,7 +469,10 @@ class MenuSheet { //=================== void defaultPlaylistMenu(Playlist playlist, - {required BuildContext context, List options = const [], Function? onRemove, Function? onUpdate}) { + {required BuildContext context, + List options = const [], + Function? onRemove, + Function? onUpdate}) { show(context, [ (playlist.library != null) ? removePlaylistLibrary(playlist, context, onRemove: onRemove) @@ -440,7 +480,8 @@ class MenuSheet { addPlaylistOffline(playlist, context), downloadPlaylist(playlist, context), shareTile('playlist', playlist.id!), - if (playlist.user?.id == deezerAPI.userId) editPlaylist(playlist, context: context, onUpdate: onUpdate), + if (playlist.user?.id == deezerAPI.userId) + editPlaylist(playlist, context: context, onUpdate: onUpdate), ...options ]); } @@ -449,9 +490,11 @@ class MenuSheet { // PLAYLIST OPTIONS //=================== - Widget removePlaylistLibrary(Playlist p, BuildContext context, {Function? onRemove}) => ListTile( + Widget removePlaylistLibrary(Playlist p, BuildContext context, + {Function? onRemove}) => + ListTile( title: Text('Remove from library'.i18n), - leading: const Icon(Icons.delete), + leading: const Icon(DeezerIcons.trash), onTap: () async { if (p.user?.id?.trim() == deezerAPI.userId) { //Delete playlist if own @@ -468,10 +511,12 @@ class MenuSheet { Widget addPlaylistLibrary(Playlist p, BuildContext context) => ListTile( title: Text('Add playlist to library'.i18n), - leading: const Icon(Icons.favorite), + leading: const Icon(DeezerIcons.heart_fill), onTap: () async { await deezerAPI.addPlaylist(p.id!); - Fluttertoast.showToast(msg: 'Added playlist to library'.i18n, gravity: ToastGravity.BOTTOM); + Fluttertoast.showToast( + msg: 'Added playlist to library'.i18n, + gravity: ToastGravity.BOTTOM); if (context.mounted) _close(context); }, ); @@ -490,20 +535,25 @@ class MenuSheet { Widget downloadPlaylist(Playlist p, BuildContext context) => ListTile( title: Text('Download playlist'.i18n), - leading: const Icon(Icons.file_download), + leading: const Icon(DeezerIcons.download), onTap: () async { if (context.mounted) _close(context); - if (await downloadManager.addOfflinePlaylist(p, private: false) != false) { + if (await downloadManager.addOfflinePlaylist(p, private: false) != + false) { showDownloadStartedToast(); } }, ); - Widget editPlaylist(Playlist p, {required BuildContext context, Function? onUpdate}) => ListTile( + Widget editPlaylist(Playlist p, + {required BuildContext context, Function? onUpdate}) => + ListTile( title: Text('Edit playlist'.i18n), - leading: const Icon(Icons.edit), + leading: const Icon(DeezerIcons.pen), onTap: () async { - await showDialog(context: context, builder: (context) => CreatePlaylistDialog(playlist: p)); + await showDialog( + context: context, + builder: (context) => CreatePlaylistDialog(playlist: p)); if (context.mounted) _close(context); if (onUpdate != null) onUpdate(); }, @@ -513,13 +563,19 @@ class MenuSheet { // SHOW/EPISODE //=================== - defaultShowEpisodeMenu(Show s, ShowEpisode e, {required BuildContext context, List options = const []}) { - show(context, [shareTile('episode', e.id!), shareShow(s.id!), downloadExternalEpisode(e), ...options]); + defaultShowEpisodeMenu(Show s, ShowEpisode e, + {required BuildContext context, List options = const []}) { + show(context, [ + shareTile('episode', e.id!), + shareShow(s.id!), + downloadExternalEpisode(e), + ...options + ]); } Widget shareShow(String id) => ListTile( title: Text('Share show'.i18n), - leading: const Icon(Icons.share), + leading: const Icon(DeezerIcons.share_android), onTap: () async { Share.share('https://deezer.com/show/$id'); }, @@ -528,7 +584,7 @@ class MenuSheet { //Open direct download link in browser Widget downloadExternalEpisode(ShowEpisode e) => ListTile( title: Text('Download externally'.i18n), - leading: const Icon(Icons.file_download), + leading: const Icon(DeezerIcons.download), onTap: () async { if (e.url != null) await launchUrlString(e.url!); }, @@ -539,7 +595,10 @@ class MenuSheet { //=================== showDownloadStartedToast() { - Fluttertoast.showToast(msg: 'Downloads added!'.i18n, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT); + Fluttertoast.showToast( + msg: 'Downloads added!'.i18n, + gravity: ToastGravity.BOTTOM, + toastLength: Toast.LENGTH_SHORT); } //Create playlist @@ -572,20 +631,24 @@ class MenuSheet { ); Widget wakelock(BuildContext context) => ListTile( - title: Text(cache.wakelock ? 'Allow screen to turn off'.i18n : 'Keep the screen on'.i18n), + title: Text(cache.wakelock + ? 'Allow screen to turn off'.i18n + : 'Keep the screen on'.i18n), leading: const Icon(Icons.screen_lock_portrait), onTap: () async { _close(context); //Enable if (!cache.wakelock) { WakelockPlus.enable(); - Fluttertoast.showToast(msg: 'Wakelock enabled!'.i18n, gravity: ToastGravity.BOTTOM); + Fluttertoast.showToast( + msg: 'Wakelock enabled!'.i18n, gravity: ToastGravity.BOTTOM); cache.wakelock = true; return; } //Disable WakelockPlus.disable(); - Fluttertoast.showToast(msg: 'Wakelock disabled!'.i18n, gravity: ToastGravity.BOTTOM); + Fluttertoast.showToast( + msg: 'Wakelock disabled!'.i18n, gravity: ToastGravity.BOTTOM); cache.wakelock = false; }, ); @@ -622,7 +685,11 @@ class _SleepTimerDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ Text('Hours:'.i18n), - NumberPicker(value: hours, minValue: 0, maxValue: 69, onChanged: (v) => setState(() => hours = v)), + NumberPicker( + value: hours, + minValue: 0, + maxValue: 69, + onChanged: (v) => setState(() => hours = v)), ], ), Column( @@ -630,7 +697,10 @@ class _SleepTimerDialogState extends State { children: [ Text('Minutes:'.i18n), NumberPicker( - value: minutes, minValue: 0, maxValue: 60, onChanged: (v) => setState(() => minutes = v)), + value: minutes, + minValue: 0, + maxValue: 60, + onChanged: (v) => setState(() => minutes = v)), ], ), ], @@ -666,7 +736,8 @@ class _SleepTimerDialogState extends State { Duration duration = Duration(hours: hours, minutes: minutes); cache.sleepTimer?.cancel(); //Create timer - cache.sleepTimer = Stream.fromFuture(Future.delayed(duration)).listen((_) { + cache.sleepTimer = + Stream.fromFuture(Future.delayed(duration)).listen((_) { GetIt.I().pause(); cache.sleepTimer?.cancel(); cache.sleepTimerTime = null; @@ -837,16 +908,20 @@ class _CreatePlaylistDialogState extends State { onPressed: () async { if (edit) { //Update - await deezerAPI.updatePlaylist( - widget.playlist!.id!, _titleController!.value.text, _descController!.value.text, + await deezerAPI.updatePlaylist(widget.playlist!.id!, + _titleController!.value.text, _descController!.value.text, status: _playlistType); - Fluttertoast.showToast(msg: 'Playlist updated!'.i18n, gravity: ToastGravity.BOTTOM); + Fluttertoast.showToast( + msg: 'Playlist updated!'.i18n, gravity: ToastGravity.BOTTOM); } else { List tracks = []; tracks = widget.tracks?.map((t) => t.id!).toList() ?? []; await deezerAPI.createPlaylist(_title, - status: _playlistType, description: _description, trackIds: tracks); - Fluttertoast.showToast(msg: 'Playlist created!'.i18n, gravity: ToastGravity.BOTTOM); + status: _playlistType, + description: _description, + trackIds: tracks); + Fluttertoast.showToast( + msg: 'Playlist created!'.i18n, gravity: ToastGravity.BOTTOM); } if (context.mounted) Navigator.of(context).pop(); }, diff --git a/lib/ui/player_bar.dart b/lib/ui/player_bar.dart index 7e005e6..aeaa0ba 100644 --- a/lib/ui/player_bar.dart +++ b/lib/ui/player_bar.dart @@ -1,7 +1,12 @@ +import 'dart:async'; + import 'package:audio_service/audio_service.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get_it/get_it.dart'; +import 'package:palette_generator/palette_generator.dart'; +import 'package:refreezer/fonts/deezer_icons.dart'; import '../service/audio_service.dart'; import '../settings.dart'; @@ -10,6 +15,8 @@ import '../ui/router.dart'; import 'cached_image.dart'; import 'player_screen.dart'; +late Function updateColor; + class PlayerBar extends StatefulWidget { const PlayerBar({super.key}); @@ -18,110 +25,184 @@ class PlayerBar extends StatefulWidget { } class _PlayerBarState extends State { - final double iconSize = 28; + AudioPlayerHandler audioHandler = GetIt.I(); + Color? _bgColor; + StreamSubscription? _mediaItemSub; + final double iconSize = 20; //bool _gestureRegistered = false; + //Recover dominant color + Future _updateColor() async { + if (audioHandler.mediaItem.value == null) return; + + PaletteGenerator palette = await PaletteGenerator.fromImageProvider( + CachedNetworkImageProvider( + audioHandler.mediaItem.value?.extras?['thumb'] ?? + audioHandler.mediaItem.value?.artUri)); + + setState(() => _bgColor = palette.dominantColor!.color.withOpacity(1)); + } + + @override + void initState() { + _updateColor; + _mediaItemSub = audioHandler.mediaItem.listen((event) { + _updateColor(); + }); + + updateColor = _updateColor; + super.initState(); + } + + @override + void dispose() { + _mediaItemSub?.cancel(); + super.dispose(); + } + double get _progress { - if (GetIt.I().playbackState.value.processingState == AudioProcessingState.idle) return 0.0; + if (GetIt.I().playbackState.value.processingState == + AudioProcessingState.idle) return 0.0; if (GetIt.I().mediaItem.value == null) return 0.0; - if (GetIt.I().mediaItem.value!.duration!.inSeconds == 0) return 0.0; //Division by 0 - return GetIt.I().playbackState.value.position.inSeconds / + if (GetIt.I().mediaItem.value!.duration!.inSeconds == 0) + return 0.0; //Division by 0 + return GetIt.I() + .playbackState + .value + .position + .inSeconds / GetIt.I().mediaItem.value!.duration!.inSeconds; } @override Widget build(BuildContext context) { + scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor; + var focusNode = FocusNode(); - return GestureDetector( - key: UniqueKey(), - onHorizontalDragEnd: (DragEndDetails details) async { - if ((details.primaryVelocity ?? 0) < -100) { - // Swiped left - await GetIt.I().skipToPrevious(); - } else if ((details.primaryVelocity ?? 0) > 100) { - // Swiped right - await GetIt.I().skipToNext(); - } - }, - onVerticalDragEnd: (DragEndDetails details) async { - if ((details.primaryVelocity ?? 0) < -100) { - // Swiped up - Navigator.of(context).push(SlideBottomRoute(widget: const PlayerScreen())); - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( - systemNavigationBarColor: settings.themeData.scaffoldBackgroundColor, - )); - } /*else if ((details.primaryVelocity ?? 0) > 100) { + return Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: scaffoldBackgroundColor, + border: Border.all( + color: _bgColor != null + ? _bgColor!.withOpacity(0.7) + : Theme.of(context).scaffoldBackgroundColor), + borderRadius: BorderRadius.circular(15)), + child: GestureDetector( + key: UniqueKey(), + onHorizontalDragEnd: (DragEndDetails details) async { + if ((details.primaryVelocity ?? 0) < -100) { + // Swiped left + await GetIt.I().skipToNext(); + updateColor(); + } else if ((details.primaryVelocity ?? 0) > 100) { + // Swiped right + await GetIt.I().skipToPrevious(); + updateColor(); + } + }, + onVerticalDragEnd: (DragEndDetails details) async { + if ((details.primaryVelocity ?? 0) < -100) { + // Swiped up + Navigator.of(context) + .push(SlideBottomRoute(widget: const PlayerScreen())); + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + systemNavigationBarColor: _bgColor, + )); + } /*else if ((details.primaryVelocity ?? 0) > 100) { // Swiped down => no action }*/ - }, - child: StreamBuilder( - stream: Stream.periodic(const Duration(milliseconds: 250)), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (GetIt.I().mediaItem.value == null) { - return const SizedBox( - width: 0, - height: 0, - ); - } - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - // For Android TV: indicate focus by grey - color: focusNode.hasFocus ? Colors.black26 : Theme.of(context).bottomAppBarTheme.color, - child: ListTile( - dense: true, - focusNode: focusNode, - contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), - onTap: () { - Navigator.of(context).push(SlideBottomRoute(widget: const PlayerScreen())); - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( - systemNavigationBarColor: settings.themeData.scaffoldBackgroundColor, - )); - }, - leading: CachedImage( - width: 50, - height: 50, - url: GetIt.I().mediaItem.value?.extras?['thumb'] ?? - GetIt.I().mediaItem.value?.artUri, - ), - title: Text( - GetIt.I().mediaItem.value?.displayTitle ?? '', - overflow: TextOverflow.clip, - maxLines: 1, - ), - subtitle: Text( - GetIt.I().mediaItem.value?.displaySubtitle ?? '', - overflow: TextOverflow.clip, - maxLines: 1, - ), - trailing: IconTheme( - data: IconThemeData(color: settings.isDark ? Colors.white : Colors.grey[600]), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - PrevNextButton( - iconSize, - prev: true, - hidePrev: true, - ), - PlayPauseButton(iconSize), - PrevNextButton(iconSize) - ], - ), - )), - ), - SizedBox( - height: 3.0, - child: LinearProgressIndicator( - backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1), - color: Theme.of(context).primaryColor, - value: _progress, + updateColor(); + }, + child: StreamBuilder( + stream: Stream.periodic(const Duration(milliseconds: 250)), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (GetIt.I().mediaItem.value == null) { + return const SizedBox( + width: 0, + height: 0, + ); + } + return Container( + decoration: BoxDecoration( + color: _bgColor?.withOpacity(0.7), ), - ) - ], - ); - }), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + dense: true, + focusNode: focusNode, + contentPadding: + const EdgeInsets.symmetric(horizontal: 8.0), + onTap: () { + Navigator.of(context).push( + SlideBottomRoute(widget: const PlayerScreen())); + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + systemNavigationBarColor: Settings.deezerBottom, + )); + }, + leading: CachedImage( + width: 50, + height: 50, + url: GetIt.I() + .mediaItem + .value + ?.extras?['thumb'] ?? + GetIt.I() + .mediaItem + .value + ?.artUri, + ), + title: Text( + GetIt.I() + .mediaItem + .value + ?.displayTitle ?? + '', + overflow: TextOverflow.clip, + maxLines: 1, + ), + subtitle: Text( + GetIt.I() + .mediaItem + .value + ?.displaySubtitle ?? + '', + overflow: TextOverflow.clip, + maxLines: 1, + ), + trailing: IconTheme( + data: IconThemeData( + color: settings.isDark + ? Colors.white + : Colors.grey[600]), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + PrevNextButton( + iconSize, + prev: true, + hidePrev: true, + ), + PlayPauseButton(iconSize), + PrevNextButton(iconSize) + ], + ), + )), + SizedBox( + height: 3.0, + child: LinearProgressIndicator( + backgroundColor: _bgColor!.withOpacity(0.1), + color: _bgColor, + value: _progress, + ), + ) + ], + )); + }), + ), ); } } @@ -131,7 +212,8 @@ class PrevNextButton extends StatelessWidget { final bool prev; final bool hidePrev; - const PrevNextButton(this.size, {super.key, this.prev = false, this.hidePrev = false}); + const PrevNextButton(this.size, + {super.key, this.prev = false, this.hidePrev = false}); @override Widget build(BuildContext context) { @@ -143,7 +225,7 @@ class PrevNextButton extends StatelessWidget { if (!(queueState?.hasNext ?? false)) { return IconButton( icon: Icon( - Icons.skip_next, + DeezerIcons.skip_next_fill, semanticLabel: 'Play next'.i18n, ), iconSize: size, @@ -152,7 +234,7 @@ class PrevNextButton extends StatelessWidget { } return IconButton( icon: Icon( - Icons.skip_next, + DeezerIcons.skip_next_fill, semanticLabel: 'Play next'.i18n, ), iconSize: size, @@ -169,7 +251,7 @@ class PrevNextButton extends StatelessWidget { } return IconButton( icon: Icon( - Icons.skip_previous, + DeezerIcons.skip_back, semanticLabel: 'Play previous'.i18n, ), iconSize: size, @@ -178,7 +260,7 @@ class PrevNextButton extends StatelessWidget { } return IconButton( icon: Icon( - Icons.skip_previous, + DeezerIcons.skip_back, semanticLabel: 'Play previous'.i18n, ), iconSize: size, @@ -199,14 +281,17 @@ class PlayPauseButton extends StatefulWidget { _PlayPauseButtonState createState() => _PlayPauseButtonState(); } -class _PlayPauseButtonState extends State with SingleTickerProviderStateMixin { +class _PlayPauseButtonState extends State + with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { - _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); - _animation = Tween(begin: 0, end: 1).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); + _controller = AnimationController( + vsync: this, duration: const Duration(milliseconds: 200)); + _animation = Tween(begin: 0, end: 1) + .animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); super.initState(); } @@ -227,7 +312,9 @@ class _PlayPauseButtonState extends State with SingleTickerProv // Animated icon by pato05 // Morph from pause to play or from play to pause - if (playing || processingState == AudioProcessingState.ready || processingState == AudioProcessingState.idle) { + if (playing || + processingState == AudioProcessingState.ready || + processingState == AudioProcessingState.idle) { if (playing) { _controller.forward(); } else { @@ -236,14 +323,25 @@ class _PlayPauseButtonState extends State with SingleTickerProv return IconButton( splashRadius: widget.size, - icon: AnimatedIcon( - icon: AnimatedIcons.play_pause, - progress: _animation, - semanticLabel: playing ? 'Pause'.i18n : 'Play'.i18n, - ), + icon: AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + transitionBuilder: (child, anim) => RotationTransition( + turns: child.key == ValueKey('icon1') + ? Tween(begin: 1, end: 0.75).animate(anim) + : Tween(begin: 0.75, end: 1).animate(anim), + child: FadeTransition(opacity: anim, child: child), + ), + child: !playing + ? Icon(DeezerIcons.play_fill_small, + key: const ValueKey('Play')) + : Icon( + DeezerIcons.pause_fill_small, + key: const ValueKey('Pause'), + )), iconSize: widget.size, - onPressed: - playing ? () => GetIt.I().pause() : () => GetIt.I().play()); + onPressed: playing + ? () => GetIt.I().pause() + : () => GetIt.I().play()); } switch (processingState) { @@ -256,7 +354,8 @@ class _PlayPauseButtonState extends State with SingleTickerProv child: Center( child: Transform.scale( scale: 0.85, // Adjust the scale to 75% of the original size - child: const CircularProgressIndicator(), + child: CircularProgressIndicator( + color: Theme.of(context).primaryColor), ), ), ); diff --git a/lib/ui/player_screen.dart b/lib/ui/player_screen.dart index 806be6b..0b3e6c0 100644 --- a/lib/ui/player_screen.dart +++ b/lib/ui/player_screen.dart @@ -5,6 +5,8 @@ import 'dart:ui'; import 'package:async/async.dart'; import 'package:audio_service/audio_service.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:refreezer/fonts/deezer_icons.dart'; +import 'package:refreezer/utils/navigator_keys.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -13,7 +15,6 @@ import 'package:get_it/get_it.dart'; import 'package:just_audio/just_audio.dart'; import 'package:marquee/marquee.dart'; import 'package:palette_generator/palette_generator.dart'; -import 'package:refreezer/utils/navigator_keys.dart'; import 'package:rxdart/rxdart.dart'; import 'package:visibility_detector/visibility_detector.dart'; @@ -21,7 +22,6 @@ import '../api/cache.dart'; import '../api/deezer.dart'; import '../api/definitions.dart'; import '../api/download.dart'; -import '../fonts/refreezer_icons.dart'; import '../service/audio_service.dart'; import '../settings.dart'; import '../translations.i18n.dart'; @@ -62,21 +62,25 @@ class _PlayerScreenState extends State { //BG Image if (settings.blurPlayerBackground) { setState(() { - _blurImage = - NetworkImage(audioHandler.mediaItem.value?.extras?['thumb'] ?? audioHandler.mediaItem.value?.artUri); + _blurImage = NetworkImage( + audioHandler.mediaItem.value?.extras?['thumb'] ?? + audioHandler.mediaItem.value?.artUri); }); } //Run in isolate - PaletteGenerator palette = await PaletteGenerator.fromImageProvider(CachedNetworkImageProvider( - audioHandler.mediaItem.value?.extras?['thumb'] ?? audioHandler.mediaItem.value?.artUri)); + PaletteGenerator palette = await PaletteGenerator.fromImageProvider( + CachedNetworkImageProvider( + audioHandler.mediaItem.value?.extras?['thumb'] ?? + audioHandler.mediaItem.value?.artUri)); //Update notification if (settings.blurPlayerBackground) { SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( statusBarColor: palette.dominantColor!.color.withOpacity(0.25), - systemNavigationBarColor: - Color.alphaBlend(palette.dominantColor!.color.withOpacity(0.25), scaffoldBackgroundColor))); + systemNavigationBarColor: Color.alphaBlend( + palette.dominantColor!.color.withOpacity(0.25), + scaffoldBackgroundColor))); } //Color gradient @@ -85,10 +89,16 @@ class _PlayerScreenState extends State { statusBarColor: palette.dominantColor!.color.withOpacity(0.7), )); setState(() => _bgGradient = LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [palette.dominantColor!.color.withOpacity(0.7), const Color.fromARGB(0, 0, 0, 0)], - stops: const [0.0, 0.6])); + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + palette.dominantColor!.color.withOpacity(0.7), + Settings.deezerBg + ], + stops: const [ + 0.0, + 0.6 + ])); } } @@ -108,7 +118,8 @@ class _PlayerScreenState extends State { _mediaItemSub?.cancel(); //Fix bottom buttons SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( - systemNavigationBarColor: settings.themeData.bottomAppBarTheme.color, statusBarColor: Colors.transparent)); + systemNavigationBarColor: settings.themeData.bottomAppBarTheme.color, + statusBarColor: Colors.transparent)); super.dispose(); } @@ -120,7 +131,9 @@ class _PlayerScreenState extends State { return Scaffold( body: SafeArea( child: Container( - decoration: BoxDecoration(gradient: settings.blurPlayerBackground ? null : _bgGradient), + decoration: BoxDecoration( + gradient: + settings.blurPlayerBackground ? null : _bgGradient), child: Stack( children: [ if (settings.blurPlayerBackground) @@ -130,7 +143,9 @@ class _PlayerScreenState extends State { image: DecorationImage( image: _blurImage ?? const NetworkImage(''), fit: BoxFit.fill, - colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.25), BlendMode.dstATop))), + colorFilter: ColorFilter.mode( + Colors.black.withOpacity(0.25), + BlendMode.dstATop))), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container(color: Colors.transparent), @@ -138,7 +153,8 @@ class _PlayerScreenState extends State { ), ), StreamBuilder( - stream: StreamZip([audioHandler.playbackState, audioHandler.mediaItem]), + stream: StreamZip( + [audioHandler.playbackState, audioHandler.mediaItem]), builder: (BuildContext context, AsyncSnapshot snapshot) { //When disconnected if (audioHandler.mediaItem.value == null) { @@ -214,26 +230,45 @@ class _PlayerScreenHorizontalState extends State { children: [ SizedBox( height: ScreenUtil().setSp(50), - child: GetIt.I().mediaItem.value!.displayTitle!.length >= 22 + child: GetIt.I() + .mediaItem + .value! + .displayTitle! + .length >= + 22 ? Marquee( - text: GetIt.I().mediaItem.value!.displayTitle!, - style: TextStyle(fontSize: ScreenUtil().setSp(40), fontWeight: FontWeight.bold), + text: GetIt.I() + .mediaItem + .value! + .displayTitle!, + style: TextStyle( + fontSize: ScreenUtil().setSp(40), + fontWeight: FontWeight.bold), blankSpace: 32.0, startPadding: 10.0, accelerationDuration: const Duration(seconds: 1), pauseAfterRound: const Duration(seconds: 2), ) : Text( - GetIt.I().mediaItem.value!.displayTitle!, + GetIt.I() + .mediaItem + .value! + .displayTitle!, maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: ScreenUtil().setSp(40), fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: ScreenUtil().setSp(40), + fontWeight: FontWeight.bold), )), Container( height: 4, ), Text( - GetIt.I().mediaItem.value!.displaySubtitle ?? '', + GetIt.I() + .mediaItem + .value! + .displaySubtitle ?? + '', maxLines: 1, textAlign: TextAlign.center, overflow: TextOverflow.clip, @@ -261,13 +296,16 @@ class _PlayerScreenHorizontalState extends State { LyricsIconButton(12, afterOnPressed: updateColor), IconButton( icon: Icon( - Icons.file_download, + DeezerIcons.download, size: ScreenUtil().setWidth(12), semanticLabel: 'Download'.i18n, ), onPressed: () async { - Track t = Track.fromMediaItem(GetIt.I().mediaItem.value!); - if (await downloadManager.addOfflineTrack(t, private: false, isSingleton: true) != false) { + Track t = Track.fromMediaItem( + GetIt.I().mediaItem.value!); + if (await downloadManager.addOfflineTrack(t, + private: false, isSingleton: true) != + false) { Fluttertoast.showToast( msg: 'Downloads added!'.i18n, gravity: ToastGravity.BOTTOM, @@ -328,26 +366,45 @@ class _PlayerScreenVerticalState extends State { children: [ SizedBox( height: ScreenUtil().setSp(26), - child: (GetIt.I().mediaItem.value?.displayTitle ?? '').length >= 26 + child: (GetIt.I() + .mediaItem + .value + ?.displayTitle ?? + '') + .length >= + 26 ? Marquee( - text: GetIt.I().mediaItem.value?.displayTitle ?? '', - style: TextStyle(fontSize: ScreenUtil().setSp(22), fontWeight: FontWeight.bold), + text: GetIt.I() + .mediaItem + .value + ?.displayTitle ?? + '', + style: TextStyle( + fontSize: ScreenUtil().setSp(22), + fontWeight: FontWeight.bold), blankSpace: 32.0, startPadding: 10.0, accelerationDuration: const Duration(seconds: 1), pauseAfterRound: const Duration(seconds: 2), ) : Text( - GetIt.I().mediaItem.value?.displayTitle ?? '', + GetIt.I() + .mediaItem + .value + ?.displayTitle ?? + '', maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: ScreenUtil().setSp(22), fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: ScreenUtil().setSp(22), + fontWeight: FontWeight.bold), )), Container( height: 4, ), Text( - GetIt.I().mediaItem.value?.displaySubtitle ?? '', + GetIt.I().mediaItem.value?.displaySubtitle ?? + '', maxLines: 1, textAlign: TextAlign.center, overflow: TextOverflow.clip, @@ -369,15 +426,20 @@ class _PlayerScreenVerticalState extends State { LyricsIconButton(20, afterOnPressed: updateColor), IconButton( icon: Icon( - Icons.file_download, + DeezerIcons.download, size: ScreenUtil().setWidth(20), semanticLabel: 'Download'.i18n, ), onPressed: () async { - Track t = Track.fromMediaItem(GetIt.I().mediaItem.value!); - if (await downloadManager.addOfflineTrack(t, private: false, isSingleton: true) != false) { + Track t = Track.fromMediaItem( + GetIt.I().mediaItem.value!); + if (await downloadManager.addOfflineTrack(t, + private: false, isSingleton: true) != + false) { Fluttertoast.showToast( - msg: 'Downloads added!'.i18n, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT); + msg: 'Downloads added!'.i18n, + gravity: ToastGravity.BOTTOM, + toastLength: Toast.LENGTH_SHORT); } }, ), @@ -407,7 +469,8 @@ class _QualityInfoWidgetState extends State { //Load data from native void _load() async { if (audioHandler.mediaItem.value == null) return; - Map? data = await DownloadManager.platform.invokeMethod('getStreamInfo', {'id': audioHandler.mediaItem.value!.id}); + Map? data = await DownloadManager.platform.invokeMethod( + 'getStreamInfo', {'id': audioHandler.mediaItem.value!.id}); //N/A if (data == null) { if (mounted) setState(() => value = ''); @@ -449,7 +512,8 @@ class _QualityInfoWidgetState extends State { return TextButton( child: Text(value), onPressed: () { - Navigator.of(context).push(MaterialPageRoute(builder: (context) => const QualitySettings())); + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const QualitySettings())); }, ); } @@ -475,26 +539,30 @@ class LyricsIconButton extends StatelessWidget { @override Widget build(BuildContext context) { - Track track = Track.fromMediaItem(GetIt.I().mediaItem.value!); + Track track = + Track.fromMediaItem(GetIt.I().mediaItem.value!); bool isEnabled = (track.lyrics?.id ?? '0') != '0'; return Opacity( - opacity: isEnabled ? 1.0 : 0.7, // Full opacity for enabled, reduced for disabled + opacity: isEnabled + ? 1.0 + : 0.7, // Full opacity for enabled, reduced for disabled child: IconButton( icon: Icon( //Icons.lyrics, - ReFreezerIcons.lyrics_mic, + DeezerIcons.microphone, size: ScreenUtil().setWidth(width), semanticLabel: 'Lyrics'.i18n, ), onPressed: isEnabled ? () async { //Fix bottom buttons - SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(statusBarColor: Colors.transparent)); + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( + statusBarColor: Colors.transparent)); - await Navigator.of(context) - .push(MaterialPageRoute(builder: (context) => LyricsScreen(trackId: track.id!))); + await Navigator.of(context).push(MaterialPageRoute( + builder: (context) => LyricsScreen(trackId: track.id!))); if (afterOnPressed != null) { afterOnPressed!(); @@ -519,16 +587,24 @@ class PlayerMenuButton extends StatelessWidget { semanticLabel: 'Options'.i18n, ), onPressed: () { - Track t = Track.fromMediaItem(GetIt.I().mediaItem.value!); + Track t = + Track.fromMediaItem(GetIt.I().mediaItem.value!); MenuSheet m = MenuSheet(navigateCallback: () { Navigator.of(context).pop(); }); - if (GetIt.I().mediaItem.value!.extras?['show'] == null) { - m.defaultTrackMenu(t, context: context, options: [m.sleepTimer(context), m.wakelock(context)]); + if (GetIt.I().mediaItem.value!.extras?['show'] == + null) { + m.defaultTrackMenu(t, + context: context, + options: [m.sleepTimer(context), m.wakelock(context)]); } else { m.defaultShowEpisodeMenu( - Show.fromJson(jsonDecode(GetIt.I().mediaItem.value!.extras?['show'])), - ShowEpisode.fromMediaItem(GetIt.I().mediaItem.value!), + Show.fromJson(jsonDecode(GetIt.I() + .mediaItem + .value! + .extras?['show'])), + ShowEpisode.fromMediaItem( + GetIt.I().mediaItem.value!), context: context, options: [m.sleepTimer(context), m.wakelock(context)]); } @@ -550,20 +626,20 @@ class _RepeatButtonState extends State { switch (GetIt.I().getLoopMode()) { case LoopMode.off: return Icon( - Icons.repeat, + DeezerIcons.repeat, size: widget.iconSize, semanticLabel: 'Repeat off'.i18n, ); case LoopMode.all: return Icon( - Icons.repeat, + DeezerIcons.repeat, color: Theme.of(context).primaryColor, size: widget.iconSize, semanticLabel: 'Repeat'.i18n, ); case LoopMode.one: return Icon( - Icons.repeat_one, + DeezerIcons.repeat_one, color: Theme.of(context).primaryColor, size: widget.iconSize, semanticLabel: 'Repeat one'.i18n, @@ -594,15 +670,17 @@ class PlaybackControls extends StatefulWidget { class _PlaybackControlsState extends State { AudioPlayerHandler audioHandler = GetIt.I(); Icon get libraryIcon { - if (cache.checkTrackFavorite(Track.fromMediaItem(audioHandler.mediaItem.value!))) { + if (cache.checkTrackFavorite( + Track.fromMediaItem(audioHandler.mediaItem.value!))) { return Icon( - Icons.favorite, + DeezerIcons.heart_fill, + color: settings.primaryColor, size: widget.iconSize * 0.44, semanticLabel: 'Unlove'.i18n, ); } return Icon( - Icons.favorite_border, + DeezerIcons.heart, size: widget.iconSize * 0.44, semanticLabel: 'Love'.i18n, ); @@ -618,7 +696,7 @@ class _PlaybackControlsState extends State { children: [ IconButton( icon: Icon( - Icons.sentiment_very_dissatisfied, + DeezerIcons.angry_face, size: widget.iconSize * 0.44, semanticLabel: 'Dislike'.i18n, ), @@ -629,22 +707,27 @@ class _PlaybackControlsState extends State { } }), PrevNextButton(widget.iconSize, prev: true), - PlayPauseButton(widget.iconSize * 1.25), + PlayPauseButton(widget.iconSize), PrevNextButton(widget.iconSize), IconButton( icon: libraryIcon, onPressed: () async { cache.libraryTracks ??= []; - if (cache.checkTrackFavorite(Track.fromMediaItem(audioHandler.mediaItem.value!))) { + if (cache.checkTrackFavorite( + Track.fromMediaItem(audioHandler.mediaItem.value!))) { //Remove from library - setState(() => cache.libraryTracks?.remove(audioHandler.mediaItem.value!.id)); - await deezerAPI.removeFavorite(audioHandler.mediaItem.value!.id); + setState(() => cache.libraryTracks + ?.remove(audioHandler.mediaItem.value!.id)); + await deezerAPI + .removeFavorite(audioHandler.mediaItem.value!.id); await cache.save(); } else { //Add - setState(() => cache.libraryTracks?.add(audioHandler.mediaItem.value!.id)); - await deezerAPI.addFavoriteTrack(audioHandler.mediaItem.value!.id); + setState(() => + cache.libraryTracks?.add(audioHandler.mediaItem.value!.id)); + await deezerAPI + .addFavoriteTrack(audioHandler.mediaItem.value!.id); await cache.save(); } }, @@ -679,7 +762,8 @@ class _BigAlbumArtState extends State with WidgetsBindingObserver { _imageList = _getImageList(audioHandler.queue.value); - _currentItemAndQueueSub = Rx.combineLatest2, void>( + _currentItemAndQueueSub = + Rx.combineLatest2, void>( audioHandler.mediaItem, audioHandler.queue, (mediaItem, queue) { @@ -698,7 +782,9 @@ class _BigAlbumArtState extends State with WidgetsBindingObserver { } List _getImageList(List queue) { - return queue.map((item) => ZoomableImage(url: item.artUri?.toString() ?? '')).toList(); + return queue + .map((item) => ZoomableImage(url: item.artUri?.toString() ?? '')) + .toList(); } bool _didQueueChange(List newQueue) { @@ -718,7 +804,8 @@ class _BigAlbumArtState extends State with WidgetsBindingObserver { void _handleMediaItemChange(MediaItem? item) async { final targetItemId = item?.id ?? ''; - final targetPage = audioHandler.queue.value.indexWhere((item) => item.id == targetItemId); + final targetPage = + audioHandler.queue.value.indexWhere((item) => item.id == targetItemId); if (targetPage == -1) return; // No need to animating to the same page @@ -799,7 +886,8 @@ class PlayerScreenTopRow extends StatelessWidget { final double? textWidth; final bool? short; final GlobalKey iconButtonKey = GlobalKey(); - PlayerScreenTopRow({super.key, this.textSize, this.iconSize, this.textWidth, this.short}); + PlayerScreenTopRow( + {super.key, this.textSize, this.iconSize, this.textWidth, this.short}); @override Widget build(BuildContext context) { @@ -825,7 +913,9 @@ class PlayerScreenTopRow extends StatelessWidget { child: Text( (short ?? false) ? (GetIt.I().queueSource?.text ?? '') - : 'Playing from:'.i18n + ' ' + (GetIt.I().queueSource?.text ?? ''), + : 'Playing from:'.i18n + + ' ' + + (GetIt.I().queueSource?.text ?? ''), maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.left, @@ -844,11 +934,14 @@ class PlayerScreenTopRow extends StatelessWidget { splashRadius: iconSize ?? ScreenUtil().setWidth(52), onPressed: () async { //Fix bottom buttons (Not needed anymore?) - SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(statusBarColor: Colors.transparent)); + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle(statusBarColor: Colors.transparent)); // Calculate the center of the icon - final RenderBox buttonRenderBox = iconButtonKey.currentContext!.findRenderObject() as RenderBox; - final Offset buttonOffset = buttonRenderBox.localToGlobal(buttonRenderBox.size.center(Offset.zero)); + final RenderBox buttonRenderBox = + iconButtonKey.currentContext!.findRenderObject() as RenderBox; + final Offset buttonOffset = buttonRenderBox + .localToGlobal(buttonRenderBox.size.center(Offset.zero)); //Navigate //await Navigator.of(context).push(MaterialPageRoute(builder: (context) => QueueScreen())); await Navigator.of(context).push(CircularExpansionRoute( @@ -879,7 +972,8 @@ class _SeekBarState extends State { double get position { if (_seeking) return _pos; - double p = audioHandler.playbackState.value.position.inMilliseconds.toDouble(); + double p = + audioHandler.playbackState.value.position.inMilliseconds.toDouble(); if (p > duration) return duration; return p; } @@ -904,17 +998,20 @@ class _SeekBarState extends State { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: const EdgeInsets.symmetric(vertical: 0.0, horizontal: 24.0), + padding: + const EdgeInsets.symmetric(vertical: 0.0, horizontal: 24.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _timeString(position), - style: TextStyle(fontSize: ScreenUtil().setSp(widget.relativeTextSize)), + style: TextStyle( + fontSize: ScreenUtil().setSp(widget.relativeTextSize)), ), Text( _timeString(duration), - style: TextStyle(fontSize: ScreenUtil().setSp(widget.relativeTextSize)), + style: TextStyle( + fontSize: ScreenUtil().setSp(widget.relativeTextSize)), ) ], ), @@ -924,7 +1021,8 @@ class _SeekBarState extends State { child: Slider( focusNode: FocusNode( canRequestFocus: false, - skipTraversal: true), // Don't focus on Slider - it doesn't work (and not needed) + skipTraversal: + true), // Don't focus on Slider - it doesn't work (and not needed) value: position, max: duration, onChangeStart: (double d) { @@ -1003,7 +1101,8 @@ class _QueueScreenState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { final queueState = audioHandler.queueState; - final shuffleModeEnabled = queueState.shuffleMode == AudioServiceShuffleMode.all; + final shuffleModeEnabled = + queueState.shuffleMode == AudioServiceShuffleMode.all; return Scaffold( appBar: FreezerAppBar( @@ -1014,9 +1113,10 @@ class _QueueScreenState extends State with WidgetsBindingObserver { child: IconButton( icon: Icon( //cons.shuffle, - ReFreezerIcons.shuffle, + DeezerIcons.shuffle, semanticLabel: 'Shuffle'.i18n, - color: shuffleModeEnabled ? Theme.of(context).primaryColor : null, + color: + shuffleModeEnabled ? Theme.of(context).primaryColor : null, ), onPressed: () async { await audioHandler.toggleShuffle(); @@ -1032,7 +1132,8 @@ class _QueueScreenState extends State with WidgetsBindingObserver { ), onPressed: () async { await audioHandler.clearQueue(); - mainNavigatorKey.currentState!.popUntil((route) => route.isFirst); + mainNavigatorKey.currentState! + .popUntil((route) => route.isFirst); }, ), ) diff --git a/lib/ui/search.dart b/lib/ui/search.dart index bf57db1..b47522f 100644 --- a/lib/ui/search.dart +++ b/lib/ui/search.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:fluttericon/font_awesome5_icons.dart'; import 'package:fluttericon/typicons_icons.dart'; import 'package:get_it/get_it.dart'; +import 'package:refreezer/settings.dart'; import '../api/cache.dart'; import '../api/deezer.dart'; @@ -218,6 +219,7 @@ class _SearchScreenState extends State { focusNode: _textFieldFocusNode, decoration: InputDecoration( labelText: 'Search or paste URL'.i18n, + hintStyle: TextStyle(color: settings.primaryColor), fillColor: Theme.of(context).bottomAppBarTheme.color, filled: true, diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart index b16297b..58a910e 100644 --- a/lib/ui/settings_screen.dart +++ b/lib/ui/settings_screen.dart @@ -56,33 +56,50 @@ class _SettingsScreenState extends State { children: [ ListTile( title: Text('General'.i18n), - leading: const LeadingIcon(Icons.settings, color: Color(0xffeca704)), - onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => const GeneralSettings())), + leading: + const LeadingIcon(Icons.settings, color: Color(0xffeca704)), + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const GeneralSettings())), ), ListTile( title: Text('Download Settings'.i18n), - leading: const LeadingIcon(Icons.cloud_download, color: Color(0xffbe3266)), - onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => const DownloadsSettings())), + leading: const LeadingIcon(Icons.cloud_download, + color: Color(0xffbe3266)), + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const DownloadsSettings())), ), ListTile( title: Text('Appearance'.i18n), - leading: const LeadingIcon(Icons.color_lens, color: Color(0xff4b2e7e)), - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const AppearanceSettings())), + leading: + const LeadingIcon(Icons.color_lens, color: Color(0xff4b2e7e)), + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AppearanceSettings())), ), ListTile( title: Text('Quality'.i18n), - leading: const LeadingIcon(Icons.high_quality, color: Color(0xff384697)), - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const QualitySettings())), + leading: + const LeadingIcon(Icons.high_quality, color: Color(0xff384697)), + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const QualitySettings())), ), ListTile( title: Text('Deezer'.i18n), - leading: const LeadingIcon(Icons.equalizer, color: Color(0xff0880b5)), - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const DeezerSettings())), + leading: + const LeadingIcon(Icons.equalizer, color: Color(0xff0880b5)), + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DeezerSettings())), ), //Language select ListTile( title: Text('Language'.i18n), - leading: const LeadingIcon(Icons.language, color: Color(0xff009a85)), + leading: + const LeadingIcon(Icons.language, color: Color(0xff009a85)), onTap: () { showDialog( context: context, @@ -94,8 +111,10 @@ class _SettingsScreenState extends State { title: Text(l.name), subtitle: Text('${l.locale}-${l.country}'), onTap: () async { - I18n.of(customNavigatorKey.currentContext!).locale = Locale(l.locale, l.country); - setState(() => settings.language = '${l.locale}_${l.country}'); + I18n.of(customNavigatorKey.currentContext!).locale = + Locale(l.locale, l.country); + setState(() => + settings.language = '${l.locale}_${l.country}'); await settings.save(); // Close the SimpleDialog if (context.mounted) Navigator.of(context).pop(); @@ -107,12 +126,14 @@ class _SettingsScreenState extends State { ListTile( title: Text('Updates'.i18n), leading: const LeadingIcon(Icons.update, color: Color(0xff2ba766)), - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const UpdaterScreen())), + onTap: () => Navigator.push(context, + MaterialPageRoute(builder: (context) => const UpdaterScreen())), ), ListTile( title: Text('About'.i18n), leading: const LeadingIcon(Icons.info, color: Colors.grey), - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const CreditsScreen())), + onTap: () => Navigator.push(context, + MaterialPageRoute(builder: (context) => const CreditsScreen())), ), ], ), @@ -138,7 +159,8 @@ class _AppearanceSettingsState extends State { children: [ ListTile( title: Text('Theme'.i18n), - subtitle: Text('Currently'.i18n + ': ${settings.theme.toString().split('.').last}'), + subtitle: Text('Currently'.i18n + + ': ${settings.theme.toString().split('.').last}'), leading: const Icon(Icons.color_lens), onTap: () { showDialog( @@ -206,7 +228,10 @@ class _AppearanceSettingsState extends State { leading: const Icon(Icons.font_download), subtitle: Text(settings.font), onTap: () { - showDialog(context: context, builder: (context) => FontSelector(() => Navigator.of(context).pop())); + showDialog( + context: context, + builder: (context) => + FontSelector(() => Navigator.of(context).pop())); }, ), ListTile( @@ -234,7 +259,9 @@ class _AppearanceSettingsState extends State { ), ListTile( title: Text('Visualizer'.i18n), - subtitle: Text('Show visualizers on lyrics page. WARNING: Requires microphone permission!'.i18n), + subtitle: Text( + 'Show visualizers on lyrics page. WARNING: Requires microphone permission!' + .i18n), leading: const Icon(Icons.equalizer), trailing: Switch( value: settings.lyricsVisualizer, @@ -260,20 +287,19 @@ class _AppearanceSettingsState extends State { context: context, builder: (context) { return AlertDialog( - title: Text('Primary color'.i18n), + title: Text('Primary colors'.i18n), content: SizedBox( height: 240, child: MaterialColorPicker( colors: [ ...Colors.primaries, //Logo colors - _swatch(0xffeca704), - _swatch(0xffbe3266), + _swatch(0xFFFF3386), _swatch(0xff4b2e7e), _swatch(0xff384697), _swatch(0xff0880b5), _swatch(0xff009a85), - _swatch(0xff2ba766) + _swatch(0xFFA238FF), ], allowShades: false, selectedColor: settings.primaryColor, @@ -291,15 +317,6 @@ class _AppearanceSettingsState extends State { }); }, ), - ListTile( - title: Text('Use album art primary color'.i18n), - subtitle: Text('Warning: might be buggy'.i18n), - leading: const Icon(Icons.invert_colors), - trailing: Switch( - value: settings.useArtColor, - onChanged: (v) => setState(() => settings.updateUseArtColor(v)), - ), - ), //Display mode ListTile( leading: const Icon(Icons.screen_lock_portrait), @@ -320,7 +337,8 @@ class _AppearanceSettingsState extends State { onPressed: () async { settings.displayMode = i; await settings.save(); - await FlutterDisplayMode.setPreferredMode(modes[i]); + await FlutterDisplayMode.setPreferredMode( + modes[i]); if (context.mounted) { Navigator.of(context).pop(); } @@ -347,7 +365,9 @@ class FontSelector extends StatefulWidget { class _FontSelectorState extends State { String query = ''; List get fonts { - return settings.fonts.where((f) => f.toLowerCase().contains(query)).toList(); + return settings.fonts + .where((f) => f.toLowerCase().contains(query)) + .toList(); } //Font selected @@ -421,25 +441,29 @@ class _QualitySettingsState extends State { children: [ ListTile( title: Text('Mobile streaming'.i18n), - leading: const LeadingIcon(Icons.network_cell, color: Color(0xff384697)), + leading: + const LeadingIcon(Icons.network_cell, color: Color(0xff384697)), ), const QualityPicker('mobile'), const FreezerDivider(), ListTile( title: Text('Wifi streaming'.i18n), - leading: const LeadingIcon(Icons.network_wifi, color: Color(0xff0880b5)), + leading: + const LeadingIcon(Icons.network_wifi, color: Color(0xff0880b5)), ), const QualityPicker('wifi'), const FreezerDivider(), ListTile( title: Text('Offline'.i18n), - leading: const LeadingIcon(Icons.offline_pin, color: Color(0xff009a85)), + leading: + const LeadingIcon(Icons.offline_pin, color: Color(0xff009a85)), ), const QualityPicker('offline'), const FreezerDivider(), ListTile( title: Text('External downloads'.i18n), - leading: const LeadingIcon(Icons.file_download, color: Color(0xff2ba766)), + leading: const LeadingIcon(Icons.file_download, + color: Color(0xff2ba766)), ), const QualityPicker('download'), ], @@ -608,7 +632,8 @@ class _DeezerSettingsState extends State { children: [ ListTile( title: Text('Content language'.i18n), - subtitle: Text('Not app language, used in headers. Now'.i18n + ': ${settings.deezerLanguage}'), + subtitle: Text('Not app language, used in headers. Now'.i18n + + ': ${settings.deezerLanguage}'), leading: const Icon(Icons.language), onTap: () { showDialog( @@ -621,7 +646,8 @@ class _DeezerSettingsState extends State { title: Text(ContentLanguage.all[i].name), subtitle: Text(ContentLanguage.all[i].code), onTap: () async { - setState(() => settings.deezerLanguage = ContentLanguage.all[i].code); + setState(() => settings.deezerLanguage = + ContentLanguage.all[i].code); await settings.save(); if (context.mounted) { Navigator.of(context).pop(); @@ -633,7 +659,8 @@ class _DeezerSettingsState extends State { ), ListTile( title: Text('Content country'.i18n), - subtitle: Text('Country used in headers. Now'.i18n + ': ${settings.deezerCountry}'), + subtitle: Text('Country used in headers. Now'.i18n + + ': ${settings.deezerCountry}'), leading: const Icon(Icons.vpn_lock), onTap: () { showDialog( @@ -655,7 +682,8 @@ class _DeezerSettingsState extends State { ], ), onValuePicked: (Country country) { - setState(() => settings.deezerCountry = country.isoCode ?? 'us'); + setState(() => + settings.deezerCountry = country.isoCode ?? 'us'); settings.save(); }, )); @@ -663,7 +691,9 @@ class _DeezerSettingsState extends State { ), ListTile( title: Text('Log tracks'.i18n), - subtitle: Text('Send track listen logs to Deezer, enable it for features like Flow to work properly'.i18n), + subtitle: Text( + 'Send track listen logs to Deezer, enable it for features like Flow to work properly' + .i18n), trailing: Switch( value: settings.logListen, onChanged: (bool v) { @@ -764,7 +794,8 @@ class _FilenameTemplateDialogState extends State { Text( 'Valid variables are'.i18n + ': %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%, %playlistTrackNumber%, %0playlistTrackNumber%, %year%, %date%\n\n' + - "If you want to use custom directory naming - use '/' as directory separator.".i18n, + "If you want to use custom directory naming - use '/' as directory separator." + .i18n, style: const TextStyle( fontSize: 12.0, ), @@ -779,7 +810,8 @@ class _FilenameTemplateDialogState extends State { TextButton( child: Text('Reset'.i18n), onPressed: () { - _controller.value = _controller.value.copyWith(text: '%artist% - %title%'); + _controller.value = + _controller.value.copyWith(text: '%artist% - %title%'); _new = '%artist% - %title%'; }, ), @@ -808,7 +840,8 @@ class DownloadsSettings extends StatefulWidget { class _DownloadsSettingsState extends State { double _downloadThreads = settings.downloadThreads.toDouble(); - final TextEditingController _artistSeparatorController = TextEditingController(text: settings.artistSeparator); + final TextEditingController _artistSeparatorController = + TextEditingController(text: settings.artistSeparator); @override Widget build(BuildContext context) { @@ -852,7 +885,8 @@ class _DownloadsSettingsState extends State { showDialog( context: context, builder: (context) { - return FilenameTemplateDialog(settings.downloadFilename, (f) async { + return FilenameTemplateDialog(settings.downloadFilename, + (f) async { setState(() => settings.downloadFilename = f); await settings.save(); }); @@ -861,13 +895,15 @@ class _DownloadsSettingsState extends State { ), ListTile( title: Text('Singleton naming'.i18n), - subtitle: Text('Currently'.i18n + ': ${settings.singletonFilename}'), + subtitle: + Text('Currently'.i18n + ': ${settings.singletonFilename}'), leading: const Icon(Icons.text_format), onTap: () { showDialog( context: context, builder: (context) { - return FilenameTemplateDialog(settings.singletonFilename, (f) async { + return FilenameTemplateDialog(settings.singletonFilename, + (f) async { setState(() => settings.singletonFilename = f); await settings.save(); }); @@ -877,7 +913,8 @@ class _DownloadsSettingsState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text( - 'Download threads'.i18n + ': ${_downloadThreads.round().toString()}', + 'Download threads'.i18n + + ': ${_downloadThreads.round().toString()}', style: const TextStyle(fontSize: 16.0), ), ), @@ -897,14 +934,17 @@ class _DownloadsSettingsState extends State { await settings.save(); //Prevent null - if (val > 8 && cache.threadsWarning != true && context.mounted) { + if (val > 8 && + cache.threadsWarning != true && + context.mounted) { showDialog( context: context, builder: (context) { return AlertDialog( title: Text('Warning'.i18n), content: Text( - 'Using too many concurrent downloads on older/weaker devices might cause crashes!'.i18n), + 'Using too many concurrent downloads on older/weaker devices might cause crashes!' + .i18n), actions: [ TextButton( child: Text('Dismiss'.i18n), @@ -922,8 +962,8 @@ class _DownloadsSettingsState extends State { ListTile( title: Text('Tags'.i18n), leading: const Icon(Icons.label), - onTap: () => - Navigator.of(context).push(MaterialPageRoute(builder: (context) => const TagSelectionScreen())), + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const TagSelectionScreen())), ), ListTile( title: Text('Create folders for artist'.i18n), @@ -1010,17 +1050,20 @@ class _DownloadsSettingsState extends State { leading: const Icon(Icons.image)), ListTile( title: Text('Album cover resolution'.i18n), - subtitle: Text("WARNING: Resolutions above 1200 aren't officially supported".i18n), + subtitle: Text( + "WARNING: Resolutions above 1200 aren't officially supported" + .i18n), leading: const Icon(Icons.image), trailing: SizedBox( width: 75.0, child: DropdownButton( value: settings.albumArtResolution, items: [400, 800, 1000, 1200, 1400, 1600, 1800] - .map>((int i) => DropdownMenuItem( - value: i, - child: Text(i.toString()), - )) + .map>( + (int i) => DropdownMenuItem( + value: i, + child: Text(i.toString()), + )) .toList(), onChanged: (int? n) async { setState(() { @@ -1031,7 +1074,8 @@ class _DownloadsSettingsState extends State { ))), ListTile( title: Text('Create .nomedia files'.i18n), - subtitle: Text('To prevent gallery being filled with album art'.i18n), + subtitle: + Text('To prevent gallery being filled with album art'.i18n), trailing: Switch( value: settings.nomediaFiles, onChanged: (v) { @@ -1058,7 +1102,8 @@ class _DownloadsSettingsState extends State { ListTile( title: Text('Download Log'.i18n), leading: const Icon(Icons.sticky_note_2), - onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => const DownloadLogViewer())), + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const DownloadLogViewer())), ) ], ), @@ -1159,7 +1204,9 @@ class _GeneralSettingsState extends State { setState(() => settings.offlineMode = false); } else { Fluttertoast.showToast( - msg: 'Error logging in, check your internet connections.'.i18n, + msg: + 'Error logging in, check your internet connections.' + .i18n, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT); } @@ -1179,7 +1226,8 @@ class _GeneralSettingsState extends State { ), ListTile( title: Text('Copy ARL'.i18n), - subtitle: Text('Copy userToken/ARL Cookie for use in other apps.'.i18n), + subtitle: + Text('Copy userToken/ARL Cookie for use in other apps.'.i18n), leading: const Icon(Icons.lock), onTap: () async { await FlutterClipboard.copy(settings.arl ?? ''); @@ -1190,7 +1238,9 @@ class _GeneralSettingsState extends State { ), ListTile( title: Text('Enable equalizer'.i18n), - subtitle: Text('Might enable some equalizer apps to work. Requires restart of ReFreezer'.i18n), + subtitle: Text( + 'Might enable some equalizer apps to work. Requires restart of ReFreezer' + .i18n), leading: const Icon(Icons.equalizer), trailing: Switch( value: settings.enableEqualizer, @@ -1202,7 +1252,9 @@ class _GeneralSettingsState extends State { ), ListTile( title: Text('LastFM'.i18n), - subtitle: Text((settings.lastFMUsername != null) ? 'Log out'.i18n : 'Login to enable scrobbling.'.i18n), + subtitle: Text((settings.lastFMUsername != null) + ? 'Log out'.i18n + : 'Login to enable scrobbling.'.i18n), leading: const Icon(FontAwesome5.lastfm), onTap: () async { if (settings.lastFMUsername != null) { @@ -1241,8 +1293,8 @@ class _GeneralSettingsState extends State { ListTile( title: Text('Application Log'.i18n), leading: const Icon(Icons.sticky_note_2), - onTap: () => - Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ApplicationLogViewer())), + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ApplicationLogViewer())), ), const FreezerDivider(), ListTile( @@ -1341,7 +1393,10 @@ class _LastFMLoginState extends State { LastFM last; try { last = await LastFM.authenticate( - apiKey: Env.lastFmApiKey, apiSecret: Env.lastFmApiSecret, username: _username, password: _password); + apiKey: Env.lastFmApiKey, + apiSecret: Env.lastFmApiSecret, + username: _username, + password: _password); } catch (e) { Logger.root.severe('Error authorizing LastFM: $e'); Fluttertoast.showToast(msg: 'Authorization error!'.i18n); @@ -1365,23 +1420,30 @@ class StorageInfo { final String appFilesDir; final int availableBytes; - StorageInfo({required this.rootDir, required this.appFilesDir, required this.availableBytes}); + StorageInfo( + {required this.rootDir, + required this.appFilesDir, + required this.availableBytes}); } Future> getStorageInfo() async { - final externalDirectories = await ExternalPath.getExternalStorageDirectories(); + final externalDirectories = + await ExternalPath.getExternalStorageDirectories(); List storageInfoList = []; if (externalDirectories.isNotEmpty) { for (var dir in externalDirectories) { - var availableMegaBytes = (await DiskSpacePlus.getFreeDiskSpaceForPath(dir)) ?? 0.0; + var availableMegaBytes = + (await DiskSpacePlus.getFreeDiskSpaceForPath(dir)) ?? 0.0; storageInfoList.add( StorageInfo( rootDir: dir, appFilesDir: dir, - availableBytes: availableMegaBytes > 0 ? (availableMegaBytes * 1000000).floor() : 0, + availableBytes: availableMegaBytes > 0 + ? (availableMegaBytes * 1000000).floor() + : 0, ), ); } @@ -1453,14 +1515,17 @@ class _DirectoryPickerState extends State { padding: EdgeInsets.symmetric(vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, - children: [CircularProgressIndicator()], + children: [ + CircularProgressIndicator() + ], ), ); } return Column( mainAxisSize: MainAxisSize.min, children: [ - ...List.generate(snapshot.data?.length ?? 0, (i) { + ...List.generate(snapshot.data?.length ?? 0, + (i) { StorageInfo si = snapshot.data![i]; return ListTile( title: Text(si.rootDir), @@ -1524,7 +1589,9 @@ class _DirectoryPickerState extends State { onTap: () { setState(() { if (_root == _path) { - Fluttertoast.showToast(msg: 'Permission denied'.i18n, gravity: ToastGravity.BOTTOM); + Fluttertoast.showToast( + msg: 'Permission denied'.i18n, + gravity: ToastGravity.BOTTOM); return; } _previous = _path; @@ -1618,8 +1685,10 @@ class _CreditsScreenState extends State { ), const FreezerDivider(), const ListTile( - title: Text('DJDoubleD', style: TextStyle(fontWeight: FontWeight.bold)), - subtitle: Text('Developer, tester, new icon & logo, some translations, ...'), + title: Text('DJDoubleD', + style: TextStyle(fontWeight: FontWeight.bold)), + subtitle: Text( + 'Developer, tester, new icon & logo, some translations, ...'), ), const FreezerDivider(), /*ListTile( @@ -1657,7 +1726,8 @@ class _CreditsScreenState extends State { ListTile( title: Text('Crowdin'.i18n), subtitle: Text('Help translating this app on Crowdin!'.i18n), - leading: const Icon(ReFreezerIcons.crowdin, color: Color(0xffbdc1c6), size: 36.0), + leading: const Icon(ReFreezerIcons.crowdin, + color: Color(0xffbdc1c6), size: 36.0), onTap: () { launchUrlString('https://crowdin.com/project/refreezer'); }, @@ -1665,15 +1735,20 @@ class _CreditsScreenState extends State { ListTile( isThreeLine: true, title: Text('Donate'.i18n), - subtitle: Text('You should rather support your favorite artists, instead of this app!'.i18n), - leading: const Icon(FontAwesome5.paypal, color: Colors.blue, size: 36.0), + subtitle: Text( + 'You should rather support your favorite artists, instead of this app!' + .i18n), + leading: + const Icon(FontAwesome5.paypal, color: Colors.blue, size: 36.0), onTap: () { showDialog( context: context, builder: (context) { return AlertDialog( title: Text('Donate'.i18n), - content: Text('No really, go support your favorite artists instead ;)'.i18n), + content: Text( + 'No really, go support your favorite artists instead ;)' + .i18n), actions: [ TextButton( child: const Text('OK'), @@ -1704,10 +1779,12 @@ class _CreditsScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Image.asset('assets/icon_legacy.png', width: 24, height: 24), + Image.asset('assets/icon_legacy.png', + width: 24, height: 24), Expanded( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: + const EdgeInsets.symmetric(horizontal: 8.0), child: Text( 'The original freezer development team'.i18n, textAlign: TextAlign.center, @@ -1720,7 +1797,8 @@ class _CreditsScreenState extends State { ), ), ), - Image.asset('assets/icon_legacy.png', width: 24, height: 24), + Image.asset('assets/icon_legacy.png', + width: 24, height: 24), ], ), ], @@ -1739,7 +1817,8 @@ class _CreditsScreenState extends State { ), const ListTile( title: Text('Bas Curtiz'), - subtitle: Text('Icon, logo, banner, design suggestions, tester'), + subtitle: + Text('Icon, logo, banner, design suggestions, tester'), ), const ListTile( title: Text('Tobs'), diff --git a/marquee b/marquee deleted file mode 160000 index 4241bdf..0000000 --- a/marquee +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4241bdf46dd1346b939b0f4f02bde26b258f9cea diff --git a/move_to_background b/move_to_background deleted file mode 160000 index 600f91b..0000000 --- a/move_to_background +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 600f91b77430341fdd2ce100e42e8b6acf8b677b diff --git a/pubspec.yaml b/pubspec.yaml index 2cb4ce9..624d014 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -174,6 +174,9 @@ flutter: - family: ReFreezerIcons fonts: - asset: assets/fonts/ReFreezerIcons.ttf + - family: Deezer + fonts: + - asset: assets/fonts/Deezer.ttf # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/scrobblenaut b/scrobblenaut deleted file mode 160000 index 6966422..0000000 --- a/scrobblenaut +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6966422e6ae49494b2cb332177f793abc265450a