import 'package:flutter/scheduler.dart'; import 'package:flutter/services.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: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:io'; import 'dart:convert'; import 'dart:async'; part 'settings.g.dart'; late Settings settings; @JsonSerializable() class Settings { //Language @JsonKey(defaultValue: null) String? language; //Main @JsonKey(defaultValue: false) late bool ignoreInterruptions; @JsonKey(defaultValue: false) late bool enableEqualizer; //Account String? arl; @JsonKey(ignore: true) bool offlineMode = false; //Quality @JsonKey(defaultValue: AudioQuality.MP3_320) late AudioQuality wifiQuality; @JsonKey(defaultValue: AudioQuality.MP3_128) late AudioQuality mobileQuality; @JsonKey(defaultValue: AudioQuality.FLAC) late AudioQuality offlineQuality; @JsonKey(defaultValue: AudioQuality.FLAC) late AudioQuality downloadQuality; //Download options String? downloadPath; @JsonKey(defaultValue: "%artist% - %title%") late String downloadFilename; @JsonKey(defaultValue: true) late bool albumFolder; @JsonKey(defaultValue: true) late bool artistFolder; @JsonKey(defaultValue: false) late bool albumDiscFolder; @JsonKey(defaultValue: false) late bool overwriteDownload; @JsonKey(defaultValue: 2) late int downloadThreads; @JsonKey(defaultValue: false) late bool playlistFolder; @JsonKey(defaultValue: true) late bool downloadLyrics; @JsonKey(defaultValue: false) late bool trackCover; @JsonKey(defaultValue: true) late bool albumCover; @JsonKey(defaultValue: false) late bool nomediaFiles; @JsonKey(defaultValue: ", ") late String artistSeparator; @JsonKey(defaultValue: "%artist% - %title%") late String singletonFilename; @JsonKey(defaultValue: 1400) late int albumArtResolution; @JsonKey(defaultValue: [ "title", "album", "artist", "track", "disc", "albumArtist", "date", "label", "isrc", "upc", "trackTotal", "bpm", "lyrics", "genre", "contributors", "art" ]) late List tags; //Appearance @JsonKey(defaultValue: Themes.Dark) late Themes theme; @JsonKey(defaultValue: false) late bool useSystemTheme; @JsonKey(defaultValue: true) late bool colorGradientBackground; @JsonKey(defaultValue: false) late bool blurPlayerBackground; @JsonKey(defaultValue: "Deezer") late String font; @JsonKey(defaultValue: false) late bool lyricsVisualizer; @JsonKey(defaultValue: null) int? displayMode; @JsonKey(defaultValue: true) late bool enableFilledPlayButton; @JsonKey(defaultValue: false) late bool playerBackgroundOnLyrics; @JsonKey(defaultValue: NavigatorRouteType.material) late NavigatorRouteType navigatorRouteType; //Colors @JsonKey(toJson: _colorToJson, fromJson: _colorFromJson) Color primaryColor = Colors.blue; static _colorToJson(Color c) => c.value; static _colorFromJson(int? v) => Color(v ?? Colors.blue.value); @JsonKey(defaultValue: false) bool useArtColor = false; //Deezer @JsonKey(defaultValue: 'en') String? deezerLanguage; @JsonKey(defaultValue: 'US') String? deezerCountry; @JsonKey(defaultValue: false) bool? logListen; @JsonKey(defaultValue: null) String? proxyAddress; //LastFM @JsonKey(defaultValue: null) String? lastFMUsername; @JsonKey(defaultValue: null) String? lastFMPassword; //Spotify @JsonKey(defaultValue: null) String? spotifyClientId; @JsonKey(defaultValue: null) String? spotifyClientSecret; @JsonKey(defaultValue: null) SpotifyCredentialsSave? spotifyCredentials; Settings({this.downloadPath, this.arl}); ThemeData? get themeData { //System theme if (useSystemTheme) { if (SchedulerBinding.instance!.window.platformBrightness == Brightness.light) { return _themeData[Themes.Light]; } else { if (theme == Themes.Light) return _themeData[Themes.Dark]; return _themeData[theme]; } } //Theme return _themeData[theme] ?? ThemeData(); } //Get all available fonts List get fonts { return ['Deezer', ...GoogleFonts.asMap().keys]; } //JSON to forward into download service Map getServiceSettings() { return {"json": jsonEncode(this.toJson())}; } 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 Future loadSettings() async { String path = await getPath(); File f = File(path); if (await f.exists()) { String data = await f.readAsString(); return Settings.fromJson(jsonDecode(data)); } Settings s = Settings.fromJson({}); //Set default path, because async s.downloadPath = await getExternalStorageDirectories(type: StorageDirectory.music) .then((paths) => paths![0].path); s.save(); return s; } Future save() async { File f = File(await getPath()); await f.writeAsString(jsonEncode(this.toJson())); downloadManager.updateServiceSettings(); } Future 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, { 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 (SchedulerBinding.instance!.window.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; Map get _themeData => { Themes.Light: ThemeData( textTheme: _textTheme, fontFamily: _fontFamily, brightness: Brightness.light, primarySwatch: _primarySwatch, primaryColor: primaryColor, colorScheme: ColorScheme.fromSwatch( primarySwatch: _primarySwatch, accentColor: primaryColor, brightness: Brightness.light), sliderTheme: _sliderTheme, toggleableActiveColor: primaryColor, bottomAppBarColor: Color(0xfff5f5f5), appBarTheme: AppBarTheme(systemOverlayStyle: SystemUiOverlayStyle.light), ), Themes.Dark: ThemeData( textTheme: _textTheme, fontFamily: _fontFamily, brightness: Brightness.dark, primarySwatch: _primarySwatch, primaryColor: primaryColor, colorScheme: ColorScheme.fromSwatch( primarySwatch: _primarySwatch, accentColor: primaryColor, brightness: Brightness.dark), sliderTheme: _sliderTheme, toggleableActiveColor: primaryColor, ), Themes.Deezer: ThemeData( textTheme: _textTheme, fontFamily: _fontFamily, brightness: Brightness.dark, primarySwatch: _primarySwatch, primaryColor: primaryColor, colorScheme: ColorScheme.fromSwatch( primarySwatch: _primarySwatch, accentColor: primaryColor, brightness: Brightness.dark), sliderTheme: _sliderTheme, toggleableActiveColor: primaryColor, backgroundColor: deezerBg, scaffoldBackgroundColor: deezerBg, bottomAppBarColor: deezerBottom, dialogBackgroundColor: deezerBottom, bottomSheetTheme: BottomSheetThemeData(backgroundColor: deezerBottom), cardColor: deezerBg, ), Themes.Black: ThemeData( textTheme: _textTheme, fontFamily: _fontFamily, brightness: Brightness.dark, primarySwatch: _primarySwatch, primaryColor: primaryColor, colorScheme: ColorScheme.fromSwatch( primarySwatch: _primarySwatch, accentColor: primaryColor, brightness: Brightness.dark), backgroundColor: Colors.black, scaffoldBackgroundColor: Colors.black, bottomAppBarColor: Colors.black, dialogBackgroundColor: Colors.black, sliderTheme: _sliderTheme, toggleableActiveColor: primaryColor, bottomSheetTheme: BottomSheetThemeData( backgroundColor: Colors.black, ), ) }; Future getPath() async => p.join((await getApplicationDocumentsDirectory()).path, 'settings.json'); //JSON factory Settings.fromJson(Map json) => _$SettingsFromJson(json); Map toJson() => _$SettingsToJson(this); } enum AudioQuality { MP3_128, MP3_320, FLAC, ASK } enum Themes { Light, Dark, Deezer, Black } @JsonSerializable() class SpotifyCredentialsSave { String? accessToken; String? refreshToken; List? scopes; DateTime? expiration; SpotifyCredentialsSave( {this.accessToken, this.refreshToken, this.scopes, this.expiration}); //JSON factory SpotifyCredentialsSave.fromJson(Map json) => _$SpotifyCredentialsSaveFromJson(json); Map toJson() => _$SpotifyCredentialsSaveToJson(this); }