refactor: consolidate lyrics builders, cancel registries, and priority plumbing

This commit is contained in:
zarzet
2026-07-26 19:08:22 +07:00
parent 8d077ae2da
commit 74615a60b5
14 changed files with 235 additions and 460 deletions
+52 -75
View File
@@ -1427,38 +1427,50 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
/// Shared load path for the download/metadata priority lists: prefs first
/// (sanitized), falling back to backend defaults, then persist + push the
/// result back to the backend.
Future<List<String>> _loadPriorityList({
required String prefsKey,
required String label,
required List<String> Function(List<String>) sanitizeStored,
required List<String> Function(List<String>) sanitizeBackend,
required Future<List<String>> Function() fetchBackend,
required Future<void> Function(List<String>) pushBackend,
}) async {
final prefs = await SharedPreferences.getInstance();
final savedJson = prefs.getString(prefsKey);
List<String> priority;
if (savedJson != null) {
final saved = _tryDecodeStringListPreference(savedJson, prefsKey);
if (saved != null) {
priority = sanitizeStored(saved);
_log.d('Loaded $label from prefs: $priority');
} else {
await prefs.remove(prefsKey);
priority = sanitizeBackend(await fetchBackend());
_log.d('Recovered $label from defaults: $priority');
}
} else {
priority = sanitizeBackend(await fetchBackend());
_log.d('Using default $label: $priority');
}
await prefs.setString(prefsKey, jsonEncode(priority));
await pushBackend(priority);
return priority;
}
Future<void> loadProviderPriority() async {
try {
final prefs = await SharedPreferences.getInstance();
final savedJson = prefs.getString(_providerPriorityKey);
List<String> priority;
if (savedJson != null) {
final saved = _tryDecodeStringListPreference(
savedJson,
_providerPriorityKey,
);
if (saved != null) {
priority = _sanitizeDownloadProviderPriority(saved);
_log.d('Loaded provider priority from prefs: $priority');
await prefs.setString(_providerPriorityKey, jsonEncode(priority));
await PlatformBridge.setProviderPriority(priority);
} else {
await prefs.remove(_providerPriorityKey);
priority = await PlatformBridge.getProviderPriority();
priority = _sanitizeDownloadProviderPriority(priority);
await prefs.setString(_providerPriorityKey, jsonEncode(priority));
await PlatformBridge.setProviderPriority(priority);
_log.d('Recovered provider priority from defaults: $priority');
}
} else {
priority = await PlatformBridge.getProviderPriority();
priority = _sanitizeDownloadProviderPriority(priority);
await prefs.setString(_providerPriorityKey, jsonEncode(priority));
await PlatformBridge.setProviderPriority(priority);
_log.d('Using default provider priority: $priority');
}
final priority = await _loadPriorityList(
prefsKey: _providerPriorityKey,
label: 'provider priority',
sanitizeStored: _sanitizeDownloadProviderPriority,
sanitizeBackend: _sanitizeDownloadProviderPriority,
fetchBackend: PlatformBridge.getProviderPriority,
pushBackend: PlatformBridge.setProviderPriority,
);
state = state.copyWith(providerPriority: priority);
} catch (e) {
_log.e('Failed to load provider priority: $e');
@@ -1502,51 +1514,16 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
Future<void> loadMetadataProviderPriority() async {
try {
final prefs = await SharedPreferences.getInstance();
final savedJson = prefs.getString(_metadataProviderPriorityKey);
List<String> priority;
if (savedJson != null) {
final saved = _tryDecodeStringListPreference(
savedJson,
_metadataProviderPriorityKey,
);
if (saved != null) {
priority = _sanitizeMetadataProviderPriority(
_replaceRetiredBuiltInMetadataProviders(saved),
);
_log.d('Loaded metadata provider priority from prefs: $priority');
await prefs.setString(
_metadataProviderPriorityKey,
jsonEncode(priority),
);
await PlatformBridge.setMetadataProviderPriority(priority);
} else {
await prefs.remove(_metadataProviderPriorityKey);
final backendPriority =
await PlatformBridge.getMetadataProviderPriority();
priority = _sanitizeMetadataProviderPriority(backendPriority);
await prefs.setString(
_metadataProviderPriorityKey,
jsonEncode(priority),
);
await PlatformBridge.setMetadataProviderPriority(priority);
_log.d(
'Recovered metadata provider priority from defaults: $priority',
);
}
} else {
final backendPriority =
await PlatformBridge.getMetadataProviderPriority();
priority = _sanitizeMetadataProviderPriority(backendPriority);
_log.d('Using default metadata provider priority: $priority');
await prefs.setString(
_metadataProviderPriorityKey,
jsonEncode(priority),
);
await PlatformBridge.setMetadataProviderPriority(priority);
}
final priority = await _loadPriorityList(
prefsKey: _metadataProviderPriorityKey,
label: 'metadata provider priority',
sanitizeStored: (saved) => _sanitizeMetadataProviderPriority(
_replaceRetiredBuiltInMetadataProviders(saved),
),
sanitizeBackend: _sanitizeMetadataProviderPriority,
fetchBackend: PlatformBridge.getMetadataProviderPriority,
pushBackend: PlatformBridge.setMetadataProviderPriority,
);
state = state.copyWith(metadataProviderPriority: priority);
} catch (e) {
_log.e('Failed to load metadata provider priority: $e');
+30 -55
View File
@@ -364,15 +364,33 @@ class RepoNotifier extends Notifier<RepoState> {
String extensionsDir,
) {
return _runSerialized(
() => _installExtensionInternal(extensionId, tempDir, extensionsDir),
() => _downloadAndApplyExtension(
extensionId,
tempDir,
action: 'install',
apply: (notifier, path) => notifier.installExtension(path),
),
);
}
Future<bool> _installExtensionInternal(
Future<bool> updateExtension(String extensionId, String tempDir) {
return _runSerialized(
() => _downloadAndApplyExtension(
extensionId,
tempDir,
action: 'update',
apply: (notifier, path) => notifier.upgradeExtension(path),
),
);
}
Future<bool> _downloadAndApplyExtension(
String extensionId,
String tempDir,
String extensionsDir,
) async {
String tempDir, {
required String action,
required Future<bool> Function(ExtensionNotifier notifier, String path)
apply,
}) async {
state = state.copyWith(
isDownloading: true,
downloadingId: extensionId,
@@ -386,64 +404,21 @@ class RepoNotifier extends Notifier<RepoState> {
tempDir,
);
_log.i('Installing extension from: $downloadPath');
final extNotifier = ref.read(extensionProvider.notifier);
final success = await extNotifier.installExtension(downloadPath);
_log.i('Applying $action from: $downloadPath');
final success = await apply(
ref.read(extensionProvider.notifier),
downloadPath,
);
if (success) {
_log.i('Extension installed: $extensionId');
_log.i('Extension $action succeeded: $extensionId');
await refresh();
}
state = state.copyWith(isDownloading: false, clearDownloadingId: true);
return success;
} catch (e) {
_log.e('Failed to install extension: $e');
state = state.copyWith(
isDownloading: false,
clearDownloadingId: true,
error: e.toString(),
);
return false;
}
}
Future<bool> updateExtension(String extensionId, String tempDir) {
return _runSerialized(
() => _updateExtensionInternal(extensionId, tempDir),
);
}
Future<bool> _updateExtensionInternal(
String extensionId,
String tempDir,
) async {
state = state.copyWith(
isDownloading: true,
downloadingId: extensionId,
clearError: true,
);
try {
_log.i('Downloading update for: $extensionId');
final downloadPath = await PlatformBridge.downloadRepoExtension(
extensionId,
tempDir,
);
_log.i('Upgrading extension from: $downloadPath');
final extNotifier = ref.read(extensionProvider.notifier);
final success = await extNotifier.upgradeExtension(downloadPath);
if (success) {
_log.i('Extension updated: $extensionId');
await refresh();
}
state = state.copyWith(isDownloading: false, clearDownloadingId: true);
return success;
} catch (e) {
_log.e('Failed to update extension: $e');
_log.e('Failed to $action extension: $e');
state = state.copyWith(
isDownloading: false,
clearDownloadingId: true,
+2 -23
View File
@@ -23,6 +23,7 @@ import 'package:spotiflac_android/screens/selection_mode_mixin.dart';
import 'package:spotiflac_android/screens/track_metadata_screen.dart';
import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart';
import 'package:spotiflac_android/widgets/album_scaffold_body.dart';
import 'package:spotiflac_android/widgets/cached_cover_image.dart';
import 'package:spotiflac_android/widgets/album_track_tile.dart';
import 'package:spotiflac_android/widgets/animation_utils.dart';
import 'package:spotiflac_android/widgets/destructive_selection_button.dart';
@@ -206,7 +207,7 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen>
required int navigationIndex,
}) async {
final navigator = Navigator.of(context);
_precacheCover(item.coverUrl);
precacheCoverImage(context, item.coverUrl);
final beforeModTime =
await DownloadedEmbeddedCoverResolver.readFileModTimeMillis(
item.filePath,
@@ -230,28 +231,6 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen>
);
}
void _precacheCover(String? url) {
if (url == null || url.isEmpty) return;
if (!url.startsWith('http://') && !url.startsWith('https://')) {
return;
}
final dpr = MediaQuery.devicePixelRatioOf(
context,
).clamp(1.0, 3.0).toDouble();
final targetSize = (360 * dpr).round().clamp(512, 1024).toInt();
precacheImage(
ResizeImage(
CachedNetworkImageProvider(
url,
cacheManager: CoverCacheManager.instance,
),
width: targetSize,
height: targetSize,
),
context,
);
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
+1 -20
View File
@@ -2359,7 +2359,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
int? navigationIndex,
}) async {
final navigator = Navigator.of(context);
_precacheCover(item.coverUrl);
precacheCoverImage(context, item.coverUrl);
final beforeModTime =
await DownloadedEmbeddedCoverResolver.readFileModTimeMillis(
item.filePath,
@@ -2382,25 +2382,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
);
}
void _precacheCover(String? url) {
if (url == null || url.isEmpty) return;
if (!url.startsWith('http://') && !url.startsWith('https://')) {
return;
}
final dpr = MediaQuery.devicePixelRatioOf(
context,
).clamp(1.0, 3.0).toDouble();
final targetSize = (360 * dpr).round().clamp(512, 1024).toInt();
precacheImage(
ResizeImage(
cachedCoverImageProvider(url),
width: targetSize,
height: targetSize,
),
context,
);
}
Widget _buildErrorWidget(String error, ColorScheme colorScheme) {
final l10n = context.l10n;
final isRateLimit =
+2 -21
View File
@@ -93,25 +93,6 @@ extension _QueueTabNavigation on _QueueTabState {
);
}
void _precacheCover(String? url) {
if (url == null || url.isEmpty) return;
if (!url.startsWith('http://') && !url.startsWith('https://')) {
return;
}
final dpr = MediaQuery.devicePixelRatioOf(
context,
).clamp(1.0, 3.0).toDouble();
final targetSize = (360 * dpr).round().clamp(512, 1024).toInt();
precacheImage(
ResizeImage(
cachedCoverImageProvider(url),
width: targetSize,
height: targetSize,
),
context,
);
}
Future<void> _navigateToMetadataScreen(DownloadItem item) async {
final historyItem = ref
.read(downloadHistoryProvider)
@@ -131,7 +112,7 @@ extension _QueueTabNavigation on _QueueTabState {
);
final navigator = Navigator.of(context);
_precacheCover(historyItem.coverUrl);
precacheCoverImage(context, historyItem.coverUrl);
_searchFocusNode.unfocus();
final beforeModTime = await _readFileModTimeMillis(historyItem.filePath);
if (!mounted) return;
@@ -159,7 +140,7 @@ extension _QueueTabNavigation on _QueueTabState {
int? navigationIndex,
}) async {
final navigator = Navigator.of(context);
_precacheCover(item.coverUrl);
precacheCoverImage(context, item.coverUrl);
_searchFocusNode.unfocus();
final beforeModTime = await _readFileModTimeMillis(item.filePath);
if (!mounted) return;
+19
View File
@@ -180,6 +180,25 @@ CachedNetworkImageProvider cachedCoverImageProvider(String url) {
);
}
/// Pre-warms the cover cache at the metadata-screen display size so the hero
/// transition doesn't pop in a low-res frame. Http(s) URLs only.
void precacheCoverImage(BuildContext context, String? url) {
if (url == null || url.isEmpty) return;
if (!url.startsWith('http://') && !url.startsWith('https://')) {
return;
}
final dpr = MediaQuery.devicePixelRatioOf(context).clamp(1.0, 3.0).toDouble();
final targetSize = (360 * dpr).round().clamp(512, 1024).toInt();
precacheImage(
ResizeImage(
cachedCoverImageProvider(url),
width: targetSize,
height: targetSize,
),
context,
);
}
int coverImageCacheExtent(
BuildContext context,
double logicalSize, {