mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-09 14:18:11 +02:00
refactor: remove more redundant comments
This commit is contained in:
@@ -1019,10 +1019,6 @@ void removeItem(String id) {
|
||||
_saveQueueToStorage();
|
||||
}
|
||||
|
||||
/// Export failed downloads to a TXT file
|
||||
/// Returns the file path if successful, null otherwise
|
||||
/// Uses daily files: same day = append to existing file, new day = new file
|
||||
/// Saves to 'failed_downloads' subfolder to keep organized
|
||||
Future<String?> exportFailedDownloads() async {
|
||||
final failedItems = state.items
|
||||
.where((item) => item.status == DownloadStatus.failed)
|
||||
@@ -1091,7 +1087,6 @@ void removeItem(String id) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all failed downloads from queue
|
||||
void clearFailedDownloads() {
|
||||
final items = state.items
|
||||
.where((item) => item.status != DownloadStatus.failed)
|
||||
|
||||
@@ -41,25 +41,20 @@ class LocalLibraryState {
|
||||
.map((item) => MapEntry(item.isrc!, item)),
|
||||
);
|
||||
|
||||
/// Check if ISRC exists in library
|
||||
bool hasIsrc(String isrc) => _isrcSet.contains(isrc);
|
||||
|
||||
/// Check if track exists by name and artist
|
||||
bool hasTrack(String trackName, String artistName) {
|
||||
final key = '${trackName.toLowerCase()}|${artistName.toLowerCase()}';
|
||||
return _trackKeySet.contains(key);
|
||||
}
|
||||
|
||||
/// Find library item by ISRC
|
||||
LocalLibraryItem? getByIsrc(String isrc) => _byIsrc[isrc];
|
||||
|
||||
/// Find library item by track name and artist
|
||||
LocalLibraryItem? findByTrackAndArtist(String trackName, String artistName) {
|
||||
final key = '${trackName.toLowerCase()}|${artistName.toLowerCase()}';
|
||||
return items.where((item) => item.matchKey == key).firstOrNull;
|
||||
}
|
||||
|
||||
/// Check if a track exists in library (by ISRC or name matching)
|
||||
bool existsInLibrary({String? isrc, String? trackName, String? artistName}) {
|
||||
if (isrc != null && isrc.isNotEmpty && hasIsrc(isrc)) {
|
||||
return true;
|
||||
@@ -136,13 +131,11 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reload library from database
|
||||
Future<void> reloadFromStorage() async {
|
||||
_isLoaded = false;
|
||||
await _loadFromDatabase();
|
||||
}
|
||||
|
||||
/// Start scanning a folder for audio files
|
||||
Future<void> startScan(String folderPath) async {
|
||||
if (state.isScanning) {
|
||||
_log.w('Scan already in progress');
|
||||
@@ -230,7 +223,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
_progressTimer = null;
|
||||
}
|
||||
|
||||
/// Cancel ongoing scan
|
||||
Future<void> cancelScan() async {
|
||||
if (!state.isScanning) return;
|
||||
|
||||
@@ -240,7 +232,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
_stopProgressPolling();
|
||||
}
|
||||
|
||||
/// Clean up missing files from library
|
||||
Future<int> cleanupMissingFiles() async {
|
||||
final removed = await _db.cleanupMissingFiles();
|
||||
if (removed > 0) {
|
||||
@@ -249,7 +240,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// Clear all library data
|
||||
Future<void> clearLibrary() async {
|
||||
await _db.clearAll();
|
||||
|
||||
@@ -264,7 +254,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
_log.i('Library cleared');
|
||||
}
|
||||
|
||||
/// Remove a single item from library by ID
|
||||
Future<void> removeItem(String id) async {
|
||||
await _db.delete(id);
|
||||
state = state.copyWith(
|
||||
@@ -272,7 +261,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Check if a track exists in library
|
||||
bool existsInLibrary({String? isrc, String? trackName, String? artistName}) {
|
||||
return state.existsInLibrary(
|
||||
isrc: isrc,
|
||||
@@ -281,12 +269,10 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Get library item by ISRC
|
||||
LocalLibraryItem? getByIsrc(String isrc) {
|
||||
return state.getByIsrc(isrc);
|
||||
}
|
||||
|
||||
/// Find library item for a track
|
||||
LocalLibraryItem? findExisting({String? isrc, String? trackName, String? artistName}) {
|
||||
if (isrc != null && isrc.isNotEmpty) {
|
||||
final byIsrc = state.getByIsrc(isrc);
|
||||
@@ -298,7 +284,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Search library
|
||||
Future<List<LocalLibraryItem>> search(String query) async {
|
||||
if (query.isEmpty) return [];
|
||||
|
||||
@@ -306,7 +291,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
return results.map((e) => LocalLibraryItem.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
/// Get library count
|
||||
Future<int> getCount() async {
|
||||
return await _db.getCount();
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ class AlbumScreen extends ConsumerStatefulWidget {
|
||||
final String albumId;
|
||||
final String albumName;
|
||||
final String? coverUrl;
|
||||
final List<Track>? tracks; // Optional - will fetch if null
|
||||
final String? extensionId; // If from extension
|
||||
final String? artistId; // Artist ID for navigation
|
||||
final String? artistName; // Artist name for navigation
|
||||
final List<Track>? tracks;
|
||||
final String? extensionId;
|
||||
final String? artistId;
|
||||
final String? artistName;
|
||||
|
||||
const AlbumScreen({
|
||||
super.key,
|
||||
@@ -93,13 +93,12 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
|
||||
);
|
||||
});
|
||||
|
||||
// Use provided tracks if not empty, otherwise try cache
|
||||
if (widget.tracks != null && widget.tracks!.isNotEmpty) {
|
||||
_tracks = widget.tracks;
|
||||
} else {
|
||||
_tracks = _AlbumCache.get(widget.albumId);
|
||||
}
|
||||
_artistId = widget.artistId; // Use provided artist ID if available
|
||||
_artistId = widget.artistId;
|
||||
|
||||
if (_tracks == null || _tracks!.isEmpty) {
|
||||
_fetchTracks();
|
||||
@@ -122,7 +121,7 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _extractDominantColor() async {
|
||||
Future<void> _extractDominantColor() async {
|
||||
if (widget.coverUrl == null) return;
|
||||
final color = await PaletteService.instance.extractDominantColor(widget.coverUrl);
|
||||
if (mounted && color != null) {
|
||||
@@ -131,21 +130,18 @@ Future<void> _extractDominantColor() async {
|
||||
}
|
||||
|
||||
String _formatReleaseDate(String date) {
|
||||
// Handle formats: "2024-01-15", "2024-01", "2024"
|
||||
if (date.length >= 10) {
|
||||
// Full date: 2024-01-15
|
||||
final parts = date.substring(0, 10).split('-');
|
||||
if (parts.length == 3) {
|
||||
return '${parts[2]}/${parts[1]}/${parts[0]}'; // DD/MM/YYYY
|
||||
return '${parts[2]}/${parts[1]}/${parts[0]}';
|
||||
}
|
||||
} else if (date.length >= 7) {
|
||||
// Month: 2024-01
|
||||
final parts = date.split('-');
|
||||
if (parts.length >= 2) {
|
||||
return '${parts[1]}/${parts[0]}'; // MM/YYYY
|
||||
return '${parts[1]}/${parts[0]}';
|
||||
}
|
||||
}
|
||||
return date; // Year only or unknown format
|
||||
return date;
|
||||
}
|
||||
|
||||
Future<void> _fetchTracks() async {
|
||||
@@ -164,7 +160,6 @@ Future<void> _fetchTracks() async {
|
||||
final trackList = metadata['track_list'] as List<dynamic>;
|
||||
final tracks = trackList.map((t) => _parseTrack(t as Map<String, dynamic>)).toList();
|
||||
|
||||
// Extract artist ID from album_info if available
|
||||
final albumInfo = metadata['album_info'] as Map<String, dynamic>?;
|
||||
final artistId = albumInfo?['artist_id'] as String?;
|
||||
|
||||
@@ -236,7 +231,7 @@ Future<void> _fetchTracks() async {
|
||||
|
||||
Widget _buildAppBar(BuildContext context, ColorScheme colorScheme) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final coverSize = screenWidth * 0.5; // 50% of screen width
|
||||
final coverSize = screenWidth * 0.5;
|
||||
final bgColor = _dominantColor ?? colorScheme.surface;
|
||||
|
||||
return SliverAppBar(
|
||||
@@ -269,7 +264,6 @@ Future<void> _fetchTracks() async {
|
||||
background: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
// Background with dominant color
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
decoration: BoxDecoration(
|
||||
@@ -501,11 +495,9 @@ ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(context.l10n.s
|
||||
}
|
||||
|
||||
void _navigateToArtist(BuildContext context, String artistName) {
|
||||
// Use stored artist ID if available, otherwise use a placeholder
|
||||
final artistId = _artistId ??
|
||||
(widget.albumId.startsWith('deezer:') ? 'deezer:unknown' : 'unknown');
|
||||
|
||||
// Don't navigate if artist ID is unknown
|
||||
if (artistId == 'unknown' || artistId == 'deezer:unknown' || artistId.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Artist information not available')),
|
||||
@@ -513,7 +505,6 @@ ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(context.l10n.s
|
||||
return;
|
||||
}
|
||||
|
||||
// If from extension, use ExtensionArtistScreen
|
||||
if (widget.extensionId != null) {
|
||||
Navigator.push(
|
||||
context,
|
||||
@@ -621,7 +612,6 @@ class _AlbumTrackItem extends ConsumerWidget {
|
||||
return state.isDownloaded(track.id);
|
||||
}));
|
||||
|
||||
// Check local library for duplicate detection
|
||||
final settings = ref.watch(settingsProvider);
|
||||
final showLocalLibraryIndicator = settings.localLibraryEnabled && settings.localLibraryShowDuplicates;
|
||||
final isInLocalLibrary = showLocalLibraryIndicator
|
||||
|
||||
@@ -74,7 +74,7 @@ class ArtistScreen extends ConsumerStatefulWidget {
|
||||
final int? monthlyListeners;
|
||||
final List<ArtistAlbum>? albums;
|
||||
final List<Track>? topTracks;
|
||||
final String? extensionId; // If set, skip fetching from Spotify/Deezer
|
||||
final String? extensionId;
|
||||
|
||||
const ArtistScreen({
|
||||
super.key,
|
||||
|
||||
@@ -50,19 +50,10 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
|
||||
late final ProviderSubscription<TrackState> _trackStateSub;
|
||||
late final ProviderSubscription<bool> _extensionInitSub;
|
||||
|
||||
/// Debounce timer for live search (extension-only feature)
|
||||
Timer? _liveSearchDebounce;
|
||||
|
||||
/// Flag to prevent concurrent live search calls (prevents race conditions in extensions)
|
||||
bool _isLiveSearchInProgress = false;
|
||||
|
||||
/// Pending query to execute after current search completes
|
||||
String? _pendingLiveSearchQuery;
|
||||
|
||||
/// Minimum characters required to trigger live search
|
||||
static const int _minLiveSearchChars = 3;
|
||||
|
||||
/// Debounce duration for live search
|
||||
static const Duration _liveSearchDelay = Duration(milliseconds: 800);
|
||||
|
||||
List<DownloadHistoryItem>? _recentAccessHistoryCache;
|
||||
|
||||
@@ -17,7 +17,7 @@ class PlaylistScreen extends ConsumerStatefulWidget {
|
||||
final String playlistName;
|
||||
final String? coverUrl;
|
||||
final List<Track> tracks;
|
||||
final String? playlistId; // Deezer playlist ID for fetching tracks
|
||||
final String? playlistId;
|
||||
|
||||
const PlaylistScreen({
|
||||
super.key,
|
||||
|
||||
@@ -567,7 +567,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Exit selection mode
|
||||
void _exitSelectionMode() {
|
||||
setState(() {
|
||||
_isSelectionMode = false;
|
||||
@@ -588,25 +587,20 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Select all visible items
|
||||
void _selectAll(List<UnifiedLibraryItem> items) {
|
||||
setState(() {
|
||||
_selectedIds.addAll(items.map((e) => e.id));
|
||||
});
|
||||
}
|
||||
|
||||
/// Get short badge text for quality display
|
||||
String _getQualityBadgeText(String quality) {
|
||||
// For lossless: "24-bit/96kHz" -> "24-bit"
|
||||
if (quality.contains('bit')) {
|
||||
return quality.split('/').first;
|
||||
}
|
||||
// For lossy: "OPUS 128kbps" -> "128k", "MP3 320kbps" -> "320k"
|
||||
final bitrateMatch = RegExp(r'(\d+)kbps').firstMatch(quality);
|
||||
if (bitrateMatch != null) {
|
||||
return '${bitrateMatch.group(1)}k';
|
||||
}
|
||||
// Fallback: return format name
|
||||
return quality.split(' ').first;
|
||||
}
|
||||
|
||||
@@ -725,7 +719,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Count of active advanced filters
|
||||
int get _activeFilterCount {
|
||||
int count = 0;
|
||||
if (_filterSource != null) count++;
|
||||
@@ -735,7 +728,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
return count;
|
||||
}
|
||||
|
||||
/// Reset all advanced filters
|
||||
void _resetFilters() {
|
||||
setState(() {
|
||||
_filterSource = null;
|
||||
@@ -746,12 +738,10 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Apply advanced filters to unified items
|
||||
List<UnifiedLibraryItem> _applyAdvancedFilters(List<UnifiedLibraryItem> items) {
|
||||
if (_activeFilterCount == 0) return items;
|
||||
|
||||
return items.where((item) {
|
||||
// Source filter
|
||||
if (_filterSource != null) {
|
||||
if (_filterSource == 'downloaded' && item.source != LibraryItemSource.downloaded) {
|
||||
return false;
|
||||
@@ -761,7 +751,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
}
|
||||
}
|
||||
|
||||
// Quality filter
|
||||
if (_filterQuality != null && item.quality != null) {
|
||||
final quality = item.quality!.toLowerCase();
|
||||
switch (_filterQuality) {
|
||||
@@ -770,21 +759,17 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
case 'cd':
|
||||
if (!quality.startsWith('16')) return false;
|
||||
case 'lossy':
|
||||
// Lossy formats typically don't have bit depth or are labeled differently
|
||||
if (quality.startsWith('24') || quality.startsWith('16')) return false;
|
||||
}
|
||||
} else if (_filterQuality != null && item.quality == null) {
|
||||
// If quality filter is set but item has no quality info, include only for 'lossy'
|
||||
if (_filterQuality != 'lossy') return false;
|
||||
}
|
||||
|
||||
// Format filter
|
||||
if (_filterFormat != null) {
|
||||
final ext = item.filePath.split('.').last.toLowerCase();
|
||||
if (ext != _filterFormat) return false;
|
||||
}
|
||||
|
||||
// Date filter
|
||||
if (_filterDateRange != null) {
|
||||
final now = DateTime.now();
|
||||
final itemDate = item.addedAt;
|
||||
@@ -808,7 +793,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
}).toList(growable: false);
|
||||
}
|
||||
|
||||
/// Get available formats from current items
|
||||
Set<String> _getAvailableFormats(List<UnifiedLibraryItem> items) {
|
||||
final formats = <String>{};
|
||||
for (final item in items) {
|
||||
@@ -820,12 +804,10 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
return formats;
|
||||
}
|
||||
|
||||
/// Show filter bottom sheet
|
||||
void _showFilterSheet(BuildContext context, List<UnifiedLibraryItem> allItems) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final availableFormats = _getAvailableFormats(allItems);
|
||||
|
||||
// Temporary filter state for the sheet
|
||||
String? tempSource = _filterSource;
|
||||
String? tempQuality = _filterQuality;
|
||||
String? tempFormat = _filterFormat;
|
||||
@@ -847,7 +829,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Handle bar
|
||||
Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
@@ -860,7 +841,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
),
|
||||
|
||||
// Title row
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
@@ -885,7 +865,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Source filter
|
||||
Text(
|
||||
context.l10n.libraryFilterSource,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
@@ -915,7 +894,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Quality filter
|
||||
Text(
|
||||
context.l10n.libraryFilterQuality,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
@@ -950,7 +928,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Format filter
|
||||
Text(
|
||||
context.l10n.libraryFilterFormat,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
@@ -976,7 +953,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Date filter
|
||||
Text(
|
||||
context.l10n.libraryFilterDate,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
@@ -1016,7 +992,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Apply button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton(
|
||||
|
||||
@@ -27,7 +27,6 @@ class CoverCacheManager {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
/// Check if cache manager is initialized
|
||||
static bool get isInitialized => _initialized && _instance != null;
|
||||
|
||||
static Future<void> initialize() async {
|
||||
@@ -62,8 +61,6 @@ class CoverCacheManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all cached cover images.
|
||||
/// Returns the number of files deleted.
|
||||
static Future<void> clearCache() async {
|
||||
if (!_initialized || _instance == null) return;
|
||||
await _instance!.emptyCache();
|
||||
@@ -98,7 +95,6 @@ class CoverCacheManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistics about the cover image cache
|
||||
class CacheStats {
|
||||
final int fileCount;
|
||||
final int totalSizeBytes;
|
||||
|
||||
@@ -154,7 +154,6 @@ class LogBuffer extends ChangeNotifier {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/// Export logs with device information for debugging
|
||||
Future<String> exportWithDeviceInfo() async {
|
||||
final buffer = StringBuffer();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user