Skip to content

Commit abc599d

Browse files
committed
refactor: migrate queue_tab cover resolver to shared service, add supporter
1 parent 9b27e86 commit abc599d

File tree

3 files changed

+54
-226
lines changed

3 files changed

+54
-226
lines changed

lib/screens/queue_tab.dart

Lines changed: 24 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'package:spotiflac_android/providers/download_queue_provider.dart';
1414
import 'package:spotiflac_android/providers/settings_provider.dart';
1515
import 'package:spotiflac_android/providers/local_library_provider.dart';
1616
import 'package:spotiflac_android/services/library_database.dart';
17-
import 'package:spotiflac_android/services/platform_bridge.dart';
17+
import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart';
1818
import 'package:spotiflac_android/screens/track_metadata_screen.dart';
1919
import 'package:spotiflac_android/screens/downloaded_album_screen.dart';
2020
import 'package:spotiflac_android/screens/local_album_screen.dart';
@@ -279,7 +279,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
279279
final Set<String> _pendingChecks = {};
280280
static const int _maxCacheSize = 500;
281281
static const int _maxSearchIndexCacheSize = 4000;
282-
static const int _maxDownloadedEmbeddedCoverCacheSize = 180;
282+
bool _embeddedCoverRefreshScheduled = false;
283283

284284
bool _isSelectionMode = false;
285285
final Set<String> _selectedIds = {};
@@ -311,10 +311,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
311311
_HistoryStats? _historyStatsCache;
312312
final Map<String, String> _searchIndexCache = {};
313313
final Map<String, String> _localSearchIndexCache = {};
314-
final Map<String, String> _downloadedEmbeddedCoverCache = {};
315-
final Set<String> _pendingDownloadedCoverExtract = {};
316-
final Set<String> _pendingDownloadedCoverRefresh = {};
317-
final Set<String> _failedDownloadedCoverExtract = {};
318314
Map<String, List<DownloadHistoryItem>> _filteredHistoryCache = const {};
319315
List<DownloadHistoryItem>? _filterItemsCache;
320316
String _filterQueryCache = '';
@@ -361,13 +357,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
361357

362358
@override
363359
void dispose() {
364-
for (final coverPath in _downloadedEmbeddedCoverCache.values) {
365-
_cleanupTempCoverPathSync(coverPath);
366-
}
367-
_downloadedEmbeddedCoverCache.clear();
368-
_pendingDownloadedCoverExtract.clear();
369-
_pendingDownloadedCoverRefresh.clear();
370-
_failedDownloadedCoverExtract.clear();
371360
for (final notifier in _fileExistsNotifiers.values) {
372361
notifier.dispose();
373362
}
@@ -425,12 +414,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
425414
.map((item) => _cleanFilePath(item.filePath))
426415
.where((path) => path.isNotEmpty)
427416
.toSet();
428-
final staleKeys = _downloadedEmbeddedCoverCache.keys
429-
.where((path) => !validPaths.contains(path))
430-
.toList(growable: false);
431-
for (final key in staleKeys) {
432-
_invalidateDownloadedEmbeddedCover(key);
433-
}
417+
DownloadedEmbeddedCoverResolver.invalidatePathsNotIn(validPaths);
434418
}
435419
_requestFilterRefresh();
436420
}
@@ -794,168 +778,42 @@ class _QueueTabState extends ConsumerState<QueueTab> {
794778

795779
/// Strip EXISTS: prefix from file path (legacy history items)
796780
String _cleanFilePath(String? filePath) {
797-
if (filePath == null) return '';
798-
if (filePath.startsWith('EXISTS:')) {
799-
return filePath.substring(7);
800-
}
801-
return filePath;
802-
}
803-
804-
void _cleanupTempCoverPathSync(String? coverPath) {
805-
if (coverPath == null || coverPath.isEmpty) return;
806-
try {
807-
final file = File(coverPath);
808-
if (file.existsSync()) {
809-
file.deleteSync();
810-
}
811-
final parent = file.parent;
812-
if (parent.existsSync()) {
813-
parent.deleteSync(recursive: true);
814-
}
815-
} catch (_) {}
816-
}
817-
818-
void _invalidateDownloadedEmbeddedCover(String? filePath) {
819-
final cleanPath = _cleanFilePath(filePath);
820-
if (cleanPath.isEmpty) return;
821-
822-
final cachedPath = _downloadedEmbeddedCoverCache.remove(cleanPath);
823-
_pendingDownloadedCoverExtract.remove(cleanPath);
824-
_pendingDownloadedCoverRefresh.remove(cleanPath);
825-
_failedDownloadedCoverExtract.remove(cleanPath);
826-
_cleanupTempCoverPathSync(cachedPath);
781+
return DownloadedEmbeddedCoverResolver.cleanFilePath(filePath);
827782
}
828783

829-
void _trimDownloadedEmbeddedCoverCache() {
830-
while (_downloadedEmbeddedCoverCache.length >
831-
_maxDownloadedEmbeddedCoverCacheSize) {
832-
final oldestKey = _downloadedEmbeddedCoverCache.keys.first;
833-
final removedPath = _downloadedEmbeddedCoverCache.remove(oldestKey);
834-
_pendingDownloadedCoverExtract.remove(oldestKey);
835-
_pendingDownloadedCoverRefresh.remove(oldestKey);
836-
_failedDownloadedCoverExtract.remove(oldestKey);
837-
_cleanupTempCoverPathSync(removedPath);
838-
}
784+
Future<int?> _readFileModTimeMillis(String? filePath) async {
785+
return DownloadedEmbeddedCoverResolver.readFileModTimeMillis(filePath);
839786
}
840787

841-
Future<int?> _readFileModTimeMillis(String? filePath) async {
842-
final cleanPath = _cleanFilePath(filePath);
843-
if (cleanPath.isEmpty) return null;
844-
845-
if (cleanPath.startsWith('content://')) {
846-
try {
847-
final modTimes = await PlatformBridge.getSafFileModTimes([cleanPath]);
848-
return modTimes[cleanPath];
849-
} catch (_) {
850-
return null;
788+
void _onEmbeddedCoverChanged() {
789+
if (!mounted || _embeddedCoverRefreshScheduled) return;
790+
_embeddedCoverRefreshScheduled = true;
791+
WidgetsBinding.instance.addPostFrameCallback((_) {
792+
_embeddedCoverRefreshScheduled = false;
793+
if (mounted) {
794+
setState(() {});
851795
}
852-
}
853-
854-
try {
855-
final stat = await File(cleanPath).stat();
856-
return stat.modified.millisecondsSinceEpoch;
857-
} catch (_) {
858-
return null;
859-
}
796+
});
860797
}
861798

862799
Future<void> _scheduleDownloadedEmbeddedCoverRefreshForPath(
863800
String? filePath, {
864801
int? beforeModTime,
865802
bool force = false,
866803
}) async {
867-
final cleanPath = _cleanFilePath(filePath);
868-
if (cleanPath.isEmpty) return;
869-
870-
if (!force) {
871-
if (beforeModTime == null) {
872-
return;
873-
}
874-
final afterModTime = await _readFileModTimeMillis(cleanPath);
875-
if (afterModTime != null && afterModTime == beforeModTime) {
876-
return;
877-
}
878-
}
879-
880-
_pendingDownloadedCoverRefresh.add(cleanPath);
881-
_failedDownloadedCoverExtract.remove(cleanPath);
882-
if (mounted) {
883-
setState(() {});
884-
}
804+
await DownloadedEmbeddedCoverResolver.scheduleRefreshForPath(
805+
filePath,
806+
beforeModTime: beforeModTime,
807+
force: force,
808+
onChanged: _onEmbeddedCoverChanged,
809+
);
885810
}
886811

887812
String? _resolveDownloadedEmbeddedCoverPath(String? filePath) {
888-
final cleanPath = _cleanFilePath(filePath);
889-
if (cleanPath.isEmpty) return null;
890-
891-
if (_pendingDownloadedCoverRefresh.remove(cleanPath)) {
892-
_ensureDownloadedEmbeddedCover(cleanPath, forceRefresh: true);
893-
}
894-
895-
final cachedPath = _downloadedEmbeddedCoverCache[cleanPath];
896-
if (cachedPath != null) {
897-
if (File(cachedPath).existsSync()) {
898-
return cachedPath;
899-
}
900-
_downloadedEmbeddedCoverCache.remove(cleanPath);
901-
_cleanupTempCoverPathSync(cachedPath);
902-
}
903-
904-
return null;
905-
}
906-
907-
void _ensureDownloadedEmbeddedCover(
908-
String cleanPath, {
909-
bool forceRefresh = false,
910-
}) {
911-
if (cleanPath.isEmpty) return;
912-
if (_pendingDownloadedCoverExtract.contains(cleanPath)) return;
913-
if (!forceRefresh && _downloadedEmbeddedCoverCache.containsKey(cleanPath)) {
914-
return;
915-
}
916-
if (!forceRefresh && _failedDownloadedCoverExtract.contains(cleanPath)) {
917-
return;
918-
}
919-
920-
_pendingDownloadedCoverExtract.add(cleanPath);
921-
Future.microtask(() async {
922-
String? outputPath;
923-
try {
924-
final tempDir = await Directory.systemTemp.createTemp('library_cover_');
925-
outputPath = '${tempDir.path}${Platform.pathSeparator}cover.jpg';
926-
final result = await PlatformBridge.extractCoverToFile(
927-
cleanPath,
928-
outputPath,
929-
);
930-
931-
final hasCover =
932-
result['error'] == null && await File(outputPath).exists();
933-
if (!hasCover) {
934-
_failedDownloadedCoverExtract.add(cleanPath);
935-
_cleanupTempCoverPathSync(outputPath);
936-
return;
937-
}
938-
939-
if (!mounted) {
940-
_cleanupTempCoverPathSync(outputPath);
941-
return;
942-
}
943-
944-
final previous = _downloadedEmbeddedCoverCache[cleanPath];
945-
_downloadedEmbeddedCoverCache[cleanPath] = outputPath;
946-
_failedDownloadedCoverExtract.remove(cleanPath);
947-
_trimDownloadedEmbeddedCoverCache();
948-
if (previous != null && previous != outputPath) {
949-
_cleanupTempCoverPathSync(previous);
950-
}
951-
setState(() {});
952-
} catch (_) {
953-
_failedDownloadedCoverExtract.add(cleanPath);
954-
_cleanupTempCoverPathSync(outputPath);
955-
} finally {
956-
_pendingDownloadedCoverExtract.remove(cleanPath);
957-
}
958-
});
813+
return DownloadedEmbeddedCoverResolver.resolve(
814+
filePath,
815+
onChanged: _onEmbeddedCoverChanged,
816+
);
959817
}
960818

961819
ValueListenable<bool> _fileExistsListenable(String? filePath) {

lib/screens/settings/donate_page.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,9 @@ class _RecentDonorsCard extends StatelessWidget {
204204
_DonorTile(name: 'Julian', colorScheme: colorScheme),
205205
_DonorTile(name: 'matt_3050', colorScheme: colorScheme),
206206
_DonorTile(name: 'Daniel', colorScheme: colorScheme),
207+
_DonorTile(name: '283Fabio', colorScheme: colorScheme),
207208
_DonorTile(
208-
name: '283Fabio',
209+
name: 'Elias el Autentico',
209210
colorScheme: colorScheme,
210211
showDivider: false,
211212
),

0 commit comments

Comments
 (0)