mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 21:02:09 +02:00
Introduce a per-file Hi-Res authenticity checker and optional auto re-download for flagged fake Hi-Res downloads. Adds Rust checker modules (fft, evidence, FLAC/WAV decoding, tests) and a mobile API (check_hires_authenticity). Wire platform bindings for Android/iOS and PlatformBridge. Add Dart HiResCheckService, UI HiResCheckCard, settings toggle (redownloadFakeHiRes) and integration in the download pipeline to quarantine and replace suspicious files with LOSSLESS when safe. Adds claxon FLAC dependency.
405 lines
17 KiB
Dart
405 lines
17 KiB
Dart
import 'package:json_annotation/json_annotation.dart';
|
|
import 'package:spotiflac_android/utils/artist_utils.dart';
|
|
|
|
part 'settings.g.dart';
|
|
|
|
@JsonSerializable()
|
|
class AppSettings {
|
|
static const String homeFeedProviderOff = '__off__';
|
|
static const String libraryQualityLabelBitrate = 'bitrate';
|
|
static const String libraryQualityLabelFileFormat = 'file_format';
|
|
static const String libraryQualityLabelBitDepthOnly = 'bit_depth_only';
|
|
static const String libraryQualityLabelBitDepth = 'bit_depth';
|
|
static const String libraryQualityLabelBitDepthBitrate = 'bit_depth_bitrate';
|
|
|
|
final String defaultService;
|
|
final String audioQuality;
|
|
final String filenameFormat;
|
|
final String downloadDirectory;
|
|
final String downloadDirectoryBookmark;
|
|
final String storageMode; // 'app' or 'saf'
|
|
final String downloadTreeUri; // SAF persistable tree URI
|
|
final bool autoFallback;
|
|
final bool embedMetadata;
|
|
|
|
/// Maximum width or height of remotely fetched artwork before embedding.
|
|
/// Zero preserves the provider's original image.
|
|
final int embeddedCoverMaxDimension;
|
|
final String
|
|
artistTagMode; // 'joined' or 'split_vorbis' for Vorbis-based formats
|
|
final bool embedLyrics;
|
|
final bool embedReplayGain;
|
|
// Apply ReplayGain/R128 tags as volume normalization in the built-in player.
|
|
final bool playbackNormalization;
|
|
final bool autoMix;
|
|
final bool discordRichPresence;
|
|
final bool isFirstLaunch;
|
|
final bool checkForUpdates;
|
|
final String updateChannel;
|
|
final bool hasSearchedBefore;
|
|
final String folderOrganization;
|
|
final bool createPlaylistFolder;
|
|
final bool useAlbumArtistForFolders;
|
|
final bool usePrimaryArtistOnly; // Strip featured artists from folder name
|
|
final bool filterContributingArtistsInAlbumArtist;
|
|
final String historyViewMode;
|
|
final String historyFilterMode;
|
|
|
|
/// Library view opened when switching to the Library tab:
|
|
/// 'last' (keep last used), 'all', 'albums', 'singles', or 'playlists'.
|
|
final String defaultLibraryView;
|
|
|
|
/// Library badge text: measured bitrate, file format, bit depth,
|
|
/// bit depth/sample rate, or the combined bit depth/bitrate label.
|
|
final String libraryQualityLabelMode;
|
|
final bool askQualityBeforeDownload;
|
|
final bool enableLogging;
|
|
final bool useExtensionProviders;
|
|
final List<String>? downloadFallbackExtensionIds;
|
|
final String? searchProvider;
|
|
final String defaultSearchTab;
|
|
final String? homeFeedProvider;
|
|
final bool separateSingles;
|
|
final String singleFilenameFormat;
|
|
final String albumFolderStructure;
|
|
final bool showExtensionStore;
|
|
|
|
/// Shared-element (Hero) flights, e.g. the mini player artwork expanding
|
|
/// into the full player. Off skips the flights entirely.
|
|
final bool heroAnimationsEnabled;
|
|
|
|
/// Forces the shell's backdrop blur on even when the startup runtime
|
|
/// profile disabled it for this device tier. Off means "follow the
|
|
/// device default".
|
|
final bool forceBackdropBlur;
|
|
final String
|
|
extensionVerificationBrowserMode; // 'external_first' or 'in_app_first'
|
|
final String locale;
|
|
final String lyricsMode;
|
|
final bool autoConvertDownloads;
|
|
// Re-download a Hi-Res request at LOSSLESS when the file measures as fake.
|
|
final bool redownloadFakeHiRes;
|
|
final String autoConvertFormat; // 'mp3', 'aac' (M4A), or 'opus'
|
|
final String autoConvertBitrate; // '128k', '192k', '256k', or '320k'
|
|
final bool
|
|
useAllFilesAccess; // Android 13+ only: enable MANAGE_EXTERNAL_STORAGE
|
|
final bool autoExportFailedDownloads;
|
|
final String
|
|
downloadNetworkMode; // 'any' = WiFi + Mobile, 'wifi_only' = WiFi only
|
|
final bool
|
|
networkCompatibilityMode; // Try HTTP + allow invalid TLS cert for API requests
|
|
final bool
|
|
allowLocalNetwork; // Allow requests to private/local network targets (local proxy / custom DNS)
|
|
final String
|
|
songLinkRegion; // SongLink userCountry region code used for platform lookup
|
|
final bool nativeDownloadWorkerEnabled; // Android service-owned worker
|
|
final int
|
|
concurrentDownloads; // Max simultaneous downloads in the Dart queue (1-3)
|
|
|
|
final bool localLibraryEnabled;
|
|
final String localLibraryPath;
|
|
final String
|
|
localLibraryBookmark; // Base64-encoded iOS security-scoped bookmark
|
|
final bool localLibraryShowDuplicates;
|
|
final String
|
|
localLibraryAutoScan; // Auto-scan mode: 'off', 'on_open', 'daily', 'weekly'
|
|
|
|
final bool hasCompletedTutorial;
|
|
|
|
final List<String> lyricsProviders;
|
|
final bool
|
|
lyricsIncludeTranslationNetease; // Append translated lyrics (Netease)
|
|
final bool
|
|
lyricsIncludeRomanizationNetease; // Append romanized lyrics (Netease)
|
|
final bool
|
|
lyricsMultiPersonWordByWord; // Enable v1/v2 + [bg:] tags for Apple/QQ syllable lyrics
|
|
final bool
|
|
lyricsAppleElrcWordSync; // Preserve Apple Music inline word timestamps for eLRC-capable players
|
|
final String
|
|
musixmatchLanguage; // Optional ISO language code for Musixmatch localized lyrics
|
|
|
|
final String
|
|
lastSeenVersion; // Last app version the user has acknowledged (e.g. '3.7.0')
|
|
|
|
final bool deduplicateDownloads;
|
|
final bool allowQualityVariants;
|
|
final bool saveDownloadHistory;
|
|
|
|
final String playerMode;
|
|
|
|
const AppSettings({
|
|
this.defaultService = '',
|
|
this.audioQuality = 'LOSSLESS',
|
|
this.filenameFormat = '{title} - {artist}',
|
|
this.downloadDirectory = '',
|
|
this.downloadDirectoryBookmark = '',
|
|
this.storageMode = 'app',
|
|
this.downloadTreeUri = '',
|
|
this.autoFallback = true,
|
|
this.embedMetadata = true,
|
|
this.embeddedCoverMaxDimension = 0,
|
|
this.artistTagMode = artistTagModeJoined,
|
|
this.embedLyrics = true,
|
|
this.embedReplayGain = false,
|
|
this.playbackNormalization = false,
|
|
this.autoMix = false,
|
|
this.discordRichPresence = false,
|
|
this.isFirstLaunch = true,
|
|
this.checkForUpdates = true,
|
|
this.updateChannel = 'stable',
|
|
this.hasSearchedBefore = false,
|
|
this.folderOrganization = 'none',
|
|
this.createPlaylistFolder = false,
|
|
this.useAlbumArtistForFolders = true,
|
|
this.usePrimaryArtistOnly = false,
|
|
this.filterContributingArtistsInAlbumArtist = false,
|
|
this.historyViewMode = 'grid',
|
|
this.historyFilterMode = 'all',
|
|
this.defaultLibraryView = 'last',
|
|
this.libraryQualityLabelMode = AppSettings.libraryQualityLabelBitrate,
|
|
this.askQualityBeforeDownload = true,
|
|
this.enableLogging = false,
|
|
this.useExtensionProviders = true,
|
|
this.downloadFallbackExtensionIds,
|
|
this.searchProvider,
|
|
this.defaultSearchTab = 'all',
|
|
this.homeFeedProvider,
|
|
this.separateSingles = false,
|
|
this.singleFilenameFormat = '{title} - {artist}',
|
|
this.albumFolderStructure = 'artist_album',
|
|
this.showExtensionStore = true,
|
|
this.heroAnimationsEnabled = true,
|
|
this.forceBackdropBlur = false,
|
|
this.extensionVerificationBrowserMode = 'in_app_first',
|
|
this.locale = 'system',
|
|
this.lyricsMode = 'embed',
|
|
this.autoConvertDownloads = false,
|
|
this.redownloadFakeHiRes = false,
|
|
this.autoConvertFormat = 'mp3',
|
|
this.autoConvertBitrate = '320k',
|
|
this.useAllFilesAccess = false,
|
|
this.autoExportFailedDownloads = false,
|
|
this.downloadNetworkMode = 'any',
|
|
this.networkCompatibilityMode = false,
|
|
this.allowLocalNetwork = false,
|
|
this.songLinkRegion = 'US',
|
|
this.nativeDownloadWorkerEnabled = false,
|
|
this.concurrentDownloads = 1,
|
|
this.localLibraryEnabled = false,
|
|
this.localLibraryPath = '',
|
|
this.localLibraryBookmark = '',
|
|
this.localLibraryShowDuplicates = true,
|
|
this.localLibraryAutoScan = 'off',
|
|
this.hasCompletedTutorial = false,
|
|
this.lyricsProviders = const ['lrclib', 'apple_music'],
|
|
this.lyricsIncludeTranslationNetease = false,
|
|
this.lyricsIncludeRomanizationNetease = false,
|
|
this.lyricsMultiPersonWordByWord = false,
|
|
this.lyricsAppleElrcWordSync = false,
|
|
this.musixmatchLanguage = '',
|
|
this.lastSeenVersion = '',
|
|
this.deduplicateDownloads = true,
|
|
this.allowQualityVariants = false,
|
|
this.saveDownloadHistory = true,
|
|
this.playerMode = 'external',
|
|
});
|
|
|
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
Map<String, dynamic> get lyricsFetchOptions => {
|
|
'include_translation_netease': lyricsIncludeTranslationNetease,
|
|
'include_romanization_netease': lyricsIncludeRomanizationNetease,
|
|
'multi_person_word_by_word': lyricsMultiPersonWordByWord,
|
|
'apple_elrc_word_sync': lyricsAppleElrcWordSync,
|
|
'musixmatch_language': musixmatchLanguage,
|
|
};
|
|
|
|
AppSettings copyWith({
|
|
String? defaultService,
|
|
String? audioQuality,
|
|
String? filenameFormat,
|
|
String? downloadDirectory,
|
|
String? downloadDirectoryBookmark,
|
|
String? storageMode,
|
|
String? downloadTreeUri,
|
|
bool? autoFallback,
|
|
bool? embedMetadata,
|
|
int? embeddedCoverMaxDimension,
|
|
String? artistTagMode,
|
|
bool? embedLyrics,
|
|
bool? embedReplayGain,
|
|
bool? playbackNormalization,
|
|
bool? autoMix,
|
|
bool? discordRichPresence,
|
|
bool? isFirstLaunch,
|
|
bool? checkForUpdates,
|
|
String? updateChannel,
|
|
bool? hasSearchedBefore,
|
|
String? folderOrganization,
|
|
bool? createPlaylistFolder,
|
|
bool? useAlbumArtistForFolders,
|
|
bool? usePrimaryArtistOnly,
|
|
bool? filterContributingArtistsInAlbumArtist,
|
|
String? historyViewMode,
|
|
String? historyFilterMode,
|
|
String? defaultLibraryView,
|
|
String? libraryQualityLabelMode,
|
|
bool? askQualityBeforeDownload,
|
|
bool? enableLogging,
|
|
bool? useExtensionProviders,
|
|
List<String>? downloadFallbackExtensionIds,
|
|
bool clearDownloadFallbackExtensionIds = false,
|
|
String? searchProvider,
|
|
bool clearSearchProvider = false,
|
|
String? defaultSearchTab,
|
|
String? homeFeedProvider,
|
|
bool clearHomeFeedProvider = false,
|
|
bool? separateSingles,
|
|
String? singleFilenameFormat,
|
|
String? albumFolderStructure,
|
|
bool? showExtensionStore,
|
|
bool? heroAnimationsEnabled,
|
|
bool? forceBackdropBlur,
|
|
String? extensionVerificationBrowserMode,
|
|
String? locale,
|
|
String? lyricsMode,
|
|
bool? autoConvertDownloads,
|
|
bool? redownloadFakeHiRes,
|
|
String? autoConvertFormat,
|
|
String? autoConvertBitrate,
|
|
bool? useAllFilesAccess,
|
|
bool? autoExportFailedDownloads,
|
|
String? downloadNetworkMode,
|
|
bool? networkCompatibilityMode,
|
|
bool? allowLocalNetwork,
|
|
String? songLinkRegion,
|
|
bool? nativeDownloadWorkerEnabled,
|
|
int? concurrentDownloads,
|
|
bool? localLibraryEnabled,
|
|
String? localLibraryPath,
|
|
String? localLibraryBookmark,
|
|
bool? localLibraryShowDuplicates,
|
|
String? localLibraryAutoScan,
|
|
bool? hasCompletedTutorial,
|
|
List<String>? lyricsProviders,
|
|
bool? lyricsIncludeTranslationNetease,
|
|
bool? lyricsIncludeRomanizationNetease,
|
|
bool? lyricsMultiPersonWordByWord,
|
|
bool? lyricsAppleElrcWordSync,
|
|
String? musixmatchLanguage,
|
|
String? lastSeenVersion,
|
|
bool? deduplicateDownloads,
|
|
bool? allowQualityVariants,
|
|
bool? saveDownloadHistory,
|
|
String? playerMode,
|
|
}) {
|
|
return AppSettings(
|
|
defaultService: defaultService ?? this.defaultService,
|
|
audioQuality: audioQuality ?? this.audioQuality,
|
|
filenameFormat: filenameFormat ?? this.filenameFormat,
|
|
downloadDirectory: downloadDirectory ?? this.downloadDirectory,
|
|
downloadDirectoryBookmark:
|
|
downloadDirectoryBookmark ?? this.downloadDirectoryBookmark,
|
|
storageMode: storageMode ?? this.storageMode,
|
|
downloadTreeUri: downloadTreeUri ?? this.downloadTreeUri,
|
|
autoFallback: autoFallback ?? this.autoFallback,
|
|
embedMetadata: embedMetadata ?? this.embedMetadata,
|
|
embeddedCoverMaxDimension:
|
|
embeddedCoverMaxDimension ?? this.embeddedCoverMaxDimension,
|
|
artistTagMode: artistTagMode ?? this.artistTagMode,
|
|
embedLyrics: embedLyrics ?? this.embedLyrics,
|
|
embedReplayGain: embedReplayGain ?? this.embedReplayGain,
|
|
playbackNormalization:
|
|
playbackNormalization ?? this.playbackNormalization,
|
|
autoMix: autoMix ?? this.autoMix,
|
|
discordRichPresence: discordRichPresence ?? this.discordRichPresence,
|
|
isFirstLaunch: isFirstLaunch ?? this.isFirstLaunch,
|
|
checkForUpdates: checkForUpdates ?? this.checkForUpdates,
|
|
updateChannel: updateChannel ?? this.updateChannel,
|
|
hasSearchedBefore: hasSearchedBefore ?? this.hasSearchedBefore,
|
|
folderOrganization: folderOrganization ?? this.folderOrganization,
|
|
createPlaylistFolder: createPlaylistFolder ?? this.createPlaylistFolder,
|
|
useAlbumArtistForFolders:
|
|
useAlbumArtistForFolders ?? this.useAlbumArtistForFolders,
|
|
usePrimaryArtistOnly: usePrimaryArtistOnly ?? this.usePrimaryArtistOnly,
|
|
filterContributingArtistsInAlbumArtist:
|
|
filterContributingArtistsInAlbumArtist ??
|
|
this.filterContributingArtistsInAlbumArtist,
|
|
historyViewMode: historyViewMode ?? this.historyViewMode,
|
|
historyFilterMode: historyFilterMode ?? this.historyFilterMode,
|
|
defaultLibraryView: defaultLibraryView ?? this.defaultLibraryView,
|
|
libraryQualityLabelMode:
|
|
libraryQualityLabelMode ?? this.libraryQualityLabelMode,
|
|
askQualityBeforeDownload:
|
|
askQualityBeforeDownload ?? this.askQualityBeforeDownload,
|
|
enableLogging: enableLogging ?? this.enableLogging,
|
|
useExtensionProviders:
|
|
useExtensionProviders ?? this.useExtensionProviders,
|
|
downloadFallbackExtensionIds: clearDownloadFallbackExtensionIds
|
|
? null
|
|
: (downloadFallbackExtensionIds ?? this.downloadFallbackExtensionIds),
|
|
searchProvider: clearSearchProvider
|
|
? null
|
|
: (searchProvider ?? this.searchProvider),
|
|
defaultSearchTab: defaultSearchTab ?? this.defaultSearchTab,
|
|
homeFeedProvider: clearHomeFeedProvider
|
|
? null
|
|
: (homeFeedProvider ?? this.homeFeedProvider),
|
|
separateSingles: separateSingles ?? this.separateSingles,
|
|
singleFilenameFormat: singleFilenameFormat ?? this.singleFilenameFormat,
|
|
albumFolderStructure: albumFolderStructure ?? this.albumFolderStructure,
|
|
showExtensionStore: showExtensionStore ?? this.showExtensionStore,
|
|
heroAnimationsEnabled:
|
|
heroAnimationsEnabled ?? this.heroAnimationsEnabled,
|
|
forceBackdropBlur: forceBackdropBlur ?? this.forceBackdropBlur,
|
|
extensionVerificationBrowserMode:
|
|
extensionVerificationBrowserMode ??
|
|
this.extensionVerificationBrowserMode,
|
|
locale: locale ?? this.locale,
|
|
lyricsMode: lyricsMode ?? this.lyricsMode,
|
|
autoConvertDownloads: autoConvertDownloads ?? this.autoConvertDownloads,
|
|
redownloadFakeHiRes: redownloadFakeHiRes ?? this.redownloadFakeHiRes,
|
|
autoConvertFormat: autoConvertFormat ?? this.autoConvertFormat,
|
|
autoConvertBitrate: autoConvertBitrate ?? this.autoConvertBitrate,
|
|
useAllFilesAccess: useAllFilesAccess ?? this.useAllFilesAccess,
|
|
autoExportFailedDownloads:
|
|
autoExportFailedDownloads ?? this.autoExportFailedDownloads,
|
|
downloadNetworkMode: downloadNetworkMode ?? this.downloadNetworkMode,
|
|
networkCompatibilityMode:
|
|
networkCompatibilityMode ?? this.networkCompatibilityMode,
|
|
allowLocalNetwork: allowLocalNetwork ?? this.allowLocalNetwork,
|
|
songLinkRegion: songLinkRegion ?? this.songLinkRegion,
|
|
nativeDownloadWorkerEnabled:
|
|
nativeDownloadWorkerEnabled ?? this.nativeDownloadWorkerEnabled,
|
|
concurrentDownloads: concurrentDownloads ?? this.concurrentDownloads,
|
|
localLibraryEnabled: localLibraryEnabled ?? this.localLibraryEnabled,
|
|
localLibraryPath: localLibraryPath ?? this.localLibraryPath,
|
|
localLibraryBookmark: localLibraryBookmark ?? this.localLibraryBookmark,
|
|
localLibraryShowDuplicates:
|
|
localLibraryShowDuplicates ?? this.localLibraryShowDuplicates,
|
|
localLibraryAutoScan: localLibraryAutoScan ?? this.localLibraryAutoScan,
|
|
hasCompletedTutorial: hasCompletedTutorial ?? this.hasCompletedTutorial,
|
|
lyricsProviders: lyricsProviders ?? this.lyricsProviders,
|
|
lyricsIncludeTranslationNetease:
|
|
lyricsIncludeTranslationNetease ??
|
|
this.lyricsIncludeTranslationNetease,
|
|
lyricsIncludeRomanizationNetease:
|
|
lyricsIncludeRomanizationNetease ??
|
|
this.lyricsIncludeRomanizationNetease,
|
|
lyricsMultiPersonWordByWord:
|
|
lyricsMultiPersonWordByWord ?? this.lyricsMultiPersonWordByWord,
|
|
lyricsAppleElrcWordSync:
|
|
lyricsAppleElrcWordSync ?? this.lyricsAppleElrcWordSync,
|
|
musixmatchLanguage: musixmatchLanguage ?? this.musixmatchLanguage,
|
|
lastSeenVersion: lastSeenVersion ?? this.lastSeenVersion,
|
|
deduplicateDownloads: deduplicateDownloads ?? this.deduplicateDownloads,
|
|
allowQualityVariants: allowQualityVariants ?? this.allowQualityVariants,
|
|
saveDownloadHistory: saveDownloadHistory ?? this.saveDownloadHistory,
|
|
playerMode: playerMode ?? this.playerMode,
|
|
);
|
|
}
|
|
|
|
factory AppSettings.fromJson(Map<String, dynamic> json) =>
|
|
_$AppSettingsFromJson(json);
|
|
Map<String, dynamic> toJson() => _$AppSettingsToJson(this);
|
|
}
|