Files
SpotiFLAC-Mobile/lib/models/settings.dart
T
zarzet 3ca881b169 feat(lyrics): animate pronunciation and translation visibility
Add a contextual language menu for available lyric supplements in portrait and landscape. Remember independent visibility choices across tracks and player sessions without changing embedded lyrics or download options.

Fade and collapse supplements over 320 ms while interpolating lyric row heights and keeping the active line aligned. Honor reduced motion and control auto-hide; cover menu availability, persistence, animation, and word timing in widget tests.
2026-09-25 10:29:22 +07:00

415 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;
final bool playerShowPronunciation;
final bool playerShowTranslation;
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',
this.playerShowPronunciation = true,
this.playerShowTranslation = true,
});
@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,
bool? playerShowPronunciation,
bool? playerShowTranslation,
}) {
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,
playerShowPronunciation:
playerShowPronunciation ?? this.playerShowPronunciation,
playerShowTranslation:
playerShowTranslation ?? this.playerShowTranslation,
);
}
factory AppSettings.fromJson(Map<String, dynamic> json) =>
_$AppSettingsFromJson(json);
Map<String, dynamic> toJson() => _$AppSettingsToJson(this);
}