Skip to content

Commit 333ee80

Browse files
authored
fix: fixed bugs related to liked songs (#852)
1 parent 28f6d80 commit 333ee80

6 files changed

Lines changed: 224 additions & 51 deletions

File tree

lib/screens/playlist_page.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,9 @@ class _PlaylistPageState extends State<PlaylistPage> {
369369
updatePlaylistLikeStatus(
370370
_playlist['ytid'],
371371
playlistLikeStatus.value,
372+
playlistData: _playlist,
372373
),
373374
);
374-
currentLikedPlaylistsLength.value =
375-
currentLikedPlaylistsLength.value - 1;
376375
},
377376
tooltip: context.l10n!.removeFromLikedSongs,
378377
)
@@ -385,10 +384,9 @@ class _PlaylistPageState extends State<PlaylistPage> {
385384
updatePlaylistLikeStatus(
386385
_playlist['ytid'],
387386
playlistLikeStatus.value,
387+
playlistData: _playlist,
388388
),
389389
);
390-
currentLikedPlaylistsLength.value =
391-
currentLikedPlaylistsLength.value + 1;
392390
},
393391
tooltip: context.l10n!.addToLikedSongs,
394392
);

lib/services/common_services.dart

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ final currentRecentlyPlayedLength = ValueNotifier<int>(
5757
userRecentlyPlayed.length,
5858
);
5959
final recentlyPlayedVersion = ValueNotifier<int>(0);
60+
var _songLikeUpdateToken = 0;
61+
final _latestSongLikeUpdateTokens = <String, int>{};
6062

6163
final lyrics = ValueNotifier<String?>(null);
6264
String? lastFetchedLyrics;
@@ -213,20 +215,44 @@ List _deduplicateAndShuffle(List playlistSongs) {
213215
return uniqueSongs;
214216
}
215217

216-
Future<void> updateSongLikeStatus(dynamic songId, bool add) async {
218+
Future<void> updateSongLikeStatus(
219+
dynamic songId,
220+
bool add, {
221+
Map? songData,
222+
}) async {
217223
try {
224+
final normalizedSongId = songId?.toString().trim() ?? '';
225+
if (normalizedSongId.isEmpty) return;
226+
227+
final updateToken = ++_songLikeUpdateToken;
228+
_latestSongLikeUpdateTokens[normalizedSongId] = updateToken;
229+
230+
final songToAdd = add
231+
? await _resolveSongForLikedStatus(normalizedSongId, songData)
232+
: null;
233+
234+
if (_latestSongLikeUpdateTokens[normalizedSongId] != updateToken) {
235+
return;
236+
}
237+
238+
final updatedLikedSongs = _deduplicateLikedSongs(userLikedSongsList);
239+
218240
if (add) {
219-
if (!userLikedSongsList.any((song) => song['ytid'] == songId)) {
220-
final songDetails = await getSongDetails(
221-
userLikedSongsList.length,
222-
songId,
223-
);
224-
userLikedSongsList.add(songDetails);
241+
if (songToAdd != null &&
242+
!updatedLikedSongs.any(
243+
(song) => song['ytid']?.toString() == normalizedSongId,
244+
)) {
245+
updatedLikedSongs.add(songToAdd);
225246
}
226247
} else {
227-
userLikedSongsList.removeWhere((song) => song['ytid'] == songId);
248+
updatedLikedSongs.removeWhere(
249+
(song) => song['ytid']?.toString() == normalizedSongId,
250+
);
228251
}
229252

253+
if (_likedSongIdsAreEqual(userLikedSongsList, updatedLikedSongs)) return;
254+
255+
userLikedSongsList = updatedLikedSongs;
230256
currentLikedSongsLength.value = userLikedSongsList.length;
231257
unawaited(addOrUpdateData('user', 'likedSongs', userLikedSongsList));
232258
} catch (e, stackTrace) {
@@ -238,6 +264,68 @@ Future<void> updateSongLikeStatus(dynamic songId, bool add) async {
238264
}
239265
}
240266

267+
Future<Map?> _resolveSongForLikedStatus(String songId, Map? songData) async {
268+
if (songData?['ytid']?.toString() == songId) {
269+
return Map<String, dynamic>.from(songData!);
270+
}
271+
272+
final cachedSong = _findSongById(userLikedSongsList, songId);
273+
if (cachedSong != null) return Map<String, dynamic>.from(cachedSong);
274+
275+
return getSongDetails(userLikedSongsList.length, songId);
276+
}
277+
278+
Map? _findSongById(Iterable<dynamic> songs, String songId) {
279+
for (final song in songs) {
280+
if (song is Map && song['ytid']?.toString() == songId) return song;
281+
}
282+
283+
return null;
284+
}
285+
286+
List _deduplicateLikedSongs(Iterable<dynamic> likedSongs) {
287+
final seenSongIds = <String>{};
288+
final deduplicatedSongs = [];
289+
290+
for (final song in likedSongs) {
291+
if (song is! Map) {
292+
deduplicatedSongs.add(song);
293+
continue;
294+
}
295+
296+
final songId = song['ytid']?.toString();
297+
if (songId == null || songId.isEmpty) {
298+
deduplicatedSongs.add(song);
299+
continue;
300+
}
301+
302+
if (seenSongIds.add(songId)) {
303+
deduplicatedSongs.add(song);
304+
}
305+
}
306+
307+
return deduplicatedSongs;
308+
}
309+
310+
bool _likedSongIdsAreEqual(List previous, List updated) {
311+
if (previous.length != updated.length) return false;
312+
313+
for (var i = 0; i < previous.length; i++) {
314+
final previousSong = previous[i];
315+
final updatedSong = updated[i];
316+
if (previousSong is! Map || updatedSong is! Map) {
317+
if (previousSong != updatedSong) return false;
318+
continue;
319+
}
320+
321+
if (previousSong['ytid']?.toString() != updatedSong['ytid']?.toString()) {
322+
return false;
323+
}
324+
}
325+
326+
return true;
327+
}
328+
241329
void moveLikedSong(int oldIndex, int newIndex) {
242330
if (oldIndex < newIndex) {
243331
newIndex -= 1;
@@ -275,11 +363,17 @@ Future<void> renameSongInLikedSongs(
275363
}
276364
}
277365

278-
bool isSongAlreadyLiked(songIdToCheck) =>
279-
userLikedSongsList.any((song) => song['ytid'] == songIdToCheck);
366+
bool isSongAlreadyLiked(songIdToCheck) {
367+
final songId = songIdToCheck?.toString();
368+
return userLikedSongsList.any((song) => song['ytid']?.toString() == songId);
369+
}
280370

281-
bool isPlaylistAlreadyLiked(playlistIdToCheck) =>
282-
userLikedPlaylists.any((playlist) => playlist['ytid'] == playlistIdToCheck);
371+
bool isPlaylistAlreadyLiked(playlistIdToCheck) {
372+
final playlistId = playlistIdToCheck?.toString();
373+
return userLikedPlaylists.any(
374+
(playlist) => playlist['ytid']?.toString() == playlistId,
375+
);
376+
}
283377

284378
bool isSongAlreadyOffline(songIdToCheck) =>
285379
userOfflineSongs.any((song) => song['ytid'] == songIdToCheck);

lib/services/playlists_manager.dart

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ const pinnedPlaylistsLimit = 5;
101101
final currentLikedPlaylistsLength = ValueNotifier<int>(
102102
userLikedPlaylists.length,
103103
);
104+
var _playlistLikeUpdateToken = 0;
105+
final _latestPlaylistLikeUpdateTokens = <String, int>{};
104106

105107
Future<List<dynamic>> getUserPlaylists() async {
106108
final futures = userPlaylists.value.map((playlistID) async {
@@ -467,9 +469,16 @@ bool _removePlaylistFromFolders(String playlistId) {
467469
}
468470

469471
bool _removePlaylistFromLikedPlaylists(String playlistId) {
470-
final previousLength = userLikedPlaylists.length;
471-
userLikedPlaylists.removeWhere((playlist) => playlist['ytid'] == playlistId);
472-
return userLikedPlaylists.length != previousLength;
472+
final updatedLikedPlaylists = _deduplicateLikedPlaylists(userLikedPlaylists)
473+
..removeWhere((playlist) => playlist['ytid']?.toString() == playlistId);
474+
475+
if (_likedPlaylistIdsAreEqual(userLikedPlaylists, updatedLikedPlaylists)) {
476+
return false;
477+
}
478+
479+
userLikedPlaylists = updatedLikedPlaylists;
480+
currentLikedPlaylistsLength.value = userLikedPlaylists.length;
481+
return true;
473482
}
474483

475484
String createPlaylistFolder(String folderName, [BuildContext? context]) {
@@ -1084,32 +1093,51 @@ Future<void> renameSongInPlaylist(
10841093
}
10851094
}
10861095

1087-
Future<void> updatePlaylistLikeStatus(String playlistId, bool add) async {
1096+
Future<void> updatePlaylistLikeStatus(
1097+
String playlistId,
1098+
bool add, {
1099+
Map? playlistData,
1100+
}) async {
10881101
try {
1089-
if (add) {
1090-
if (!userLikedPlaylists.any(
1091-
(playlist) => playlist['ytid'] == playlistId,
1092-
)) {
1093-
final playlist = playlists.firstWhere(
1094-
(playlist) => playlist['ytid'] == playlistId,
1095-
orElse: () => <String, dynamic>{},
1096-
);
1102+
final normalizedPlaylistId = playlistId.trim();
1103+
if (normalizedPlaylistId.isEmpty) return;
10971104

1098-
if (playlist.isNotEmpty) {
1099-
userLikedPlaylists.add(playlist);
1100-
} else {
1101-
final playlistInfo = await getPlaylistInfoForWidget(playlistId);
1102-
if (playlistInfo != null) {
1103-
userLikedPlaylists.add(playlistInfo);
1104-
}
1105-
}
1105+
final updateToken = ++_playlistLikeUpdateToken;
1106+
_latestPlaylistLikeUpdateTokens[normalizedPlaylistId] = updateToken;
1107+
1108+
final playlistToAdd = add
1109+
? await _resolvePlaylistForLikedStatus(
1110+
normalizedPlaylistId,
1111+
playlistData,
1112+
)
1113+
: null;
1114+
1115+
if (_latestPlaylistLikeUpdateTokens[normalizedPlaylistId] != updateToken) {
1116+
return;
1117+
}
1118+
1119+
final updatedLikedPlaylists = _deduplicateLikedPlaylists(
1120+
userLikedPlaylists,
1121+
);
1122+
1123+
if (add) {
1124+
if (playlistToAdd != null &&
1125+
!updatedLikedPlaylists.any(
1126+
(playlist) => playlist['ytid']?.toString() == normalizedPlaylistId,
1127+
)) {
1128+
updatedLikedPlaylists.add(playlistToAdd);
11061129
}
11071130
} else {
1108-
userLikedPlaylists.removeWhere(
1109-
(playlist) => playlist['ytid'] == playlistId,
1131+
updatedLikedPlaylists.removeWhere(
1132+
(playlist) => playlist['ytid']?.toString() == normalizedPlaylistId,
11101133
);
11111134
}
11121135

1136+
if (_likedPlaylistIdsAreEqual(userLikedPlaylists, updatedLikedPlaylists)) {
1137+
return;
1138+
}
1139+
1140+
userLikedPlaylists = updatedLikedPlaylists;
11131141
currentLikedPlaylistsLength.value = userLikedPlaylists.length;
11141142
unawaited(addOrUpdateData('user', 'likedPlaylists', userLikedPlaylists));
11151143
} catch (e, stackTrace) {
@@ -1121,6 +1149,54 @@ Future<void> updatePlaylistLikeStatus(String playlistId, bool add) async {
11211149
}
11221150
}
11231151

1152+
List<Map> _deduplicateLikedPlaylists(Iterable<Map> likedPlaylists) {
1153+
final seenPlaylistIds = <String>{};
1154+
final deduplicatedPlaylists = <Map>[];
1155+
1156+
for (final playlist in likedPlaylists) {
1157+
final playlistId = playlist['ytid']?.toString();
1158+
if (playlistId == null || playlistId.isEmpty) {
1159+
deduplicatedPlaylists.add(playlist);
1160+
continue;
1161+
}
1162+
1163+
if (seenPlaylistIds.add(playlistId)) {
1164+
deduplicatedPlaylists.add(playlist);
1165+
}
1166+
}
1167+
1168+
return deduplicatedPlaylists;
1169+
}
1170+
1171+
bool _likedPlaylistIdsAreEqual(List<Map> previous, List<Map> updated) {
1172+
if (previous.length != updated.length) return false;
1173+
1174+
for (var i = 0; i < previous.length; i++) {
1175+
if (previous[i]['ytid']?.toString() != updated[i]['ytid']?.toString()) {
1176+
return false;
1177+
}
1178+
}
1179+
1180+
return true;
1181+
}
1182+
1183+
Future<Map?> _resolvePlaylistForLikedStatus(
1184+
String playlistId,
1185+
Map? playlistData,
1186+
) async {
1187+
if (playlistData?['ytid']?.toString() == playlistId) {
1188+
return Map<String, dynamic>.from(playlistData!);
1189+
}
1190+
1191+
final cachedPlaylist = _searchAppPlaylistsById(playlistId);
1192+
if (cachedPlaylist != null) {
1193+
return Map<String, dynamic>.from(cachedPlaylist);
1194+
}
1195+
1196+
final playlistInfo = await getPlaylistInfoForWidget(playlistId);
1197+
return playlistInfo == null ? null : Map<String, dynamic>.from(playlistInfo);
1198+
}
1199+
11241200
bool isPlaylistPinned(String playlistId) =>
11251201
pinnedPlaylistIds.value.contains(playlistId);
11261202

lib/widgets/now_playing/bottom_actions_row.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,11 @@ class _BottomActionsRowState extends State<BottomActionsRow> {
155155
statusNotifier: _songLikeStatus,
156156
activeColor: colorScheme.primary,
157157
onPressed: () {
158-
updateSongLikeStatus(widget.audioId, !_songLikeStatus.value);
158+
updateSongLikeStatus(
159+
widget.audioId,
160+
!_songLikeStatus.value,
161+
songData: mediaItemToMap(widget.metadata),
162+
);
159163
_songLikeStatus.value = !_songLikeStatus.value;
160164
},
161165
tooltip: l10n.likedSongs,

lib/widgets/playlist_bar.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,13 @@ class PlaylistBar extends StatelessWidget {
206206
case 'like':
207207
if (_resolvedPlaylistId != null) {
208208
final isLiked = isPlaylistAlreadyLiked(_resolvedPlaylistId);
209-
updatePlaylistLikeStatus(_resolvedPlaylistId!, !isLiked);
210-
currentLikedPlaylistsLength.value += !isLiked ? 1 : -1;
209+
unawaited(
210+
updatePlaylistLikeStatus(
211+
_resolvedPlaylistId!,
212+
!isLiked,
213+
playlistData: playlistData,
214+
),
215+
);
211216
}
212217
break;
213218
case 'pin':

lib/widgets/song_bar.dart

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -255,16 +255,12 @@ class _SongBarState extends State<SongBar> {
255255
case 'like':
256256
final newValue = !_songLikeStatus.value;
257257
_songLikeStatus.value = newValue;
258-
final likedSongsLength = currentLikedSongsLength.value;
259-
currentLikedSongsLength.value = newValue
260-
? likedSongsLength + 1
261-
: likedSongsLength - 1;
262-
updateSongLikeStatus(_ytid, newValue).catchError((e) {
263-
logger.log('Error updating song like status', error: e);
264-
// Revert on error
265-
_songLikeStatus.value = !newValue;
266-
currentLikedSongsLength.value = likedSongsLength;
267-
});
258+
updateSongLikeStatus(_ytid, newValue, songData: widget.song).catchError(
259+
(e) {
260+
logger.log('Error updating song like status', error: e);
261+
_songLikeStatus.value = !newValue;
262+
},
263+
);
268264
showToast(
269265
context,
270266
newValue

0 commit comments

Comments
 (0)