@@ -309,168 +309,161 @@ extension ActivityResponseMapper on ActivityResponse {
309309
310310/// Extension functions for [ActivityData] to handle common operations.
311311extension ActivityDataMutations on ActivityData {
312- /// Adds a comment to the activity, updating the comment count and the list of comments .
312+ /// Updates this activity with new data while preserving own data .
313313 ///
314- /// @param comment The comment to be added.
315- /// @return A new [ActivityData] instance with the updated comments and comment count.
316- ActivityData addComment (CommentData comment) {
317- final updatedComments = comments.upsert (comment, key: (it) => it.id);
314+ /// Merges [updated] activity data with this instance, preserving [ownBookmarks] and
315+ /// [ownReactions] from this instance when not provided. This ensures that user-specific
316+ /// data is not lost when updating from WebSocket events.
317+ ///
318+ /// Returns a new [ActivityData] instance with the merged data.
319+ ActivityData updateWith (
320+ ActivityData updated, {
321+ List <BookmarkData >? ownBookmarks,
322+ List <FeedsReactionData >? ownReactions,
323+ }) {
324+ return updated.copyWith (
325+ // Preserve own data from the current instance if not provided
326+ // as they may not be reliable from WS events.
327+ ownBookmarks: ownBookmarks ?? this .ownBookmarks,
328+ ownReactions: ownReactions ?? this .ownReactions,
329+ poll: updated.poll? .let ((it) => poll? .updateWith (it) ?? it),
330+ );
331+ }
332+
333+ /// Adds or updates a comment in this activity.
334+ ///
335+ /// Updates the comments list by adding or updating [comment] . If the comment already
336+ /// exists, it will be updated. The comment count is automatically recalculated.
337+ ///
338+ /// Returns a new [ActivityData] instance with the updated comments and comment count.
339+ ActivityData upsertComment (CommentData comment) {
340+ final currentComments = [...comments];
341+ final updatedComments = currentComments.upsert (comment, key: (it) => it.id);
342+
343+ final difference = updatedComments.length - currentComments.length;
344+ final updatedCommentCount = math.max (0 , commentCount + difference);
318345
319346 return copyWith (
320347 comments: updatedComments,
321- commentCount: math. max ( 0 , commentCount + 1 ) ,
348+ commentCount: updatedCommentCount ,
322349 );
323350 }
324351
325- /// Removes a comment from the activity, updating the comment count and the list of comments.
352+ /// Removes a comment from this activity.
353+ ///
354+ /// Updates the comments list by removing [comment] . The comment count is automatically
355+ /// recalculated.
326356 ///
327- /// @param comment The comment to be removed.
328- /// @return A new [ActivityData] instance with the updated comments and comment count.
357+ /// Returns a new [ActivityData] instance with the updated comments and comment count.
329358 ActivityData removeComment (CommentData comment) {
330- final updatedComments = comments.where ((it) {
359+ final currentComments = [...comments];
360+ final updatedComments = currentComments.where ((it) {
331361 return it.id != comment.id;
332362 }).toList ();
333363
364+ final difference = updatedComments.length - currentComments.length;
365+ final updatedCommentCount = math.max (0 , commentCount + difference);
366+
334367 return copyWith (
335368 comments: updatedComments,
336- commentCount: math. max ( 0 , commentCount - 1 ) ,
369+ commentCount: updatedCommentCount ,
337370 );
338371 }
339372
340- /// Adds a bookmark to the activity, updating the own bookmarks and bookmark count.
373+ /// Adds or updates a bookmark in this activity.
374+ ///
375+ /// Updates the own bookmarks list by adding or updating [bookmark] . Only adds bookmarks
376+ /// that belong to [currentUserId] . If the bookmark already exists, it will be updated.
341377 ///
342- /// @param bookmark The bookmark to be added.
343- /// @param currentUserId The ID of the current user, used to determine if the bookmark belongs to
344- /// them.
345- /// @return A new [ActivityData] instance with the updated own bookmarks and bookmark count.
346- ActivityData addBookmark (
378+ /// Returns a new [ActivityData] instance with the updated own bookmarks and bookmark count.
379+ ActivityData upsertBookmark (
347380 BookmarkData bookmark,
348381 String currentUserId,
349382 ) {
350- final updatedOwnBookmarks = switch (bookmark.user.id == currentUserId ) {
351- true => ownBookmarks. upsert (bookmark, key : (it) => it.id),
352- false => ownBookmarks,
353- };
383+ final updatedOwnBookmarks = ownBookmarks. let ((it ) {
384+ if (bookmark.user.id != currentUserId) return it;
385+ return it. upsert (bookmark, key : (it) => it.id);
386+ }) ;
354387
355- return copyWith (
356- ownBookmarks: updatedOwnBookmarks,
357- bookmarkCount: math.max (0 , bookmarkCount + 1 ),
358- );
388+ return updateWith (bookmark.activity, ownBookmarks: updatedOwnBookmarks);
359389 }
360390
361- /// Removes a bookmark from the activity, updating the own bookmarks and bookmark count .
391+ /// Removes a bookmark from this activity.
362392 ///
363- /// @param bookmark The bookmark to be deleted.
364- /// @param currentUserId The ID of the current user, used to determine if the bookmark belongs to
365- /// them.
366- /// @return A new [ActivityData] instance with the updated own bookmarks and bookmark count.
393+ /// Updates the own bookmarks list by removing [bookmark] . Only removes bookmarks
394+ /// that belong to [currentUserId] .
395+ ///
396+ /// Returns a new [ActivityData] instance with the updated own bookmarks and bookmark count.
367397 ActivityData removeBookmark (
368398 BookmarkData bookmark,
369399 String currentUserId,
370400 ) {
371- final updatedOwnBookmarks = switch (bookmark.user.id == currentUserId ) {
372- true => ownBookmarks. where ((it) => it. id != bookmark.id). toList (),
373- false => ownBookmarks,
374- };
401+ final updatedOwnBookmarks = ownBookmarks. let ((it ) {
402+ if (bookmark.user. id != currentUserId) return it;
403+ return it. where ((it) => it.id != bookmark.id). toList ();
404+ }) ;
375405
376- return copyWith (
377- ownBookmarks: updatedOwnBookmarks,
378- bookmarkCount: math.max (0 , bookmarkCount - 1 ),
379- );
406+ return updateWith (bookmark.activity, ownBookmarks: updatedOwnBookmarks);
380407 }
381408
382- /// Adds a reaction to the activity, updating the latest reactions, reaction groups,
383- /// reaction count, and own reactions.
409+ /// Adds or updates a reaction in this activity with unique enforcement.
410+ ///
411+ /// Updates the own reactions list by adding or updating [reaction] . Only adds reactions
412+ /// that belong to [currentUserId] . When unique enforcement is enabled, replaces any
413+ /// existing reaction from the same user.
384414 ///
385- /// @param reaction The reaction to be added.
386- /// @param currentUserId The ID of the current user, used to determine if the reaction belongs to.
387- /// @return A new [ActivityData] instance with the updated reactions and counts.
388- ActivityData addReaction (
415+ /// Returns a new [ActivityData] instance with the updated own reactions.
416+ ActivityData upsertUniqueReaction (
417+ ActivityData updatedActivity,
389418 FeedsReactionData reaction,
390419 String currentUserId,
391420 ) {
392- final updatedOwnReactions = switch (reaction.user.id == currentUserId) {
393- true => ownReactions.upsert (reaction, key: (it) => it.id),
394- false => ownReactions,
395- };
396-
397- final updatedLatestReactions = latestReactions.upsert (
421+ return upsertReaction (
422+ updatedActivity,
398423 reaction,
399- key: (reaction) => reaction.id,
424+ currentUserId,
425+ enforceUnique: true ,
400426 );
427+ }
401428
402- final reactionGroup = switch (reactionGroups[ reaction.type]) {
403- final existingGroup ? => existingGroup,
404- _ => ReactionGroupData (
405- count : 1 ,
406- firstReactionAt : reaction.createdAt,
407- lastReactionAt : reaction.createdAt,
408- ),
409- };
410-
411- final updatedReactionGroups = {
412- ...reactionGroups,
413- reaction.type : reactionGroup. increment (reaction.createdAt) ,
414- };
415-
416- final updatedReactionCount = updatedReactionGroups.values. sumOf (
417- (group) => group.count,
418- );
429+ /// Adds or updates a reaction in this activity.
430+ ///
431+ /// Updates the own reactions list by adding or updating [reaction] . Only adds reactions
432+ /// that belong to [currentUserId] . When [enforceUnique] is true, replaces any existing
433+ /// reaction from the same user; otherwise, allows multiple reactions from the same user.
434+ ///
435+ /// Returns a new [ActivityData] instance with the updated own reactions.
436+ ActivityData upsertReaction (
437+ ActivityData updatedActivity,
438+ FeedsReactionData reaction,
439+ String currentUserId, {
440+ bool enforceUnique = false ,
441+ }) {
442+ final updatedOwnReactions = ownReactions. let ((it) {
443+ if (reaction.user.id != currentUserId) return it;
444+ return it. upsertReaction (reaction, enforceUnique : enforceUnique);
445+ } );
419446
420- return copyWith (
421- ownReactions: updatedOwnReactions,
422- latestReactions: updatedLatestReactions,
423- reactionGroups: updatedReactionGroups,
424- reactionCount: updatedReactionCount,
425- );
447+ return updateWith (updatedActivity, ownReactions: updatedOwnReactions);
426448 }
427449
428- /// Removes a reaction from the activity, updating the latest reactions, reaction groups,
429- /// reaction count, and own reactions.
450+ /// Removes a reaction from this activity.
451+ ///
452+ /// Updates the own reactions list by removing [reaction] . Only removes reactions
453+ /// that belong to [currentUserId] .
430454 ///
431- /// @param reaction The reaction to be removed.
432- /// @param currentUserId The ID of the current user, used to determine if the reaction belongs to.
433- /// @return A new [ActivityData] instance with the updated reactions and counts.
455+ /// Returns a new [ActivityData] instance with the updated own reactions.
434456 ActivityData removeReaction (
457+ ActivityData updatedActivity,
435458 FeedsReactionData reaction,
436459 String currentUserId,
437460 ) {
438- final updatedOwnReactions = switch (reaction.user.id == currentUserId) {
439- true => ownReactions.where ((it) => it.id != reaction.id).toList (),
440- false => ownReactions,
441- };
442-
443- final updatedLatestReactions = latestReactions.where ((it) {
444- return it.id != reaction.id;
445- }).toList ();
446-
447- final updatedReactionGroups = {...reactionGroups};
448- final reactionGroup = updatedReactionGroups.remove (reaction.type);
449-
450- if (reactionGroup == null ) {
451- // If there is no reaction group for this type, just update latest and own reactions.
452- // Note: This is only a hypothetical case, as we should always have a reaction group.
453- return copyWith (
454- latestReactions: updatedLatestReactions,
455- ownReactions: updatedOwnReactions,
456- );
457- }
458-
459- final updatedReactionGroup = reactionGroup.decrement (reaction.createdAt);
460- if (updatedReactionGroup.count > 0 ) {
461- updatedReactionGroups[reaction.type] = updatedReactionGroup;
462- }
463-
464- final updatedReactionCount = updatedReactionGroups.values.sumOf (
465- (group) => group.count,
466- );
461+ final updatedOwnReactions = ownReactions.let ((it) {
462+ if (reaction.user.id != currentUserId) return it;
463+ return it.where ((it) => it.id != reaction.id).toList ();
464+ });
467465
468- return copyWith (
469- ownReactions: updatedOwnReactions,
470- latestReactions: updatedLatestReactions,
471- reactionGroups: updatedReactionGroups,
472- reactionCount: updatedReactionCount,
473- );
466+ return updateWith (updatedActivity, ownReactions: updatedOwnReactions);
474467 }
475468}
476469
0 commit comments