417 lines
11 KiB
Dart
417 lines
11 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:freezer/api/definitions.dart';
|
|
import 'package:freezer/api/download.dart';
|
|
import 'package:freezer/api/player.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
|
import 'package:json_annotation/json_annotation.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'dart:convert';
|
|
import 'dart:async';
|
|
import 'dart:io' show Platform;
|
|
|
|
part 'settings.g.dart';
|
|
|
|
late Settings settings;
|
|
|
|
@HiveType(typeId: 24)
|
|
@JsonSerializable()
|
|
class Settings {
|
|
//Language
|
|
@HiveField(0)
|
|
@JsonKey(defaultValue: null)
|
|
String? language;
|
|
|
|
//Main
|
|
@HiveField(1)
|
|
bool ignoreInterruptions = false;
|
|
@HiveField(2)
|
|
bool enableEqualizer = false;
|
|
|
|
//Account
|
|
@HiveField(3)
|
|
String? arl;
|
|
@JsonKey(includeToJson: false)
|
|
bool offlineMode = false;
|
|
|
|
//Quality
|
|
@HiveField(4)
|
|
AudioQuality wifiQuality = AudioQuality.MP3_320;
|
|
@HiveField(5)
|
|
AudioQuality mobileQuality = AudioQuality.MP3_128;
|
|
@HiveField(6)
|
|
AudioQuality offlineQuality = AudioQuality.FLAC;
|
|
@HiveField(7)
|
|
AudioQuality downloadQuality = AudioQuality.FLAC;
|
|
|
|
//Download options
|
|
@HiveField(8)
|
|
String? downloadPath;
|
|
|
|
@HiveField(9)
|
|
String downloadFilename = "%artist% - %title%";
|
|
@HiveField(10)
|
|
bool albumFolder = true;
|
|
@HiveField(11)
|
|
bool artistFolder = true;
|
|
@HiveField(12)
|
|
bool albumDiscFolder = true;
|
|
@HiveField(13)
|
|
bool overwriteDownload = false;
|
|
|
|
@HiveField(14)
|
|
int downloadThreads = 2;
|
|
@HiveField(15)
|
|
bool playlistFolder = false;
|
|
@HiveField(16)
|
|
bool downloadLyrics = true;
|
|
@HiveField(17)
|
|
bool trackCover = false;
|
|
@HiveField(18)
|
|
bool albumCover = true;
|
|
@HiveField(19)
|
|
bool nomediaFiles = false;
|
|
@HiveField(20)
|
|
String artistSeparator = ', ';
|
|
@HiveField(21)
|
|
String singletonFilename = "%artist% - %title%";
|
|
@HiveField(22)
|
|
int albumArtResolution = 1400;
|
|
@HiveField(23)
|
|
List<String> tags = [
|
|
"title",
|
|
"album",
|
|
"artist",
|
|
"track",
|
|
"disc",
|
|
"albumArtist",
|
|
"date",
|
|
"label",
|
|
"isrc",
|
|
"upc",
|
|
"trackTotal",
|
|
"bpm",
|
|
"lyrics",
|
|
"genre",
|
|
"contributors",
|
|
"art"
|
|
];
|
|
|
|
//Appearance
|
|
@HiveField(24)
|
|
Themes theme = Themes.Dark;
|
|
@HiveField(25)
|
|
bool useSystemTheme = false;
|
|
@HiveField(26)
|
|
bool colorGradientBackground = false;
|
|
@HiveField(27)
|
|
bool blurPlayerBackground = false;
|
|
@HiveField(28)
|
|
String font = 'Deezer';
|
|
@HiveField(29)
|
|
bool lyricsVisualizer = false;
|
|
@HiveField(30)
|
|
int? displayMode;
|
|
@HiveField(31, defaultValue: true)
|
|
bool enableFilledPlayButton = true;
|
|
@HiveField(32, defaultValue: false)
|
|
bool playerBackgroundOnLyrics = false;
|
|
@HiveField(33, defaultValue: NavigatorRouteType.material)
|
|
NavigatorRouteType navigatorRouteType = NavigatorRouteType.material;
|
|
|
|
//Colors
|
|
@HiveField(34, defaultValue: Colors.blue)
|
|
@JsonKey(toJson: _colorToJson, fromJson: _colorFromJson)
|
|
Color primaryColor = Colors.blue;
|
|
|
|
static _colorToJson(Color c) => c.value;
|
|
static _colorFromJson(int? v) => Color(v ?? Colors.blue.value);
|
|
|
|
@HiveField(35)
|
|
bool useArtColor = false;
|
|
|
|
//Deezer
|
|
@HiveField(36)
|
|
// TODO: maybe convert to [Locale]?
|
|
String deezerLanguage = 'en';
|
|
@HiveField(37)
|
|
String deezerCountry = 'US';
|
|
@HiveField(38)
|
|
bool logListen = false;
|
|
@HiveField(39)
|
|
String? proxyAddress;
|
|
|
|
//LastFM
|
|
@HiveField(40)
|
|
String? lastFMUsername;
|
|
@HiveField(41)
|
|
String? lastFMPassword;
|
|
|
|
//Spotify
|
|
@HiveField(42)
|
|
String? spotifyClientId;
|
|
@HiveField(43)
|
|
String? spotifyClientSecret;
|
|
@HiveField(44)
|
|
SpotifyCredentialsSave? spotifyCredentials;
|
|
|
|
@HiveField(45, defaultValue: false)
|
|
bool materialYouAccent = false;
|
|
|
|
@HiveField(46, defaultValue: true)
|
|
bool playerAlbumArtDropShadow = true;
|
|
|
|
static LazyBox<Settings>? __box;
|
|
static Future<LazyBox<Settings>> get _box async =>
|
|
__box ??= await Hive.openLazyBox<Settings>('settings');
|
|
|
|
Settings();
|
|
|
|
ThemeData? get themeData {
|
|
//System theme
|
|
if (useSystemTheme) {
|
|
if (PlatformDispatcher.instance.platformBrightness == Brightness.light) {
|
|
return _themeData[Themes.Light];
|
|
}
|
|
|
|
if (theme == Themes.Light) return _themeData[Themes.Dark];
|
|
return _themeData[theme];
|
|
}
|
|
//Theme
|
|
return _themeData[theme] ?? ThemeData();
|
|
}
|
|
|
|
//Get all available fonts
|
|
List<String> get fonts {
|
|
return ['System', 'Deezer', ...GoogleFonts.asMap().keys];
|
|
}
|
|
|
|
//JSON to forward into download service
|
|
Map getServiceSettings() {
|
|
return {"json": jsonEncode(this)};
|
|
}
|
|
|
|
void updateUseArtColor(bool v) {
|
|
useArtColor = v;
|
|
//TODO: let's reimplement this somewhere better
|
|
//if (v) {
|
|
// //On media item change set color
|
|
// _useArtColorSub =
|
|
// AudioService.currentMediaItemStream.listen((event) async {
|
|
// if (event == null || event.artUri == null) return;
|
|
// this.primaryColor =
|
|
// await imagesDatabase.getPrimaryColor(event.artUri.toString());
|
|
// updateTheme();
|
|
// });
|
|
//} else {
|
|
// //Cancel stream subscription
|
|
// if (_useArtColorSub != null) {
|
|
// _useArtColorSub!.cancel();
|
|
// _useArtColorSub = null;
|
|
// }
|
|
//}
|
|
}
|
|
|
|
SliderThemeData get _sliderTheme => SliderThemeData(
|
|
thumbColor: primaryColor,
|
|
activeTrackColor: primaryColor,
|
|
inactiveTrackColor: primaryColor.withOpacity(0.2));
|
|
|
|
//Load settings/init
|
|
static Future<Settings> load() async {
|
|
final box = await _box;
|
|
if (box.containsKey(0)) {
|
|
return (await box.get(0))!;
|
|
}
|
|
|
|
final settings = Settings();
|
|
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
|
settings.downloadPath =
|
|
await getDownloadsDirectory().then((path) => path!.path);
|
|
} else {
|
|
settings.downloadPath =
|
|
await getExternalStorageDirectories(type: StorageDirectory.music)
|
|
.then((paths) => paths![0].path);
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
Future<void> save() async {
|
|
final box = await _box;
|
|
await box.clear();
|
|
await box.put(0, this);
|
|
downloadManager.updateServiceSettings();
|
|
}
|
|
|
|
Future<void> updateAudioServiceQuality() async {
|
|
//Send wifi & mobile quality to audio service isolate
|
|
await audioHandler.customAction('updateQuality',
|
|
{'mobileQuality': mobileQuality, 'wifiQuality': wifiQuality});
|
|
}
|
|
|
|
// MaterialColor get _primarySwatch =>
|
|
// MaterialColor(primaryColor.value, <int, Color>{
|
|
// 50: primaryColor.withOpacity(.1),
|
|
// 100: primaryColor.withOpacity(.2),
|
|
// 200: primaryColor.withOpacity(.3),
|
|
// 300: primaryColor.withOpacity(.4),
|
|
// 400: primaryColor.withOpacity(.5),
|
|
// 500: primaryColor.withOpacity(.6),
|
|
// 600: primaryColor.withOpacity(.7),
|
|
// 700: primaryColor.withOpacity(.8),
|
|
// 800: primaryColor.withOpacity(.9),
|
|
// 900: primaryColor.withOpacity(1),
|
|
// });
|
|
|
|
//Check if is dark, can't use theme directly, because of system themes, and Theme.of(context).brightness broke
|
|
bool get isDark {
|
|
if (useSystemTheme) {
|
|
if (PlatformDispatcher.instance.platformBrightness == Brightness.light)
|
|
return false;
|
|
return true;
|
|
}
|
|
if (theme == Themes.Light) return false;
|
|
return true;
|
|
}
|
|
|
|
static const deezerBg = Color(0xFF1F1A16);
|
|
static const deezerBottom = Color(0xFF1b1714);
|
|
TextTheme? get textTheme => (font == 'Deezer')
|
|
? null
|
|
: GoogleFonts.getTextTheme(
|
|
font,
|
|
this.isDark
|
|
? ThemeData.dark().textTheme
|
|
: ThemeData.light().textTheme);
|
|
String? get fontFamily => (font == 'Deezer') ? 'MabryPro' : null;
|
|
|
|
final _elevation1Black = Color.alphaBlend(Colors.white12, Colors.black);
|
|
|
|
late final Map<Themes, ThemeData> _themeData = {
|
|
Themes.Light: ThemeData(
|
|
textTheme: textTheme,
|
|
fontFamily: fontFamily,
|
|
brightness: Brightness.light,
|
|
primaryColor: primaryColor,
|
|
colorScheme: ColorScheme.fromSeed(seedColor: primaryColor),
|
|
sliderTheme: _sliderTheme,
|
|
bottomAppBarTheme: BottomAppBarTheme(color: Color(0xfff5f5f5)),
|
|
useMaterial3: true,
|
|
),
|
|
Themes.Dark: ThemeData(
|
|
textTheme: textTheme,
|
|
fontFamily: fontFamily,
|
|
brightness: Brightness.dark,
|
|
primaryColor: primaryColor,
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: primaryColor,
|
|
brightness: Brightness.dark,
|
|
),
|
|
sliderTheme: _sliderTheme,
|
|
useMaterial3: true,
|
|
),
|
|
Themes.Deezer: ThemeData(
|
|
textTheme: textTheme,
|
|
fontFamily: fontFamily,
|
|
brightness: Brightness.dark,
|
|
primaryColor: primaryColor,
|
|
colorScheme: ColorScheme.fromSeed(
|
|
primary: primaryColor,
|
|
seedColor: deezerBg,
|
|
brightness: Brightness.dark),
|
|
sliderTheme: _sliderTheme,
|
|
scaffoldBackgroundColor: deezerBg,
|
|
bottomAppBarTheme: const BottomAppBarTheme(color: deezerBottom),
|
|
dialogBackgroundColor: deezerBottom,
|
|
bottomSheetTheme:
|
|
const BottomSheetThemeData(backgroundColor: deezerBottom),
|
|
cardColor: deezerBg,
|
|
useMaterial3: true,
|
|
),
|
|
Themes.Black: ThemeData(
|
|
textTheme: textTheme,
|
|
fontFamily: fontFamily,
|
|
brightness: Brightness.dark,
|
|
primaryColor: primaryColor,
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: Colors.black,
|
|
primary: primaryColor,
|
|
background: Colors.black,
|
|
brightness: Brightness.dark),
|
|
scaffoldBackgroundColor: Colors.black,
|
|
navigationBarTheme:
|
|
const NavigationBarThemeData(backgroundColor: Colors.black),
|
|
bottomAppBarTheme: const BottomAppBarTheme(color: Colors.black),
|
|
dialogBackgroundColor: _elevation1Black,
|
|
sliderTheme: _sliderTheme,
|
|
bottomSheetTheme: BottomSheetThemeData(backgroundColor: _elevation1Black),
|
|
cardColor: _elevation1Black,
|
|
useMaterial3: true,
|
|
)
|
|
};
|
|
|
|
//JSON
|
|
factory Settings.fromJson(Map<String, dynamic> json) =>
|
|
_$SettingsFromJson(json);
|
|
Map<String, dynamic> toJson() => _$SettingsToJson(this);
|
|
}
|
|
|
|
@HiveType(typeId: 29)
|
|
enum AudioQuality {
|
|
@HiveField(0)
|
|
MP3_128,
|
|
@HiveField(1)
|
|
MP3_320,
|
|
@HiveField(2)
|
|
FLAC,
|
|
@HiveField(3)
|
|
ASK
|
|
}
|
|
|
|
extension ToDeezerInt on AudioQuality {
|
|
int toDeezerQualityInt() {
|
|
return const {
|
|
AudioQuality.MP3_128: 1,
|
|
AudioQuality.MP3_320: 3,
|
|
AudioQuality.FLAC: 9,
|
|
}[this] ??
|
|
8;
|
|
}
|
|
}
|
|
|
|
@HiveType(typeId: 28)
|
|
enum Themes {
|
|
@HiveField(0)
|
|
Light,
|
|
@HiveField(1)
|
|
Dark,
|
|
@HiveField(2)
|
|
Deezer,
|
|
@HiveField(3)
|
|
Black
|
|
}
|
|
|
|
@HiveType(typeId: 25)
|
|
@JsonSerializable()
|
|
class SpotifyCredentialsSave {
|
|
@HiveField(0)
|
|
String? accessToken;
|
|
@HiveField(1)
|
|
String? refreshToken;
|
|
@HiveField(2)
|
|
List<String>? scopes;
|
|
@HiveField(3)
|
|
DateTime? expiration;
|
|
|
|
SpotifyCredentialsSave(
|
|
{this.accessToken, this.refreshToken, this.scopes, this.expiration});
|
|
|
|
//JSON
|
|
factory SpotifyCredentialsSave.fromJson(Map<String, dynamic> json) =>
|
|
_$SpotifyCredentialsSaveFromJson(json);
|
|
Map<String, dynamic> toJson() => _$SpotifyCredentialsSaveToJson(this);
|
|
}
|