Initial accent theme & background theme support

This commit is contained in:
Kingkor Roy Tirtho 2022-05-26 19:39:55 +06:00
parent 211affa79d
commit 94db62baa5
7 changed files with 228 additions and 193 deletions

View File

@ -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", "compilerPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30133\\bin\\Hostx64\\x64\\cl.exe",
"cStandard": "c17", "cStandard": "c17",
"cppStandard": "c++17", "cppStandard": "c++17",
"intelliSenseMode": "windows-msvc-x64" "intelliSenseMode": "windows-msvc-x64",
"configurationProvider": "ms-vscode.makefile-tools"
} }
], ],
"version": 4 "version": 4

View File

@ -12,6 +12,8 @@ import 'package:spotube/provider/AudioPlayer.dart';
import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/UserPreferences.dart';
import 'package:spotube/provider/YouTube.dart'; import 'package:spotube/provider/YouTube.dart';
import 'package:just_audio_background/just_audio_background.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 { void main() async {
if (Platform.isAndroid || Platform.isIOS) { if (Platform.isAndroid || Platform.isIOS) {
@ -59,98 +61,13 @@ class MyApp extends HookConsumerWidget {
routerDelegate: _router.routerDelegate, routerDelegate: _router.routerDelegate,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'Spotube', title: 'Spotube',
theme: ThemeData( theme: lightTheme(
primaryColor: Colors.green, accentMaterialColor: Colors.deepPurple,
primarySwatch: Colors.green, backgroundMaterialColor: Colors.grey,
buttonTheme: const ButtonThemeData(
buttonColor: Colors.green,
), ),
shadowColor: Colors.grey[300], darkTheme: darkTheme(
backgroundColor: Colors.white, accentMaterialColor: Colors.purple,
textTheme: TextTheme( backgroundMaterialColor: Colors.grey,
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,
),
),
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],
), ),
themeMode: themeMode, themeMode: themeMode,
); );

View File

@ -68,8 +68,8 @@ class Auth extends PersistedChangeNotifier {
@override @override
FutureOr<void> loadFromLocal(Map<String, dynamic> map) { FutureOr<void> loadFromLocal(Map<String, dynamic> map) {
_clientId = map["clientId"].isNotEmpty ? map["clientId"] : null; _clientId = map["clientId"];
_clientSecret = map["clientSecret"].isNotEmpty ? map["clientSecret"] : null; _clientSecret = map["clientSecret"];
_accessToken = map["accessToken"]; _accessToken = map["accessToken"];
_refreshToken = map["refreshToken"]; _refreshToken = map["refreshToken"];
_expiration = DateTime.tryParse(map["expiration"]); _expiration = DateTime.tryParse(map["expiration"]);

View File

@ -1,24 +1,25 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hotkey_manager/hotkey_manager.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/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/models/generated_secrets.dart';
import 'package:spotube/utils/PersistedChangeNotifier.dart';
class UserPreferences extends ChangeNotifier { class UserPreferences extends PersistedChangeNotifier {
ThemeMode themeMode; ThemeMode themeMode;
String ytSearchFormat; String ytSearchFormat;
String recommendationMarket; String recommendationMarket;
bool saveTrackLyrics; bool saveTrackLyrics;
String geniusAccessToken; String geniusAccessToken;
SharedPreferences? localStorage;
HotKey? nextTrackHotKey; HotKey? nextTrackHotKey;
HotKey? prevTrackHotKey; HotKey? prevTrackHotKey;
HotKey? playPauseHotKey; HotKey? playPauseHotKey;
MaterialColor? accentColorScheme;
MaterialColor? backgroundColorScheme;
UserPreferences({ UserPreferences({
required this.geniusAccessToken, required this.geniusAccessToken,
required this.recommendationMarket, required this.recommendationMarket,
@ -28,135 +29,101 @@ class UserPreferences extends ChangeNotifier {
this.nextTrackHotKey, this.nextTrackHotKey,
this.prevTrackHotKey, this.prevTrackHotKey,
this.playPauseHotKey, this.playPauseHotKey,
}) { this.accentColorScheme,
onInit(); this.backgroundColorScheme,
} }) : super();
final logger = getLogger(UserPreferences);
Future<HotKey?> _getHotKeyFromLocalStorage(String key) async {
String? str = localStorage?.getString(key);
if (str != null) {
Map<String, dynamic> json = await jsonDecode(str);
if (json.isEmpty) {
return null;
}
return HotKey.fromJson(json);
}
return null;
}
Future<void> 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);
}
}
void setThemeMode(ThemeMode mode) { void setThemeMode(ThemeMode mode) {
themeMode = mode; themeMode = mode;
localStorage?.setString(LocalStorageKeys.themeMode, mode.name);
notifyListeners(); notifyListeners();
updatePersistence();
} }
void setSaveTrackLyrics(bool shouldSave) { void setSaveTrackLyrics(bool shouldSave) {
saveTrackLyrics = shouldSave; saveTrackLyrics = shouldSave;
localStorage?.setBool(LocalStorageKeys.saveTrackLyrics, shouldSave);
notifyListeners(); notifyListeners();
updatePersistence();
} }
void setRecommendationMarket(String country) { void setRecommendationMarket(String country) {
recommendationMarket = country; recommendationMarket = country;
localStorage?.setString(LocalStorageKeys.recommendationMarket, country);
notifyListeners(); notifyListeners();
updatePersistence();
} }
void setGeniusAccessToken(String token) { void setGeniusAccessToken(String token) {
geniusAccessToken = token; geniusAccessToken = token;
notifyListeners(); notifyListeners();
updatePersistence();
} }
void setNextTrackHotKey(HotKey? value) { void setNextTrackHotKey(HotKey? value) {
nextTrackHotKey = value; nextTrackHotKey = value;
localStorage?.setString(
LocalStorageKeys.nextTrackHotKey,
jsonEncode(value?.toJson() ?? {}),
);
notifyListeners(); notifyListeners();
updatePersistence();
} }
void setPrevTrackHotKey(HotKey? value) { void setPrevTrackHotKey(HotKey? value) {
prevTrackHotKey = value; prevTrackHotKey = value;
localStorage?.setString(
LocalStorageKeys.prevTrackHotKey,
jsonEncode(value?.toJson() ?? {}),
);
notifyListeners(); notifyListeners();
updatePersistence();
} }
void setPlayPauseHotKey(HotKey? value) { void setPlayPauseHotKey(HotKey? value) {
playPauseHotKey = value; playPauseHotKey = value;
localStorage?.setString(
LocalStorageKeys.playPauseHotKey,
jsonEncode(value?.toJson() ?? {}),
);
notifyListeners(); notifyListeners();
updatePersistence();
} }
void setYtSearchFormat(String format) { void setYtSearchFormat(String format) {
ytSearchFormat = format; ytSearchFormat = format;
localStorage?.setString(LocalStorageKeys.ytSearchFormate, format);
notifyListeners(); notifyListeners();
updatePersistence();
}
void setAccentColorScheme(MaterialColor color) {
accentColorScheme = color;
notifyListeners();
updatePersistence();
}
void setBackgroundColorScheme(MaterialColor color) {
backgroundColorScheme = color;
notifyListeners();
updatePersistence();
}
@override
FutureOr<void> loadFromLocal(Map<String, dynamic> 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<Map<String, dynamic>> 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,
};
} }
} }

View File

@ -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],
);
}

View File

@ -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],
);
}

View File

@ -14,13 +14,13 @@ abstract class PersistedChangeNotifier extends ChangeNotifier {
.fold<Map<String, dynamic>>({}, (acc, entry) { .fold<Map<String, dynamic>>({}, (acc, entry) {
if (entry.value != null) { if (entry.value != null) {
if (entry.value is bool) { 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) { } 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) { } 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) { } else if (entry.value is String) {
acc[entry.key] = _localStorage.getString(entry.key) ?? ""; acc[entry.key] = _localStorage.getString(entry.key);
} }
} else { } else {
acc[entry.key] = _localStorage.get(entry.key); acc[entry.key] = _localStorage.get(entry.key);