mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-05 18:48:38 +02:00
refactor: consolidate lyrics builders, cancel registries, and priority plumbing
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, {
|
||||
|
||||
Reference in New Issue
Block a user