Skip to content

Commit 4bbd1dc

Browse files
authored
refactor(llc)!: unify comment data model and add tests (#64)
1 parent b3e9754 commit 4bbd1dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+6410
-1325
lines changed

melos.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ command:
4848
shared_preferences: ^2.5.3
4949
state_notifier: ^1.0.0
5050
stream_feeds: ^0.4.0
51-
stream_core: ^0.3.0
51+
stream_core: ^0.3.1
5252
video_player: ^2.10.0
5353
uuid: ^4.5.1
5454

packages/stream_feeds/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Upcoming
2+
- [BREAKING] Unified `ThreadedCommentData` into `CommentData` to handle both flat and threaded comments.
3+
- Add support for `enforceUnique` parameter while adding reactions.
4+
15
## 0.4.0
26
- [BREAKING] Change `queryFollowSuggestions` return type to `List<FeedSuggestionData>`.
37
- [BREAKING] Remove `activitySelectorOptions` from `FeedQuery`.

packages/stream_feeds/lib/src/models.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,4 @@ export 'models/request/activity_update_comment_request.dart'
2121
show ActivityUpdateCommentRequest;
2222
export 'models/request/feed_add_activity_request.dart'
2323
show FeedAddActivityRequest;
24-
export 'models/threaded_comment_data.dart';
2524
export 'models/user_data.dart';

packages/stream_feeds/lib/src/models/activity_data.dart

Lines changed: 107 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -309,168 +309,161 @@ extension ActivityResponseMapper on ActivityResponse {
309309

310310
/// Extension functions for [ActivityData] to handle common operations.
311311
extension 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

Comments
 (0)