Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ command:
shared_preferences: ^2.5.3
state_notifier: ^1.0.0
stream_feeds: ^0.4.0
stream_core: ^0.3.0
stream_core: ^0.3.1
video_player: ^2.10.0
uuid: ^4.5.1

Expand Down
1 change: 0 additions & 1 deletion packages/stream_feeds/lib/src/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,4 @@ export 'models/request/activity_update_comment_request.dart'
show ActivityUpdateCommentRequest;
export 'models/request/feed_add_activity_request.dart'
show FeedAddActivityRequest;
export 'models/threaded_comment_data.dart';
export 'models/user_data.dart';
221 changes: 107 additions & 114 deletions packages/stream_feeds/lib/src/models/activity_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -309,168 +309,161 @@ extension ActivityResponseMapper on ActivityResponse {

/// Extension functions for [ActivityData] to handle common operations.
extension ActivityDataMutations on ActivityData {
/// Adds a comment to the activity, updating the comment count and the list of comments.
/// Updates this activity with new data while preserving own data.
///
/// @param comment The comment to be added.
/// @return A new [ActivityData] instance with the updated comments and comment count.
ActivityData addComment(CommentData comment) {
final updatedComments = comments.upsert(comment, key: (it) => it.id);
/// Merges [updated] activity data with this instance, preserving [ownBookmarks] and
/// [ownReactions] from this instance when not provided. This ensures that user-specific
/// data is not lost when updating from WebSocket events.
///
/// Returns a new [ActivityData] instance with the merged data.
ActivityData updateWith(
ActivityData updated, {
List<BookmarkData>? ownBookmarks,
List<FeedsReactionData>? ownReactions,
}) {
return updated.copyWith(
// Preserve own data from the current instance if not provided
// as they may not be reliable from WS events.
ownBookmarks: ownBookmarks ?? this.ownBookmarks,
ownReactions: ownReactions ?? this.ownReactions,
poll: updated.poll?.let((it) => poll?.updateWith(it) ?? it),
);
}

/// Adds or updates a comment in this activity.
///
/// Updates the comments list by adding or updating [comment]. If the comment already
/// exists, it will be updated. The comment count is automatically recalculated.
///
/// Returns a new [ActivityData] instance with the updated comments and comment count.
ActivityData upsertComment(CommentData comment) {
final currentComments = [...comments];
final updatedComments = currentComments.upsert(comment, key: (it) => it.id);

final difference = updatedComments.length - currentComments.length;
final updatedCommentCount = math.max(0, commentCount + difference);

return copyWith(
comments: updatedComments,
commentCount: math.max(0, commentCount + 1),
commentCount: updatedCommentCount,
);
}

/// Removes a comment from the activity, updating the comment count and the list of comments.
/// Removes a comment from this activity.
///
/// Updates the comments list by removing [comment]. The comment count is automatically
/// recalculated.
///
/// @param comment The comment to be removed.
/// @return A new [ActivityData] instance with the updated comments and comment count.
/// Returns a new [ActivityData] instance with the updated comments and comment count.
ActivityData removeComment(CommentData comment) {
final updatedComments = comments.where((it) {
final currentComments = [...comments];
final updatedComments = currentComments.where((it) {
return it.id != comment.id;
}).toList();

final difference = updatedComments.length - currentComments.length;
final updatedCommentCount = math.max(0, commentCount + difference);

return copyWith(
comments: updatedComments,
commentCount: math.max(0, commentCount - 1),
commentCount: updatedCommentCount,
);
}

/// Adds a bookmark to the activity, updating the own bookmarks and bookmark count.
/// Adds or updates a bookmark in this activity.
///
/// Updates the own bookmarks list by adding or updating [bookmark]. Only adds bookmarks
/// that belong to [currentUserId]. If the bookmark already exists, it will be updated.
///
/// @param bookmark The bookmark to be added.
/// @param currentUserId The ID of the current user, used to determine if the bookmark belongs to
/// them.
/// @return A new [ActivityData] instance with the updated own bookmarks and bookmark count.
ActivityData addBookmark(
/// Returns a new [ActivityData] instance with the updated own bookmarks and bookmark count.
ActivityData upsertBookmark(
BookmarkData bookmark,
String currentUserId,
) {
final updatedOwnBookmarks = switch (bookmark.user.id == currentUserId) {
true => ownBookmarks.upsert(bookmark, key: (it) => it.id),
false => ownBookmarks,
};
final updatedOwnBookmarks = ownBookmarks.let((it) {
if (bookmark.user.id != currentUserId) return it;
return it.upsert(bookmark, key: (it) => it.id);
});

return copyWith(
ownBookmarks: updatedOwnBookmarks,
bookmarkCount: math.max(0, bookmarkCount + 1),
);
return updateWith(bookmark.activity, ownBookmarks: updatedOwnBookmarks);
}

/// Removes a bookmark from the activity, updating the own bookmarks and bookmark count.
/// Removes a bookmark from this activity.
///
/// @param bookmark The bookmark to be deleted.
/// @param currentUserId The ID of the current user, used to determine if the bookmark belongs to
/// them.
/// @return A new [ActivityData] instance with the updated own bookmarks and bookmark count.
/// Updates the own bookmarks list by removing [bookmark]. Only removes bookmarks
/// that belong to [currentUserId].
///
/// Returns a new [ActivityData] instance with the updated own bookmarks and bookmark count.
ActivityData removeBookmark(
BookmarkData bookmark,
String currentUserId,
) {
final updatedOwnBookmarks = switch (bookmark.user.id == currentUserId) {
true => ownBookmarks.where((it) => it.id != bookmark.id).toList(),
false => ownBookmarks,
};
final updatedOwnBookmarks = ownBookmarks.let((it) {
if (bookmark.user.id != currentUserId) return it;
return it.where((it) => it.id != bookmark.id).toList();
});

return copyWith(
ownBookmarks: updatedOwnBookmarks,
bookmarkCount: math.max(0, bookmarkCount - 1),
);
return updateWith(bookmark.activity, ownBookmarks: updatedOwnBookmarks);
}

/// Adds a reaction to the activity, updating the latest reactions, reaction groups,
/// reaction count, and own reactions.
/// Adds or updates a reaction in this activity with unique enforcement.
///
/// Updates the own reactions list by adding or updating [reaction]. Only adds reactions
/// that belong to [currentUserId]. When unique enforcement is enabled, replaces any
/// existing reaction from the same user.
///
/// @param reaction The reaction to be added.
/// @param currentUserId The ID of the current user, used to determine if the reaction belongs to.
/// @return A new [ActivityData] instance with the updated reactions and counts.
ActivityData addReaction(
/// Returns a new [ActivityData] instance with the updated own reactions.
ActivityData upsertUniqueReaction(
ActivityData updatedActivity,
FeedsReactionData reaction,
String currentUserId,
) {
final updatedOwnReactions = switch (reaction.user.id == currentUserId) {
true => ownReactions.upsert(reaction, key: (it) => it.id),
false => ownReactions,
};

final updatedLatestReactions = latestReactions.upsert(
return upsertReaction(
updatedActivity,
reaction,
key: (reaction) => reaction.id,
currentUserId,
enforceUnique: true,
);
}

final reactionGroup = switch (reactionGroups[reaction.type]) {
final existingGroup? => existingGroup,
_ => ReactionGroupData(
count: 1,
firstReactionAt: reaction.createdAt,
lastReactionAt: reaction.createdAt,
),
};

final updatedReactionGroups = {
...reactionGroups,
reaction.type: reactionGroup.increment(reaction.createdAt),
};

final updatedReactionCount = updatedReactionGroups.values.sumOf(
(group) => group.count,
);
/// Adds or updates a reaction in this activity.
///
/// Updates the own reactions list by adding or updating [reaction]. Only adds reactions
/// that belong to [currentUserId]. When [enforceUnique] is true, replaces any existing
/// reaction from the same user; otherwise, allows multiple reactions from the same user.
///
/// Returns a new [ActivityData] instance with the updated own reactions.
ActivityData upsertReaction(
ActivityData updatedActivity,
FeedsReactionData reaction,
String currentUserId, {
bool enforceUnique = false,
}) {
final updatedOwnReactions = ownReactions.let((it) {
if (reaction.user.id != currentUserId) return it;
return it.upsertReaction(reaction, enforceUnique: enforceUnique);
});

return copyWith(
ownReactions: updatedOwnReactions,
latestReactions: updatedLatestReactions,
reactionGroups: updatedReactionGroups,
reactionCount: updatedReactionCount,
);
return updateWith(updatedActivity, ownReactions: updatedOwnReactions);
}

/// Removes a reaction from the activity, updating the latest reactions, reaction groups,
/// reaction count, and own reactions.
/// Removes a reaction from this activity.
///
/// Updates the own reactions list by removing [reaction]. Only removes reactions
/// that belong to [currentUserId].
///
/// @param reaction The reaction to be removed.
/// @param currentUserId The ID of the current user, used to determine if the reaction belongs to.
/// @return A new [ActivityData] instance with the updated reactions and counts.
/// Returns a new [ActivityData] instance with the updated own reactions.
ActivityData removeReaction(
ActivityData updatedActivity,
FeedsReactionData reaction,
String currentUserId,
) {
final updatedOwnReactions = switch (reaction.user.id == currentUserId) {
true => ownReactions.where((it) => it.id != reaction.id).toList(),
false => ownReactions,
};

final updatedLatestReactions = latestReactions.where((it) {
return it.id != reaction.id;
}).toList();

final updatedReactionGroups = {...reactionGroups};
final reactionGroup = updatedReactionGroups.remove(reaction.type);

if (reactionGroup == null) {
// If there is no reaction group for this type, just update latest and own reactions.
// Note: This is only a hypothetical case, as we should always have a reaction group.
return copyWith(
latestReactions: updatedLatestReactions,
ownReactions: updatedOwnReactions,
);
}

final updatedReactionGroup = reactionGroup.decrement(reaction.createdAt);
if (updatedReactionGroup.count > 0) {
updatedReactionGroups[reaction.type] = updatedReactionGroup;
}

final updatedReactionCount = updatedReactionGroups.values.sumOf(
(group) => group.count,
);
final updatedOwnReactions = ownReactions.let((it) {
if (reaction.user.id != currentUserId) return it;
return it.where((it) => it.id != reaction.id).toList();
});

return copyWith(
ownReactions: updatedOwnReactions,
latestReactions: updatedLatestReactions,
reactionGroups: updatedReactionGroups,
reactionCount: updatedReactionCount,
);
return updateWith(updatedActivity, ownReactions: updatedOwnReactions);
}
}

Expand Down
Loading
Loading