refactor: extract shared helpers for repeated low-level patterns

Dart:
- notification_service: single _details() builder replaces 13 copies
  of the NotificationDetails block
- platform_bridge: _invokeMap() for 34 invoke+decode call sites,
  _cachedInvoke() unifies the three TTL/in-flight cache scaffolds
- ffmpeg_service: _promoteTempOutput(), _appendCoverInputArgs(),
  single _writeReplayGainTags() and _convertToLossless() for the
  ALAC/FLAC twins
- sqlite_helpers.dart: shared openAppDatabase/path-key/migration
  helpers for the three database classes
- library_collections: parametrized wishlist/loved/favorite CRUD
- extension_provider: one predicate-based replacedBuiltIn* lookup

Go:
- extension runtime: parseGojaHeaders/coerceGojaBody/doExtensionHTTP
  shared by httpGet/httpPost/httpRequest/shortcuts/fetch
- exports_metadata: applyAudioMetadataToResult + successMethodJSON,
  APE edit path reuses audioMetadataFromEditFields
- lyrics: lrclibGet() for both LRCLib fetchers
- extension_store: drop hand-rolled strings helpers
This commit is contained in:
zarzet
2026-07-12 14:10:23 +07:00
parent aa2cdef1d2
commit 4e0cae9c20
16 changed files with 851 additions and 1779 deletions
+11 -29
View File
@@ -1298,7 +1298,10 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
.firstOrNull;
}
String? replacedBuiltInDownloadProviderFor(String providerId) {
String? _replacedBuiltInProviderFor(
String providerId,
bool Function(Extension ext) hasCapability,
) {
final normalized = providerId.trim().toLowerCase();
if (normalized.isEmpty) return null;
@@ -1306,42 +1309,21 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
.where(
(ext) =>
ext.enabled &&
ext.hasDownloadProvider &&
hasCapability(ext) &&
ext.replacesBuiltInProviders.contains(normalized),
)
.map((ext) => ext.id)
.firstOrNull;
}
String? replacedBuiltInSearchProviderFor(String providerId) {
final normalized = providerId.trim().toLowerCase();
if (normalized.isEmpty) return null;
String? replacedBuiltInDownloadProviderFor(String providerId) =>
_replacedBuiltInProviderFor(providerId, (ext) => ext.hasDownloadProvider);
return state.extensions
.where(
(ext) =>
ext.enabled &&
ext.hasCustomSearch &&
ext.replacesBuiltInProviders.contains(normalized),
)
.map((ext) => ext.id)
.firstOrNull;
}
String? replacedBuiltInSearchProviderFor(String providerId) =>
_replacedBuiltInProviderFor(providerId, (ext) => ext.hasCustomSearch);
String? replacedBuiltInMetadataProviderFor(String providerId) {
final normalized = providerId.trim().toLowerCase();
if (normalized.isEmpty) return null;
return state.extensions
.where(
(ext) =>
ext.enabled &&
ext.hasMetadataProvider &&
ext.replacesBuiltInProviders.contains(normalized),
)
.map((ext) => ext.id)
.firstOrNull;
}
String? replacedBuiltInMetadataProviderFor(String providerId) =>
_replacedBuiltInProviderFor(providerId, (ext) => ext.hasMetadataProvider);
bool downloadProviderReplacesLegacyProvider(
String providerId,
+80 -66
View File
@@ -584,15 +584,30 @@ class LibraryCollectionsNotifier extends Notifier<LibraryCollectionsState> {
return true;
}
Future<bool> toggleWishlist(Track track) async {
Future<bool> _toggleTrackEntry(
Track track, {
required bool Function(String key) contains,
required List<CollectionTrackEntry> Function(LibraryCollectionsState state)
select,
required LibraryCollectionsState Function(List<CollectionTrackEntry> list)
withList,
required Future<void> Function(String key) dbDelete,
required Future<void> Function({
required String trackKey,
required String trackJson,
required String addedAt,
})
dbUpsert,
}) async {
await _ensureLoaded();
final key = trackCollectionKey(track);
if (state.containsWishlistKey(key)) {
await _db.deleteWishlistEntry(key);
final updated = state.wishlist
.where((entry) => entry.key != key)
.toList(growable: false);
state = state.copyWith(wishlist: updated);
if (contains(key)) {
await dbDelete(key);
state = withList(
select(
state,
).where((entry) => entry.key != key).toList(growable: false),
);
return false;
}
@@ -601,42 +616,32 @@ class LibraryCollectionsNotifier extends Notifier<LibraryCollectionsState> {
track: track,
addedAt: DateTime.now(),
);
await _db.upsertWishlistEntry(
await dbUpsert(
trackKey: key,
trackJson: jsonEncode(track.toJson()),
addedAt: entry.addedAt.toIso8601String(),
);
final updated = [entry, ...state.wishlist];
state = state.copyWith(wishlist: updated);
state = withList([entry, ...select(state)]);
return true;
}
Future<bool> toggleLoved(Track track) async {
await _ensureLoaded();
final key = trackCollectionKey(track);
if (state.containsLovedKey(key)) {
await _db.deleteLovedEntry(key);
final updated = state.loved
.where((entry) => entry.key != key)
.toList(growable: false);
state = state.copyWith(loved: updated);
return false;
}
Future<bool> toggleWishlist(Track track) => _toggleTrackEntry(
track,
contains: (key) => state.containsWishlistKey(key),
select: (state) => state.wishlist,
withList: (list) => state.copyWith(wishlist: list),
dbDelete: _db.deleteWishlistEntry,
dbUpsert: _db.upsertWishlistEntry,
);
final entry = CollectionTrackEntry(
key: key,
track: track,
addedAt: DateTime.now(),
);
await _db.upsertLovedEntry(
trackKey: key,
trackJson: jsonEncode(track.toJson()),
addedAt: entry.addedAt.toIso8601String(),
);
final updated = [entry, ...state.loved];
state = state.copyWith(loved: updated);
return true;
}
Future<bool> toggleLoved(Track track) => _toggleTrackEntry(
track,
contains: (key) => state.containsLovedKey(key),
select: (state) => state.loved,
withList: (list) => state.copyWith(loved: list),
dbDelete: _db.deleteLovedEntry,
dbUpsert: _db.upsertLovedEntry,
);
Future<bool> toggleFavoriteArtist({
required String artistId,
@@ -654,11 +659,7 @@ class LibraryCollectionsNotifier extends Notifier<LibraryCollectionsState> {
? trimmedProviderId
: (source.isNotEmpty && source != 'builtin' ? source : null);
if (state.containsFavoriteArtistKey(key)) {
await _db.deleteFavoriteArtistEntry(key);
final updated = state.favoriteArtists
.where((entry) => entry.key != key)
.toList(growable: false);
state = state.copyWith(favoriteArtists: updated);
await removeFavoriteArtist(key);
return false;
}
@@ -680,38 +681,51 @@ class LibraryCollectionsNotifier extends Notifier<LibraryCollectionsState> {
return true;
}
Future<void> removeFavoriteArtist(String artistKey) async {
Future<void> _removeEntry<T>(
String key, {
required bool Function(String key) contains,
required List<T> Function(LibraryCollectionsState state) select,
required String Function(T entry) keyOf,
required LibraryCollectionsState Function(List<T> list) withList,
required Future<void> Function(String key) dbDelete,
}) async {
await _ensureLoaded();
if (!state.containsFavoriteArtistKey(artistKey)) return;
if (!contains(key)) return;
await _db.deleteFavoriteArtistEntry(artistKey);
final updated = state.favoriteArtists
.where((entry) => entry.key != artistKey)
.toList(growable: false);
state = state.copyWith(favoriteArtists: updated);
await dbDelete(key);
state = withList(
select(
state,
).where((entry) => keyOf(entry) != key).toList(growable: false),
);
}
Future<void> removeFromWishlist(String trackKey) async {
await _ensureLoaded();
if (!state.containsWishlistKey(trackKey)) return;
Future<void> removeFavoriteArtist(String artistKey) => _removeEntry(
artistKey,
contains: (key) => state.containsFavoriteArtistKey(key),
select: (state) => state.favoriteArtists,
keyOf: (entry) => entry.key,
withList: (list) => state.copyWith(favoriteArtists: list),
dbDelete: _db.deleteFavoriteArtistEntry,
);
await _db.deleteWishlistEntry(trackKey);
final updated = state.wishlist
.where((entry) => entry.key != trackKey)
.toList(growable: false);
state = state.copyWith(wishlist: updated);
}
Future<void> removeFromWishlist(String trackKey) => _removeEntry(
trackKey,
contains: (key) => state.containsWishlistKey(key),
select: (state) => state.wishlist,
keyOf: (entry) => entry.key,
withList: (list) => state.copyWith(wishlist: list),
dbDelete: _db.deleteWishlistEntry,
);
Future<void> removeFromLoved(String trackKey) async {
await _ensureLoaded();
if (!state.containsLovedKey(trackKey)) return;
await _db.deleteLovedEntry(trackKey);
final updated = state.loved
.where((entry) => entry.key != trackKey)
.toList(growable: false);
state = state.copyWith(loved: updated);
}
Future<void> removeFromLoved(String trackKey) => _removeEntry(
trackKey,
contains: (key) => state.containsLovedKey(key),
select: (state) => state.loved,
keyOf: (entry) => entry.key,
withList: (list) => state.copyWith(loved: list),
dbDelete: _db.deleteLovedEntry,
);
Future<String> createPlaylist(String name) async {
await _ensureLoaded();