freezer/lib/settings.dart
2023-07-29 04:17:26 +02:00

414 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:path/path.dart' as p;
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:async';
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 = true;
@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;
@HiveField(45, defaultValue: false)
bool materialYouAccent = false;
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;
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 ['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))!;
}
return Settings()
..downloadPath =
await getExternalStorageDirectories(type: StorageDirectory.music)
.then((paths) => paths![0].path)
..save();
}
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': getQualityInt(mobileQuality),
'wifiQuality': getQualityInt(wifiQuality)
});
}
//AudioQuality to deezer int
int getQualityInt(AudioQuality? q) {
switch (q) {
case AudioQuality.MP3_128:
return 1;
case AudioQuality.MP3_320:
return 3;
case AudioQuality.FLAC:
return 9;
//Deezer default
default:
return 8;
}
}
// 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,
)
};
Future<String> getPath() async =>
p.join((await getApplicationDocumentsDirectory()).path, 'settings.json');
//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
}
@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);
}