diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 2248d357..6d27ad30 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -14,7 +14,8 @@ "compilerPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30133\\bin\\Hostx64\\x64\\cl.exe", "cStandard": "c17", "cppStandard": "c++17", - "intelliSenseMode": "windows-msvc-x64" + "intelliSenseMode": "windows-msvc-x64", + "configurationProvider": "ms-vscode.makefile-tools" } ], "version": 4 diff --git a/lib/main.dart b/lib/main.dart index 61e868ad..defec1cb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,8 @@ import 'package:spotube/provider/AudioPlayer.dart'; import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/YouTube.dart'; import 'package:just_audio_background/just_audio_background.dart'; +import 'package:spotube/themes/dark-theme.dart'; +import 'package:spotube/themes/light-theme.dart'; void main() async { if (Platform.isAndroid || Platform.isIOS) { @@ -59,98 +61,13 @@ class MyApp extends HookConsumerWidget { routerDelegate: _router.routerDelegate, debugShowCheckedModeBanner: false, title: 'Spotube', - theme: ThemeData( - primaryColor: Colors.green, - primarySwatch: Colors.green, - buttonTheme: const ButtonThemeData( - buttonColor: Colors.green, - ), - shadowColor: Colors.grey[300], - backgroundColor: Colors.white, - textTheme: TextTheme( - bodyText1: TextStyle(color: Colors.grey[850]), - headline1: TextStyle(color: Colors.grey[850]), - headline2: TextStyle(color: Colors.grey[850]), - headline3: TextStyle(color: Colors.grey[850]), - headline4: TextStyle(color: Colors.grey[850]), - headline5: TextStyle(color: Colors.grey[850]), - headline6: TextStyle(color: Colors.grey[850]), - ), - listTileTheme: ListTileThemeData( - iconColor: Colors.grey[850], - horizontalTitleGap: 0, - ), - inputDecorationTheme: InputDecorationTheme( - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.green[400]!, - width: 2.0, - ), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.grey[800]!, - ), - ), - ), - navigationRailTheme: NavigationRailThemeData( - backgroundColor: Colors.blueGrey[50], - unselectedIconTheme: - IconThemeData(color: Colors.grey[850], opacity: 1), - unselectedLabelTextStyle: TextStyle( - color: Colors.grey[850], - ), - ), - navigationBarTheme: NavigationBarThemeData( - backgroundColor: Colors.blueGrey[50], - height: 55, - ), - cardTheme: CardTheme( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - color: Colors.white, - ), + theme: lightTheme( + accentMaterialColor: Colors.deepPurple, + backgroundMaterialColor: Colors.grey, ), - darkTheme: ThemeData( - brightness: Brightness.dark, - primaryColor: Colors.green, - primarySwatch: Colors.green, - backgroundColor: Colors.blueGrey[900], - scaffoldBackgroundColor: Colors.blueGrey[900], - dialogBackgroundColor: Colors.blueGrey[800], - shadowColor: Colors.black26, - popupMenuTheme: PopupMenuThemeData(color: Colors.blueGrey[800]), - buttonTheme: const ButtonThemeData( - buttonColor: Colors.green, - ), - inputDecorationTheme: InputDecorationTheme( - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.green[400]!, - width: 2.0, - ), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.grey[800]!, - ), - ), - ), - navigationRailTheme: NavigationRailThemeData( - backgroundColor: Colors.blueGrey[800], - unselectedIconTheme: const IconThemeData(opacity: 1), - ), - navigationBarTheme: NavigationBarThemeData( - backgroundColor: Colors.blueGrey[800], - height: 55, - ), - cardTheme: CardTheme( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - color: Colors.blueGrey[900], - elevation: 20, - ), - canvasColor: Colors.blueGrey[900], + darkTheme: darkTheme( + accentMaterialColor: Colors.purple, + backgroundMaterialColor: Colors.grey, ), themeMode: themeMode, ); diff --git a/lib/provider/Auth.dart b/lib/provider/Auth.dart index cceee530..b34df796 100644 --- a/lib/provider/Auth.dart +++ b/lib/provider/Auth.dart @@ -68,8 +68,8 @@ class Auth extends PersistedChangeNotifier { @override FutureOr loadFromLocal(Map map) { - _clientId = map["clientId"].isNotEmpty ? map["clientId"] : null; - _clientSecret = map["clientSecret"].isNotEmpty ? map["clientSecret"] : null; + _clientId = map["clientId"]; + _clientSecret = map["clientSecret"]; _accessToken = map["accessToken"]; _refreshToken = map["refreshToken"]; _expiration = DateTime.tryParse(map["expiration"]); diff --git a/lib/provider/UserPreferences.dart b/lib/provider/UserPreferences.dart index 3836115c..04ae66af 100644 --- a/lib/provider/UserPreferences.dart +++ b/lib/provider/UserPreferences.dart @@ -1,24 +1,25 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotube/helpers/get-random-element.dart'; -import 'package:spotube/models/LocalStorageKeys.dart'; -import 'package:spotube/models/Logger.dart'; import 'package:spotube/models/generated_secrets.dart'; +import 'package:spotube/utils/PersistedChangeNotifier.dart'; -class UserPreferences extends ChangeNotifier { +class UserPreferences extends PersistedChangeNotifier { ThemeMode themeMode; String ytSearchFormat; String recommendationMarket; bool saveTrackLyrics; String geniusAccessToken; - SharedPreferences? localStorage; HotKey? nextTrackHotKey; HotKey? prevTrackHotKey; HotKey? playPauseHotKey; + + MaterialColor? accentColorScheme; + MaterialColor? backgroundColorScheme; UserPreferences({ required this.geniusAccessToken, required this.recommendationMarket, @@ -28,135 +29,101 @@ class UserPreferences extends ChangeNotifier { this.nextTrackHotKey, this.prevTrackHotKey, this.playPauseHotKey, - }) { - onInit(); - } - - final logger = getLogger(UserPreferences); - - Future _getHotKeyFromLocalStorage(String key) async { - String? str = localStorage?.getString(key); - if (str != null) { - Map json = await jsonDecode(str); - if (json.isEmpty) { - return null; - } - return HotKey.fromJson(json); - } - return null; - } - - Future onInit() async { - try { - localStorage = await SharedPreferences.getInstance(); - String? accessToken = - localStorage?.getString(LocalStorageKeys.geniusAccessToken); - - saveTrackLyrics = - localStorage?.getBool(LocalStorageKeys.saveTrackLyrics) ?? false; - - final themeModeRaw = localStorage?.getString(LocalStorageKeys.themeMode); - switch (themeModeRaw) { - case "light": - themeMode = ThemeMode.light; - break; - case "dark": - themeMode = ThemeMode.dark; - break; - default: - themeMode = ThemeMode.system; - } - - recommendationMarket = - localStorage?.getString(LocalStorageKeys.recommendationMarket) ?? - 'US'; - geniusAccessToken = accessToken != null && accessToken.isNotEmpty - ? accessToken - : getRandomElement(lyricsSecrets); - - nextTrackHotKey ??= (await _getHotKeyFromLocalStorage( - LocalStorageKeys.nextTrackHotKey, - )) ?? - HotKey( - KeyCode.slash, - modifiers: [KeyModifier.control, KeyModifier.alt], - ); - - prevTrackHotKey ??= (await _getHotKeyFromLocalStorage( - LocalStorageKeys.prevTrackHotKey, - )) ?? - HotKey( - KeyCode.comma, - modifiers: [KeyModifier.control, KeyModifier.alt], - ); - - playPauseHotKey ??= (await _getHotKeyFromLocalStorage( - LocalStorageKeys.playPauseHotKey, - )) ?? - HotKey( - KeyCode.period, - modifiers: [KeyModifier.control, KeyModifier.alt], - ); - notifyListeners(); - } catch (e, stack) { - logger.e("onInit", e, stack); - } - } + this.accentColorScheme, + this.backgroundColorScheme, + }) : super(); void setThemeMode(ThemeMode mode) { themeMode = mode; - localStorage?.setString(LocalStorageKeys.themeMode, mode.name); notifyListeners(); + updatePersistence(); } void setSaveTrackLyrics(bool shouldSave) { saveTrackLyrics = shouldSave; - localStorage?.setBool(LocalStorageKeys.saveTrackLyrics, shouldSave); notifyListeners(); + updatePersistence(); } void setRecommendationMarket(String country) { recommendationMarket = country; - localStorage?.setString(LocalStorageKeys.recommendationMarket, country); notifyListeners(); + updatePersistence(); } void setGeniusAccessToken(String token) { geniusAccessToken = token; notifyListeners(); + updatePersistence(); } void setNextTrackHotKey(HotKey? value) { nextTrackHotKey = value; - localStorage?.setString( - LocalStorageKeys.nextTrackHotKey, - jsonEncode(value?.toJson() ?? {}), - ); notifyListeners(); + updatePersistence(); } void setPrevTrackHotKey(HotKey? value) { prevTrackHotKey = value; - localStorage?.setString( - LocalStorageKeys.prevTrackHotKey, - jsonEncode(value?.toJson() ?? {}), - ); notifyListeners(); + updatePersistence(); } void setPlayPauseHotKey(HotKey? value) { playPauseHotKey = value; - localStorage?.setString( - LocalStorageKeys.playPauseHotKey, - jsonEncode(value?.toJson() ?? {}), - ); notifyListeners(); + updatePersistence(); } void setYtSearchFormat(String format) { ytSearchFormat = format; - localStorage?.setString(LocalStorageKeys.ytSearchFormate, format); notifyListeners(); + updatePersistence(); + } + + void setAccentColorScheme(MaterialColor color) { + accentColorScheme = color; + notifyListeners(); + updatePersistence(); + } + + void setBackgroundColorScheme(MaterialColor color) { + backgroundColorScheme = color; + notifyListeners(); + updatePersistence(); + } + + @override + FutureOr loadFromLocal(Map map) { + saveTrackLyrics = map["saveTrackLyrics"] ?? false; + recommendationMarket = map["recommendationMarket"] ?? recommendationMarket; + geniusAccessToken = + map["geniusAccessToken"] ?? getRandomElement(lyricsSecrets); + nextTrackHotKey = map["nextTrackHotKey"] != null + ? HotKey.fromJson(jsonDecode(map["nextTrackHotKey"])) + : null; + prevTrackHotKey = map["prevTrackHotKey"] != null + ? HotKey.fromJson(jsonDecode(map["prevTrackHotKey"])) + : null; + playPauseHotKey = map["playPauseHotKey"] != null + ? HotKey.fromJson(jsonDecode(map["playPauseHotKey"])) + : null; + ytSearchFormat = map["ytSearchFormat"] ?? ytSearchFormat; + themeMode = ThemeMode.values[map["themeMode"] ?? 0]; + } + + @override + FutureOr> toMap() { + return { + "saveTrackLyrics": saveTrackLyrics, + "recommendationMarket": recommendationMarket, + "geniusAccessToken": geniusAccessToken, + "nextTrackHotKey": jsonEncode(nextTrackHotKey?.toJson() ?? {}), + "prevTrackHotKey": jsonEncode(prevTrackHotKey?.toJson() ?? {}), + "playPauseHotKey": jsonEncode(playPauseHotKey?.toJson() ?? {}), + "ytSearchFormat": ytSearchFormat, + "themeMode": themeMode.index, + }; } } diff --git a/lib/themes/dark-theme.dart b/lib/themes/dark-theme.dart new file mode 100644 index 00000000..12180168 --- /dev/null +++ b/lib/themes/dark-theme.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +ThemeData darkTheme({ + MaterialColor accentMaterialColor = Colors.green, + MaterialColor backgroundMaterialColor = Colors.blueGrey, +}) { + return ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + primaryColor: accentMaterialColor, + primarySwatch: accentMaterialColor, + backgroundColor: backgroundMaterialColor[900], + scaffoldBackgroundColor: backgroundMaterialColor[900], + dialogBackgroundColor: backgroundMaterialColor[800], + shadowColor: Colors.black26, + popupMenuTheme: PopupMenuThemeData(color: backgroundMaterialColor[800]), + buttonTheme: ButtonThemeData( + buttonColor: accentMaterialColor, + ), + inputDecorationTheme: InputDecorationTheme( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: accentMaterialColor[400]!, + width: 2.0, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: backgroundMaterialColor[800]!, + ), + ), + ), + navigationRailTheme: NavigationRailThemeData( + backgroundColor: backgroundMaterialColor[800], + unselectedIconTheme: IconThemeData(color: Colors.grey[300], opacity: 1), + selectedIconTheme: IconThemeData(color: backgroundMaterialColor[850]), + selectedLabelTextStyle: TextStyle(color: accentMaterialColor[300]), + unselectedLabelTextStyle: TextStyle(color: Colors.grey[300]), + indicatorColor: accentMaterialColor[300], + ), + navigationBarTheme: NavigationBarThemeData( + backgroundColor: backgroundMaterialColor[800], + height: 55, + indicatorColor: accentMaterialColor[300], + ), + cardTheme: CardTheme( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + color: backgroundMaterialColor[900], + elevation: 20, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + onPrimary: accentMaterialColor[300], + textStyle: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + cardColor: backgroundMaterialColor[800], + canvasColor: backgroundMaterialColor[900], + ); +} diff --git a/lib/themes/light-theme.dart b/lib/themes/light-theme.dart new file mode 100644 index 00000000..1e00f01f --- /dev/null +++ b/lib/themes/light-theme.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; + +final materialWhite = MaterialColor(Colors.white.value, { + 50: Colors.white, + 100: Colors.blueGrey[50]!, + 200: Colors.white, + 300: Colors.white, + 400: Colors.white, + 500: Colors.blueGrey, + 600: Colors.white, + 700: Colors.white, + 800: Colors.white, + 900: Colors.white, +}); + +ThemeData lightTheme({ + MaterialColor accentMaterialColor = Colors.green, + MaterialColor? backgroundMaterialColor, +}) { + backgroundMaterialColor ??= materialWhite; + return ThemeData( + useMaterial3: true, + primaryColor: accentMaterialColor, + primarySwatch: accentMaterialColor, + buttonTheme: ButtonThemeData( + buttonColor: accentMaterialColor, + ), + shadowColor: Colors.grey[300], + backgroundColor: backgroundMaterialColor[50], + textTheme: TextTheme( + bodyText1: TextStyle(color: Colors.grey[850]), + headline1: TextStyle(color: Colors.grey[850]), + headline2: TextStyle(color: Colors.grey[850]), + headline3: TextStyle(color: Colors.grey[850]), + headline4: TextStyle(color: Colors.grey[850]), + headline5: TextStyle(color: Colors.grey[850]), + headline6: TextStyle(color: Colors.grey[850]), + ), + listTileTheme: ListTileThemeData( + iconColor: Colors.grey[850], + horizontalTitleGap: 0, + ), + inputDecorationTheme: InputDecorationTheme( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: accentMaterialColor[400]!, + width: 2.0, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey[800]!, + ), + ), + ), + navigationRailTheme: NavigationRailThemeData( + backgroundColor: backgroundMaterialColor[100], + indicatorColor: accentMaterialColor[300], + selectedIconTheme: IconThemeData(color: accentMaterialColor[850]), + unselectedIconTheme: IconThemeData(color: Colors.grey[850], opacity: 1), + unselectedLabelTextStyle: TextStyle( + color: Colors.grey[850], + ), + ), + navigationBarTheme: NavigationBarThemeData( + backgroundColor: backgroundMaterialColor[100], + height: 55, + indicatorColor: accentMaterialColor[300], + iconTheme: MaterialStateProperty.all( + IconThemeData(color: Colors.grey[850]), + ), + ), + cardTheme: CardTheme( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + color: backgroundMaterialColor[50], + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + onPrimary: accentMaterialColor[800], + textStyle: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + cardColor: backgroundMaterialColor[50], + canvasColor: backgroundMaterialColor[50], + ); +} diff --git a/lib/utils/PersistedChangeNotifier.dart b/lib/utils/PersistedChangeNotifier.dart index e888bbdd..556984c7 100644 --- a/lib/utils/PersistedChangeNotifier.dart +++ b/lib/utils/PersistedChangeNotifier.dart @@ -14,13 +14,13 @@ abstract class PersistedChangeNotifier extends ChangeNotifier { .fold>({}, (acc, entry) { if (entry.value != null) { if (entry.value is bool) { - acc[entry.key] = _localStorage.getBool(entry.key) ?? false; + acc[entry.key] = _localStorage.getBool(entry.key); } else if (entry.value is int) { - acc[entry.key] = _localStorage.getInt(entry.key) ?? -1; + acc[entry.key] = _localStorage.getInt(entry.key); } else if (entry.value is double) { - acc[entry.key] = _localStorage.getDouble(entry.key) ?? -1.0; + acc[entry.key] = _localStorage.getDouble(entry.key); } else if (entry.value is String) { - acc[entry.key] = _localStorage.getString(entry.key) ?? ""; + acc[entry.key] = _localStorage.getString(entry.key); } } else { acc[entry.key] = _localStorage.get(entry.key);