chore: cleanup unused code and dead imports

This commit is contained in:
zarzet
2026-01-20 02:10:10 +07:00
parent 8e9d0c3e9a
commit 03027813c1
62 changed files with 213 additions and 1326 deletions
+28 -71
View File
@@ -125,7 +125,7 @@ class DownloadHistoryItem {
class DownloadHistoryState {
final List<DownloadHistoryItem> items;
final Set<String> _downloadedSpotifyIds; // Cache for O(1) lookup
final Set<String> _downloadedSpotifyIds;
DownloadHistoryState({this.items = const []})
: _downloadedSpotifyIds = items
@@ -133,7 +133,6 @@ class DownloadHistoryState {
.map((item) => item.spotifyId!)
.toSet();
/// Check if a track has been downloaded (by Spotify ID)
bool isDownloaded(String spotifyId) =>
_downloadedSpotifyIds.contains(spotifyId);
@@ -188,7 +187,6 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
}
}
/// Deduplicate history items by spotifyId, deezerId, or ISRC
/// Keeps the most recent entry (first occurrence since list is sorted by date desc)
List<DownloadHistoryItem> _deduplicateHistory(List<DownloadHistoryItem> items) {
final seen = <String, int>{}; // key -> index of first occurrence
@@ -234,7 +232,6 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
}
}
/// Force reload from storage (useful after app restart)
Future<void> reloadFromStorage() async {
await _loadFromStorage();
}
@@ -285,7 +282,6 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
_saveToStorage();
}
/// Remove item from history by Spotify ID
void removeBySpotifyId(String spotifyId) {
state = state.copyWith(
items: state.items.where((item) => item.spotifyId != spotifyId).toList(),
@@ -294,7 +290,6 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
_historyLog.d('Removed item with spotifyId: $spotifyId');
}
/// Get history item by Spotify ID
DownloadHistoryItem? getBySpotifyId(String spotifyId) {
return state.items.where((item) => item.spotifyId == spotifyId).firstOrNull;
}
@@ -314,12 +309,12 @@ class DownloadQueueState {
final List<DownloadItem> items;
final DownloadItem? currentDownload;
final bool isProcessing;
final bool isPaused; // NEW: pause state
final bool isPaused;
final String outputDir;
final String filenameFormat;
final String audioQuality; // LOSSLESS, HI_RES, HI_RES_LOSSLESS
final String audioQuality;
final bool autoFallback;
final int concurrentDownloads; // 1 = sequential, max 3
final int concurrentDownloads;
const DownloadQueueState({
this.items = const [],
@@ -386,14 +381,13 @@ class _ProgressUpdate {
class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
Timer? _progressTimer;
int _downloadCount = 0; // Counter for connection cleanup
static const _cleanupInterval = 50; // Cleanup every 50 downloads
static const _queueStorageKey =
'download_queue'; // Storage key for queue persistence
int _downloadCount = 0;
static const _cleanupInterval = 50;
static const _queueStorageKey = 'download_queue';
final NotificationService _notificationService = NotificationService();
int _totalQueuedAtStart = 0; // Track total items when queue started
int _completedInSession = 0; // Track completed downloads in current session
int _failedInSession = 0; // Track failed downloads in current session
int _totalQueuedAtStart = 0;
int _completedInSession = 0;
int _failedInSession = 0;
bool _isLoaded = false;
final Set<String> _ensuredDirs = {};
@@ -411,7 +405,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
return const DownloadQueueState();
}
/// Load persisted queue from storage (for app restart recovery)
Future<void> _loadQueueFromStorage() async {
if (_isLoaded) return;
_isLoaded = true;
@@ -453,7 +446,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
/// Save current queue to storage (only pending items)
Future<void> _saveQueueToStorage() async {
try {
final prefs = await SharedPreferences.getInstance();
@@ -479,7 +471,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
/// Start multi-progress polling for all downloads (sequential and parallel)
void _startMultiProgressPolling() {
_progressTimer?.cancel();
_progressTimer = Timer.periodic(const Duration(milliseconds: 500), (
@@ -607,7 +598,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
trackName: finalizingTrackName,
artistName: finalizingArtistName ?? '',
);
return; // Don't show download progress notification
return;
}
if (items.isNotEmpty) {
@@ -651,14 +642,11 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
progress: notifProgress,
total: notifTotal > 0 ? notifTotal : 1,
queueCount: state.queuedCount,
).catchError((_) {}); // Ignore errors
).catchError((_) {});
}
}
}
} catch (e) {
// Silently ignore polling errors to avoid spamming logs
// Polling is not critical and will retry on next interval
}
} catch (_) {}
});
}
@@ -725,7 +713,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
state = state.copyWith(outputDir: dir);
}
/// Build output directory based on folder organization setting and separateSingles
Future<String> _buildOutputDir(Track track, String folderOrganization, {bool separateSingles = false, String albumFolderStructure = 'artist_album'}) async {
String baseDir = state.outputDir;
final albumArtist = _normalizeOptionalString(track.albumArtist) ?? track.artistName;
@@ -794,7 +781,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
return baseDir;
}
/// Sanitize folder names (remove invalid characters)
String _sanitizeFolderName(String name) {
return name
.replaceAll(RegExp(r'[<>:"/\\|?*]'), '_')
@@ -866,7 +852,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}).toList();
state = state.copyWith(items: [...state.items, ...newItems]);
_saveQueueToStorage(); // Persist queue
_saveQueueToStorage();
if (!state.isProcessing) {
Future.microtask(() => _processQueue());
@@ -951,15 +937,14 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
.toList();
state = state.copyWith(items: items);
_saveQueueToStorage(); // Persist queue
_saveQueueToStorage();
}
void clearAll() {
state = state.copyWith(items: [], isPaused: false);
_saveQueueToStorage(); // Clear persisted queue
_saveQueueToStorage();
}
/// Pause the download queue
void pauseQueue() {
if (state.isProcessing && !state.isPaused) {
state = state.copyWith(isPaused: true);
@@ -968,7 +953,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
/// Resume the download queue
void resumeQueue() {
if (state.isPaused) {
state = state.copyWith(isPaused: false);
@@ -979,7 +963,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
/// Toggle pause/resume
void togglePause() {
if (state.isPaused) {
resumeQueue();
@@ -988,7 +971,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
/// Retry a failed or skipped download
void retryItem(String id) {
final item = state.items.where((i) => i.id == id).firstOrNull;
if (item == null) {
@@ -1025,14 +1007,12 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
/// Remove a specific item from queue
void removeItem(String id) {
final items = state.items.where((item) => item.id != id).toList();
state = state.copyWith(items: items);
_saveQueueToStorage(); // Persist queue
_saveQueueToStorage();
}
/// Run post-processing hooks on a downloaded file
Future<void> _runPostProcessingHooks(String filePath, Track track) async {
try {
final settings = ref.read(settingsProvider);
@@ -1079,7 +1059,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
/// Upgrade Spotify cover URL to max quality (~2000x2000)
/// Same logic as Go backend cover.go
String _upgradeToMaxQualityCover(String coverUrl) {
const spotifySize300 = 'ab67616d00001e02'; // 300x300 (small)
@@ -1098,7 +1077,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
return result;
}
/// Embed metadata and cover to a FLAC file after M4A conversion
Future<void> _embedMetadataAndCover(
String flacPath,
Track track, {
@@ -1155,12 +1133,12 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
if (track.trackNumber != null) {
metadata['TRACKNUMBER'] = track.trackNumber.toString();
metadata['TRACK'] = track.trackNumber.toString(); // Compatibility
metadata['TRACK'] = track.trackNumber.toString();
}
if (track.discNumber != null) {
metadata['DISCNUMBER'] = track.discNumber.toString();
metadata['DISC'] = track.discNumber.toString(); // Compatibility
metadata['DISC'] = track.discNumber.toString();
}
if (track.releaseDate != null) {
@@ -1172,7 +1150,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
metadata['ISRC'] = track.isrc!;
}
// Extended metadata from enrichment (genre, label, copyright)
if (genre != null && genre.isNotEmpty) {
metadata['GENRE'] = genre;
_log.d('Adding GENRE: $genre');
@@ -1189,20 +1166,19 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
_log.d('Metadata map content: $metadata');
try {
// Convert duration from seconds to milliseconds for better lyrics matching
final durationMs = track.duration * 1000;
final lrcContent = await PlatformBridge.getLyricsLRC(
track.id, // spotifyID
track.id,
track.name,
track.artistName,
filePath: '', // No local file path yet (processed in memory)
filePath: '',
durationMs: durationMs,
);
if (lrcContent.isNotEmpty) {
metadata['LYRICS'] = lrcContent;
metadata['UNSYNCEDLYRICS'] = lrcContent; // Fallback for some players
metadata['UNSYNCEDLYRICS'] = lrcContent;
_log.d('Lyrics fetched for embedding (${lrcContent.length} chars)');
}
} catch (e) {
@@ -1240,7 +1216,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
/// Embed metadata, lyrics, and cover to a MP3 file
Future<void> _embedMetadataToMp3(String mp3Path, Track track) async {
final settings = ref.read(settingsProvider);
@@ -1310,7 +1285,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
_log.d('MP3 Metadata map content: $metadata');
// Fetch lyrics if embedLyrics is enabled
if (settings.embedLyrics) {
try {
final durationMs = track.duration * 1000;
@@ -1365,7 +1339,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
Future<void> _processQueue() async {
if (state.isProcessing) return; // Prevent multiple concurrent processing
if (state.isProcessing) return;
state = state.copyWith(isProcessing: true);
_log.i('Starting queue processing...');
@@ -1462,7 +1436,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
/// Sequential download processing (uses multi-progress system with single item)
Future<void> _processQueueSequential() async {
_startMultiProgressPolling();
@@ -1508,10 +1481,10 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
_stopProgressPolling();
}
/// Parallel download processing with worker pool
Future<void> _processQueueParallel() async {
final maxConcurrent = state.concurrentDownloads;
final activeDownloads = <String, Future<void>>{}; // Map item ID to future
final activeDownloads = <String, Future<void>>{};
_startMultiProgressPolling();
@@ -1565,7 +1538,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
_stopProgressPolling();
}
/// Download a single item (used by both sequential and parallel processing)
Future<void> _downloadSingleItem(DownloadItem item) async {
_log.d('Processing: ${item.track.name} by ${item.track.artistName}');
_log.d('Cover URL: ${item.track.coverUrl}');
@@ -1628,7 +1600,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
trackToDownload.albumName,
albumArtist: data['album_artist'] as String?,
coverUrl: data['images'] as String?,
// duration_ms from Go is in milliseconds, Track.duration is in seconds
duration:
((data['duration_ms'] as int?) ??
(trackToDownload.duration * 1000)) ~/
@@ -1675,7 +1646,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
String? genre;
String? label;
// Try to get Deezer track ID from various sources
String? deezerTrackId = trackToDownload.deezerId;
if (deezerTrackId == null && trackToDownload.id.startsWith('deezer:')) {
deezerTrackId = trackToDownload.id.split(':')[1];
@@ -1696,7 +1666,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
} catch (e) {
_log.w('Failed to fetch extended metadata from Deezer: $e');
// Continue without extended metadata
}
}
@@ -1728,7 +1697,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
releaseDate: trackToDownload.releaseDate,
itemId: item.id,
durationMs: trackToDownload.duration,
source: trackToDownload.source, // Pass extension ID that provided this track
source: trackToDownload.source,
genre: genre,
label: label,
lyricsMode: settings.lyricsMode,
@@ -1754,9 +1723,8 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
discNumber: trackToDownload.discNumber ?? 1,
releaseDate: trackToDownload.releaseDate,
preferredService: item.service,
itemId: item.id, // Pass item ID for progress tracking
durationMs:
trackToDownload.duration, // Duration in ms for verification
itemId: item.id,
durationMs: trackToDownload.duration,
genre: genre,
label: label,
lyricsMode: settings.lyricsMode,
@@ -1809,8 +1777,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
if (result['success'] == true) {
var filePath = result['file_path'] as String?;
// Track if this was an existing file (not a new download)
// This is important to prevent converting existing FLAC files to MP3
final wasExisting = filePath != null && filePath.startsWith('EXISTS:');
if (wasExisting) {
filePath = filePath.substring(7); // Remove "EXISTS:" prefix
@@ -1912,7 +1878,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
);
}
// Get extended metadata from backend response
final backendGenre = result['genre'] as String?;
final backendLabel = result['label'] as String?;
final backendCopyright = result['copyright'] as String?;
@@ -1962,15 +1927,9 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
return;
}
// Convert FLAC to MP3 if MP3 quality was selected
// IMPORTANT: Only convert NEW downloads, never convert existing files
// to prevent overwriting the user's existing FLAC files
if (quality == 'MP3' && filePath != null && filePath.endsWith('.flac')) {
if (wasExisting) {
// User wanted MP3 but an existing FLAC file was found
// Do NOT convert it - that would delete their existing FLAC
_log.i('MP3 requested but existing FLAC found - skipping conversion to preserve original file');
// Keep the existing FLAC file as-is
} else {
_log.i('MP3 quality selected, converting FLAC to MP3...');
updateItemStatus(
@@ -1991,7 +1950,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
actualQuality = 'MP3 320kbps';
_log.i('Successfully converted to MP3: $mp3Path');
// Embed metadata, lyrics, and cover to the MP3 file
_log.i('Embedding metadata to MP3...');
updateItemStatus(
item.id,
@@ -2050,7 +2008,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
? normalizedAlbumArtist
: null;
// For MP3 files, don't save FLAC bitDepth/sampleRate - they're not applicable
final isMp3 = filePath.endsWith('.mp3');
final historyBitDepth = isMp3 ? null : backendBitDepth;
final historySampleRate = isMp3 ? null : backendSampleRate;
+15 -40
View File
@@ -5,7 +5,6 @@ import 'package:spotiflac_android/providers/settings_provider.dart';
final _log = AppLogger('ExtensionProvider');
/// Represents an installed extension
class Extension {
final String id;
final String name;
@@ -14,19 +13,19 @@ class Extension {
final String author;
final String description;
final bool enabled;
final String status; // 'loaded', 'error', 'disabled'
final String status;
final String? errorMessage;
final String? iconPath; // Path to extension icon
final String? iconPath;
final List<String> permissions;
final List<ExtensionSetting> settings;
final List<QualityOption> qualityOptions; // Custom quality options for download providers
final List<QualityOption> qualityOptions;
final bool hasMetadataProvider;
final bool hasDownloadProvider;
final bool skipMetadataEnrichment; // If true, use metadata from extension instead of enriching
final SearchBehavior? searchBehavior; // Custom search behavior
final URLHandler? urlHandler; // Custom URL handling
final TrackMatching? trackMatching; // Custom track matching
final PostProcessing? postProcessing; // Post-processing hooks
final SearchBehavior? searchBehavior;
final URLHandler? urlHandler;
final TrackMatching? trackMatching;
final PostProcessing? postProcessing;
const Extension({
required this.id,
@@ -140,7 +139,6 @@ class Extension {
bool get hasPostProcessing => postProcessing?.enabled ?? false;
}
/// Custom search behavior configuration
class SearchBehavior {
final bool enabled;
final String? placeholder;
@@ -172,8 +170,6 @@ class SearchBehavior {
);
}
/// Get thumbnail size based on configuration
/// Returns (width, height) tuple
(double, double) getThumbnailSize({double defaultSize = 56}) {
if (thumbnailWidth != null && thumbnailHeight != null) {
return (thumbnailWidth!.toDouble(), thumbnailHeight!.toDouble());
@@ -191,11 +187,10 @@ class SearchBehavior {
}
}
/// Custom track matching configuration
class TrackMatching {
final bool customMatching;
final String? strategy; // "isrc", "name", "duration", "custom"
final int durationTolerance; // in seconds
final String? strategy;
final int durationTolerance;
const TrackMatching({
required this.customMatching,
@@ -212,7 +207,6 @@ class TrackMatching {
}
}
/// Post-processing configuration
class PostProcessing {
final bool enabled;
final List<PostProcessingHook> hooks;
@@ -262,7 +256,6 @@ class URLHandler {
}
}
/// A post-processing hook
class PostProcessingHook {
final String id;
final String name;
@@ -289,12 +282,11 @@ class PostProcessingHook {
}
}
/// Represents a quality option for download providers
class QualityOption {
final String id;
final String label;
final String? description;
final List<QualitySpecificSetting> settings; // Quality-specific settings
final List<QualitySpecificSetting> settings;
const QualityOption({
required this.id,
@@ -315,14 +307,13 @@ class QualityOption {
}
}
/// Represents a setting that's specific to a quality option
class QualitySpecificSetting {
final String key;
final String label;
final String type; // 'string', 'number', 'boolean', 'select'
final String type;
final dynamic defaultValue;
final String? description;
final List<String>? options; // For select type
final List<String>? options;
final bool required;
final bool secret;
@@ -351,16 +342,15 @@ class QualitySpecificSetting {
}
}
/// Represents a setting field for an extension
class ExtensionSetting {
final String key;
final String label;
final String type; // 'string', 'number', 'boolean', 'select', 'button'
final String type;
final dynamic defaultValue;
final String? description;
final List<String>? options; // For select type
final List<String>? options;
final bool required;
final String? action; // For button type: JS function name to call
final String? action;
const ExtensionSetting({
required this.key,
@@ -387,7 +377,6 @@ class ExtensionSetting {
}
}
/// State for extension management
class ExtensionState {
final List<Extension> extensions;
final List<String> providerPriority;
@@ -425,7 +414,6 @@ class ExtensionState {
}
/// Provider for managing extensions
class ExtensionNotifier extends Notifier<ExtensionState> {
@override
ExtensionState build() {
@@ -451,7 +439,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Load all extensions from directory
Future<void> loadExtensions(String dirPath) async {
state = state.copyWith(isLoading: true, error: null);
@@ -486,12 +473,10 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Clear any error state
void clearError() {
state = state.copyWith(error: null);
}
/// Install extension from file (auto-upgrades if already installed with newer version)
Future<bool> installExtension(String filePath) async {
state = state.copyWith(isLoading: true, error: null);
@@ -508,8 +493,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Check if a package file is an upgrade for an existing extension
/// Returns: {extension_id, current_version, new_version, can_upgrade, is_installed}
Future<Map<String, dynamic>> checkExtensionUpgrade(String filePath) async {
try {
return await PlatformBridge.checkExtensionUpgrade(filePath);
@@ -519,7 +502,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Upgrade an existing extension from a new package file
Future<bool> upgradeExtension(String filePath) async {
state = state.copyWith(isLoading: true, error: null);
@@ -553,7 +535,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Enable or disable an extension
Future<void> setExtensionEnabled(String extensionId, bool enabled) async {
try {
await PlatformBridge.setExtensionEnabled(extensionId, enabled);
@@ -600,7 +581,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Update settings for an extension
Future<void> setExtensionSettings(String extensionId, Map<String, dynamic> settings) async {
try {
await PlatformBridge.setExtensionSettings(extensionId, settings);
@@ -621,7 +601,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Set provider priority order
Future<void> setProviderPriority(List<String> priority) async {
try {
await PlatformBridge.setProviderPriority(priority);
@@ -643,7 +622,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Set metadata provider priority order
Future<void> setMetadataProviderPriority(List<String> priority) async {
try {
await PlatformBridge.setMetadataProviderPriority(priority);
@@ -665,7 +643,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Get extension by ID
Extension? getExtension(String extensionId) {
try {
return state.extensions.firstWhere((ext) => ext.id == extensionId);
@@ -679,7 +656,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
return state.extensions.where((ext) => ext.enabled).toList();
}
/// Get all download providers (built-in + extensions)
List<String> getAllDownloadProviders() {
final providers = ['tidal', 'qobuz', 'amazon'];
for (final ext in state.extensions) {
@@ -700,7 +676,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
return providers;
}
/// Get all extensions that provide custom search
List<Extension> get searchProviders {
return state.extensions.where((ext) => ext.enabled && ext.hasCustomSearch).toList();
}
@@ -121,7 +121,6 @@ class RecentAccessNotifier extends Notifier<RecentAccessState> {
.map((e) => RecentAccessItem.fromJson(e as Map<String, dynamic>))
.toList();
} catch (e) {
// Ignore parse errors
}
}
@@ -266,7 +265,6 @@ class RecentAccessNotifier extends Notifier<RecentAccessState> {
}
}
/// Provider instance
final recentAccessProvider = NotifierProvider<RecentAccessNotifier, RecentAccessState>(
RecentAccessNotifier.new,
);
-3
View File
@@ -30,7 +30,6 @@ class SettingsNotifier extends Notifier<AppSettings> {
}
}
/// Run one-time migrations for settings
Future<void> _runMigrations(SharedPreferences prefs) async {
final lastMigration = prefs.getInt(_migrationVersionKey) ?? 0;
@@ -51,7 +50,6 @@ class SettingsNotifier extends Notifier<AppSettings> {
await prefs.setString(_settingsKey, jsonEncode(state.toJson()));
}
/// Apply current Spotify credentials to Go backend
Future<void> _applySpotifyCredentials() async {
if (state.spotifyClientId.isNotEmpty &&
state.spotifyClientSecret.isNotEmpty) {
@@ -93,7 +91,6 @@ class SettingsNotifier extends Notifier<AppSettings> {
}
void setLyricsMode(String mode) {
// Valid modes: embed, external, both
if (mode == 'embed' || mode == 'external' || mode == 'both') {
state = state.copyWith(lyricsMode: mode);
_saveSettings();
-7
View File
@@ -52,7 +52,6 @@ class StoreCategory {
}
}
/// Represents an extension in the store
class StoreExtension {
final String id;
final String name;
@@ -118,7 +117,6 @@ class StoreExtension {
}
}
/// State for extension store
class StoreState {
final List<StoreExtension> extensions;
final String? selectedCategory;
@@ -200,7 +198,6 @@ class StoreNotifier extends Notifier<StoreState> {
return const StoreState();
}
/// Initialize the store
Future<void> initialize(String cacheDir) async {
if (state.isInitialized) return;
@@ -234,7 +231,6 @@ class StoreNotifier extends Notifier<StoreState> {
}
}
/// Set category filter
void setCategory(String? category) {
if (category == null) {
state = state.copyWith(clearCategory: true);
@@ -248,7 +244,6 @@ class StoreNotifier extends Notifier<StoreState> {
state = state.copyWith(searchQuery: query);
}
/// Clear search
void clearSearch() {
state = state.copyWith(searchQuery: '', clearCategory: true);
}
@@ -279,7 +274,6 @@ class StoreNotifier extends Notifier<StoreState> {
}
}
/// Update an installed extension
Future<bool> updateExtension(String extensionId, String tempDir) async {
state = state.copyWith(isDownloading: true, downloadingId: extensionId, clearError: true);
@@ -305,7 +299,6 @@ class StoreNotifier extends Notifier<StoreState> {
}
}
/// Clear error
void clearError() {
state = state.copyWith(clearError: true);
}
-1
View File
@@ -34,7 +34,6 @@ class ThemeNotifier extends Notifier<ThemeSettings> {
);
} catch (e) {
debugPrint('Error loading theme settings: $e');
// Keep default state on error
}
}
+2 -17
View File
@@ -89,7 +89,6 @@ class TrackState {
}
}
/// Represents an album in artist discography
class ArtistAlbum {
final String id;
final String name;
@@ -112,7 +111,6 @@ class ArtistAlbum {
});
}
/// Represents an artist in search results
class SearchArtist {
final String id;
final String name;
@@ -130,7 +128,6 @@ class SearchArtist {
}
class TrackNotifier extends Notifier<TrackState> {
/// Request ID to track and cancel outdated requests
int _currentRequestId = 0;
@override
@@ -213,14 +210,8 @@ class TrackNotifier extends Notifier<TrackState> {
Map<String, dynamic> metadata;
try {
// ignore: avoid_print
print('[FetchURL] Fetching $type with Deezer fallback enabled...');
metadata = await PlatformBridge.getSpotifyMetadataWithFallback(url);
// ignore: avoid_print
print('[FetchURL] Metadata fetch success');
} catch (e) {
// ignore: avoid_print
print('[FetchURL] Metadata fetch failed: $e');
rethrow;
}
@@ -263,7 +254,7 @@ class TrackNotifier extends Notifier<TrackState> {
final albumsList = metadata['albums'] as List<dynamic>;
final albums = albumsList.map((a) => _parseArtistAlbum(a as Map<String, dynamic>)).toList();
state = TrackState(
tracks: [], // No tracks for artist view
tracks: [],
isLoading: false,
artistId: artistInfo['id'] as String?,
artistName: artistInfo['name'] as String?,
@@ -397,7 +388,6 @@ class TrackNotifier extends Notifier<TrackState> {
}
}
/// Perform custom search using a specific extension
Future<void> customSearch(String extensionId, String query, {Map<String, dynamic>? options}) async {
final requestId = ++_currentRequestId;
@@ -429,7 +419,7 @@ class TrackNotifier extends Notifier<TrackState> {
state = TrackState(
tracks: tracks,
searchArtists: [], // Custom search doesn't return artists
searchArtists: [],
isLoading: false,
hasSearchText: state.hasSearchText,
searchExtensionId: extensionId, // Store which extension was used
@@ -477,8 +467,6 @@ class TrackNotifier extends Notifier<TrackState> {
tracks[index] = updatedTrack;
state = state.copyWith(tracks: tracks);
} catch (e) {
// Silently ignore availability check errors
// This is a background operation that shouldn't disrupt the user
}
}
@@ -494,7 +482,6 @@ class TrackNotifier extends Notifier<TrackState> {
state = state.copyWith(hasSearchText: hasText);
}
/// Set recent access mode state
void setShowingRecentAccess(bool showing) {
state = state.copyWith(isShowingRecentAccess: showing);
}
@@ -584,8 +571,6 @@ class TrackNotifier extends Notifier<TrackState> {
);
}
/// Pre-warm track ID cache for faster downloads
/// Runs in background, doesn't block UI
void _preWarmCacheForTracks(List<Track> tracks) {
final tracksWithIsrc = tracks.where((t) => t.isrc != null && t.isrc!.isNotEmpty).toList();
if (tracksWithIsrc.isEmpty) return;