Skip to content

Commit cb4fa2a

Browse files
committed
improve on reaction added
1 parent 0744eb3 commit cb4fa2a

File tree

10 files changed

+324
-5
lines changed

10 files changed

+324
-5
lines changed

packages/stream_feeds/lib/src/client/feeds_client_impl.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ class StreamFeedsClientImpl implements StreamFeedsClient {
383383
query: query,
384384
commentsRepository: _commentsRepository,
385385
eventsEmitter: events,
386+
currentUserId: user.id,
386387
);
387388
}
388389

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

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// ignore_for_file: avoid_redundant_argument_values
22

33
import 'package:freezed_annotation/freezed_annotation.dart';
4+
import 'package:stream_core/stream_core.dart';
45

56
import '../generated/api/models.dart';
67
import '../state/query/comments_query.dart';
@@ -197,3 +198,99 @@ extension CommentResponseMapper on CommentResponse {
197198
);
198199
}
199200
}
201+
202+
extension CommentDataMutations on CommentData {
203+
/// Adds a reaction to the comment, updating the latest reactions, reaction groups, reaction count,
204+
/// and own reactions if applicable.
205+
///
206+
/// @param reaction The reaction to add.
207+
/// @param currentUserId The ID of the current user, used to update own reactions.
208+
/// @return A new [CommentData] instance with the updated reaction data.
209+
CommentData addReaction(
210+
FeedsReactionData reaction,
211+
String currentUserId,
212+
) {
213+
final updatedOwnReactions = switch (reaction.user.id == currentUserId) {
214+
true => ownReactions.upsert(reaction, key: (it) => it.id),
215+
false => ownReactions,
216+
};
217+
218+
final updatedLatestReactions = latestReactions.upsert(
219+
reaction,
220+
key: (reaction) => reaction.id,
221+
);
222+
223+
final reactionGroup = switch (reactionGroups[reaction.type]) {
224+
final existingGroup? => existingGroup,
225+
_ => ReactionGroupData(
226+
count: 1,
227+
firstReactionAt: reaction.createdAt,
228+
lastReactionAt: reaction.createdAt,
229+
),
230+
};
231+
232+
final updatedReactionGroups = {
233+
...reactionGroups,
234+
reaction.type: reactionGroup.increment(reaction.createdAt),
235+
};
236+
237+
final updatedReactionCount = updatedReactionGroups.values.sumOf(
238+
(group) => group.count,
239+
);
240+
241+
return copyWith(
242+
ownReactions: updatedOwnReactions,
243+
latestReactions: updatedLatestReactions,
244+
reactionGroups: updatedReactionGroups,
245+
reactionCount: updatedReactionCount,
246+
);
247+
}
248+
249+
/// Removes a reaction from the comment, updating the latest reactions, reaction groups, reaction
250+
/// count, and own reactions if applicable.
251+
///
252+
/// @param reaction The reaction to remove.
253+
/// @param currentUserId The ID of the current user, used to update own reactions.
254+
/// @return A new [CommentData] instance with the updated reaction data.
255+
CommentData removeReaction(
256+
FeedsReactionData reaction,
257+
String currentUserId,
258+
) {
259+
final updatedOwnReactions = switch (reaction.user.id == currentUserId) {
260+
true => ownReactions.where((it) => it.id != reaction.id).toList(),
261+
false => ownReactions,
262+
};
263+
264+
final updatedLatestReactions = latestReactions.where((it) {
265+
return it.id != reaction.id;
266+
}).toList(growable: false);
267+
268+
final updatedReactionGroups = {...reactionGroups};
269+
final reactionGroup = updatedReactionGroups.remove(reaction.type);
270+
271+
if (reactionGroup == null) {
272+
// If there is no reaction group for this type, just update latest and own reactions.
273+
// Note: This is only a hypothetical case, as we should always have a reaction group.
274+
return copyWith(
275+
latestReactions: updatedLatestReactions,
276+
ownReactions: updatedOwnReactions,
277+
);
278+
}
279+
280+
final updatedReactionGroup = reactionGroup.decrement(reaction.createdAt);
281+
if (updatedReactionGroup.count > 0) {
282+
updatedReactionGroups[reaction.type] = updatedReactionGroup;
283+
}
284+
285+
final updatedReactionCount = updatedReactionGroups.values.sumOf(
286+
(group) => group.count,
287+
);
288+
289+
return copyWith(
290+
ownReactions: updatedOwnReactions,
291+
latestReactions: updatedLatestReactions,
292+
reactionGroups: updatedReactionGroups,
293+
reactionCount: updatedReactionCount,
294+
);
295+
}
296+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class FeedsReactionData with _$FeedsReactionData {
2626
@override
2727
final String activityId;
2828

29+
/// The ID of the comment this reaction is associated with.
30+
@override
2931
final String? commentId;
3032

3133
/// The date and time when the reaction was created.

packages/stream_feeds/lib/src/models/feeds_reaction_data.freezed.dart

Lines changed: 12 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/stream_feeds/lib/src/state/comment_list.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ class CommentList extends Disposable {
2525
required this.query,
2626
required this.commentsRepository,
2727
required this.eventsEmitter,
28+
required this.currentUserId,
2829
}) {
2930
_stateNotifier = CommentListStateNotifier(
3031
initialState: const CommentListState(),
32+
currentUserId: currentUserId,
3133
);
3234

3335
// Attach event handlers for real-time updates
@@ -41,6 +43,7 @@ class CommentList extends Disposable {
4143

4244
final CommentsQuery query;
4345
final CommentsRepository commentsRepository;
46+
final String currentUserId;
4447

4548
CommentListState get state => stateNotifier.value;
4649
StateNotifier<CommentListState> get notifier => stateNotifier;

packages/stream_feeds/lib/src/state/comment_list_state.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:state_notifier/state_notifier.dart';
33
import 'package:stream_core/stream_core.dart';
44

55
import '../models/comment_data.dart';
6+
import '../models/feeds_reaction_data.dart';
67
import '../models/pagination_data.dart';
78
import 'query/comments_query.dart';
89

@@ -15,8 +16,10 @@ part 'comment_list_state.freezed.dart';
1516
class CommentListStateNotifier extends StateNotifier<CommentListState> {
1617
CommentListStateNotifier({
1718
required CommentListState initialState,
19+
required this.currentUserId,
1820
}) : super(initialState);
1921

22+
final String currentUserId;
2023
({Filter? filter, CommentsSort? sort})? _queryConfig;
2124
CommentsSort get commentSort => _queryConfig?.sort ?? CommentsSort.last;
2225

@@ -59,6 +62,32 @@ class CommentListStateNotifier extends StateNotifier<CommentListState> {
5962

6063
state = state.copyWith(comments: updatedComments);
6164
}
65+
66+
/// Handles the addition of a reaction to a comment.
67+
void onCommentReactionAdded(String commentId, FeedsReactionData reaction) {
68+
final updatedComments = state.comments.updateNested(
69+
(comment) => comment.id == commentId,
70+
children: (it) => it.replies ?? [],
71+
update: (found) => found.addReaction(reaction, currentUserId),
72+
updateChildren: (parent, replies) => parent.copyWith(replies: replies),
73+
compare: commentSort.compare,
74+
);
75+
76+
state = state.copyWith(comments: updatedComments);
77+
}
78+
79+
/// Handles the removal of a reaction from a comment.
80+
void onCommentReactionRemoved(String commentId, FeedsReactionData reaction) {
81+
final updatedComments = state.comments.updateNested(
82+
(comment) => comment.id == commentId,
83+
children: (it) => it.replies ?? [],
84+
update: (found) => found.removeReaction(reaction, currentUserId),
85+
updateChildren: (parent, replies) => parent.copyWith(replies: replies),
86+
compare: commentSort.compare,
87+
);
88+
89+
state = state.copyWith(comments: updatedComments);
90+
}
6291
}
6392

6493
/// An observable state object that manages the current state of a comment list.

packages/stream_feeds/lib/src/state/event/comment_list_event_handler.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:stream_core/stream_core.dart';
33
import '../../generated/api/models.dart' as api;
44

55
import '../../models/comment_data.dart';
6+
import '../../models/feeds_reaction_data.dart';
67
import '../comment_list_state.dart';
78
import '../query/comments_query.dart';
89
import 'state_event_handler.dart';
@@ -41,6 +42,20 @@ class CommentListEventHandler implements StateEventHandler {
4142
return state.onCommentRemoved(event.comment.id);
4243
}
4344

45+
if (event is api.CommentReactionAddedEvent) {
46+
return state.onCommentReactionAdded(
47+
event.comment.id,
48+
event.reaction.toModel(),
49+
);
50+
}
51+
52+
if (event is api.CommentReactionDeletedEvent) {
53+
return state.onCommentReactionRemoved(
54+
event.comment.id,
55+
event.reaction.toModel(),
56+
);
57+
}
58+
4459
// Handle other comment-related events if needed
4560
}
4661
}

0 commit comments

Comments
 (0)