perf(collections): reuse membership indexes for metadata changes

This commit is contained in:
zarzet committed 2026-10-07 03:15:57 +07:00
1 parent 0c6ad3b96a
commit 996efb4f54
2 files changed
+76 -1

No files matched your search

+43 -1
View File
@@ -171,6 +171,28 @@ class UserPlaylistCollection {
return _trackKeys.contains(trackKey);
}
bool sharesMembershipIndexWith(UserPlaylistCollection other) =>
identical(_trackKeys, other._trackKeys);
/// Publishes tracks and their index built by the hydration worker. When it
/// verified unchanged membership, keep the original index so the enclosing
/// collections state need not rescan every other playlist's tracks.
UserPlaylistCollection withHydratedTracks({
required List<CollectionTrackEntry> tracks,
required Set<String> trackKeys,
required bool membershipUnchanged,
}) => UserPlaylistCollection(
id: id,
name: name,
coverImagePath: coverImagePath,
createdAt: createdAt,
updatedAt: updatedAt,
tracks: tracks,
previewCover: previewCover,
tracksLoaded: true,
trackKeys: membershipUnchanged ? _trackKeys : trackKeys,
);
Set<String> get trackKeys => UnmodifiableSetView(_trackKeys);
int get trackCount => _trackKeys.length;
@@ -365,10 +387,30 @@ class LibraryCollectionsState {
lovedKeys: keepLovedIndex ? _lovedKeys : null,
favoriteArtistKeys: keepFavoriteArtistIndex ? _favoriteArtistKeys : null,
playlistsById: keepPlaylistIndex ? _playlistsById : null,
allPlaylistTrackKeys: keepPlaylistIndex ? _allPlaylistTrackKeys : null,
allPlaylistTrackKeys:
keepPlaylistIndex || _hasSamePlaylistMembership(nextPlaylists)
? _allPlaylistTrackKeys
: null,
);
}
bool _hasSamePlaylistMembership(List<UserPlaylistCollection> nextPlaylists) {
if (nextPlaylists.length != playlists.length ||
_playlistsById.length != playlists.length) {
return false;
}
final seen = <String>{};
for (final playlist in nextPlaylists) {
final previous = _playlistsById[playlist.id];
if (!seen.add(playlist.id) ||
previous == null ||
!identical(previous._trackKeys, playlist._trackKeys)) {
return false;
}
}
return true;
}
Map<String, dynamic> toJson() => {
'wishlist': wishlist.map((e) => e.toJson()).toList(),
'loved': loved.map((e) => e.toJson()).toList(),
+33
View File
@@ -106,4 +106,37 @@ void main() {
expect(loaded.tracksLoaded, isTrue);
expect(loaded.trackCount, 0);
});
test(
'membership remains correct after metadata copies and playlist changes',
() {
UserPlaylistCollection playlist(String id, String key) =>
UserPlaylistCollection(
id: id,
name: id,
createdAt: DateTime.utc(2026),
updatedAt: DateTime.utc(2026),
tracks: const [],
tracksLoaded: false,
trackKeys: {key},
);
final first = playlist('first', 'example:first');
final second = playlist('second', 'example:second');
final state = LibraryCollectionsState(playlists: [first, second]);
final renamed = state.copyWith(
playlists: [
second.copyWith(name: 'Renamed'),
first,
],
);
expect(renamed.isTrackInAnyPlaylist('example:first'), isTrue);
expect(renamed.isTrackInAnyPlaylist('example:second'), isTrue);
final replaced = renamed.copyWith(playlists: [first, first]);
expect(replaced.isTrackInAnyPlaylist('example:first'), isTrue);
expect(replaced.isTrackInAnyPlaylist('example:second'), isFalse);
final removed = renamed.copyWith(playlists: [second]);
expect(removed.isTrackInAnyPlaylist('example:first'), isFalse);
expect(removed.isTrackInAnyPlaylist('example:second'), isTrue);
},
);
}