Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
57 changes: 57 additions & 0 deletions docs/code_snippets/05_06_notification_feeds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,60 @@ Future<void> markNotificationsAsRead() async {
),
);
}

/// New notification types: `mention` and `comment_mention`.
///
/// These are created by the backend automatically when a user is mentioned in
/// an activity or a comment. They appear as notification activities alongside
/// the standard types (follow, reaction, comment).
Future<void> readMentionNotifications() async {
final notificationFeed = client.feed(group: 'notification', id: 'jane');
await notificationFeed.getOrCreate();

for (final group in notificationFeed.state.aggregatedActivities) {
for (final activity in group.activities) {
switch (activity.type) {
case 'mention':
print('${activity.user.name ?? 'Someone'} mentioned you in an activity');
case 'comment_mention':
print('${activity.user.name ?? 'Someone'} mentioned you in a comment');
default:
print('Notification type: ${activity.type}');
}
}
}
}

/// Delete a notification activity together with its source (activity, comment,
/// or reaction) by passing `deleteNotificationActivity: true`.
///
/// This removes both the original item and the notification it created, which
/// is useful when a user deletes content they no longer want to appear in
/// others' notification feeds.
Future<void> deleteWithNotification() async {
// Delete an activity and its notification
await feed.deleteActivity(
id: 'activity_123',
deleteNotificationActivity: true,
);

// Delete a comment and its notification
await feed.deleteComment(
commentId: 'comment_456',
deleteNotificationActivity: true,
);

// Delete a reaction and its notification
await feed.deleteActivityReaction(
activityId: 'activity_123',
type: 'like',
deleteNotificationActivity: true,
);

// Delete a comment reaction and its notification
await feed.deleteCommentReaction(
commentId: 'comment_456',
type: 'like',
deleteNotificationActivity: true,
);
}
1 change: 1 addition & 0 deletions packages/stream_feeds/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Added `bookmarkCount` and `editedAt` fields to `CommentData`.
- Added `location` (`LocationCoordinate?`) field to `FeedData`.
- Added `createNotificationActivity`, `skipPush`, and `enrichOwnFields` optional flags to `FeedAddActivityRequest`.
- Added optional `deleteNotificationActivity` parameter to `Feed.deleteActivity`, `Feed.deleteComment`, `Feed.deleteActivityReaction`, `Feed.deleteCommentReaction`, `Activity.deleteComment`, and `Activity.deleteCommentReaction` — when `true`, the corresponding notification activity is also deleted.

### WebSocket events
- `ActivityRestoredEvent` and `CommentRestoredEvent` are now handled: restored items are upserted back into feed/list state.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ class ActivitiesRepository {
Future<Result<void>> deleteActivity(
String activityId, {
required bool hardDelete,
bool? deleteNotificationActivity,
}) {
return _api.deleteActivity(
id: activityId,
hardDelete: hardDelete,
deleteNotificationActivity: deleteNotificationActivity,
);
}

Expand Down Expand Up @@ -245,11 +247,13 @@ class ActivitiesRepository {
/// was removed, and the `reaction` field contains the deleted [FeedsReactionData].
Future<Result<({ActivityData activity, FeedsReactionData reaction})>> deleteActivityReaction(
String activityId,
String type,
) async {
String type, {
bool? deleteNotificationActivity,
}) async {
final result = await _api.deleteActivityReaction(
activityId: activityId,
type: type,
deleteNotificationActivity: deleteNotificationActivity,
);

return result.map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,12 @@ class CommentsRepository {
Future<Result<({ActivityData activity, CommentData comment})>> deleteComment(
String commentId, {
bool? hardDelete,
bool? deleteNotificationActivity,
}) async {
final result = await _api.deleteComment(
id: commentId,
hardDelete: hardDelete,
deleteNotificationActivity: deleteNotificationActivity,
);

return result.map(
Expand Down Expand Up @@ -208,9 +210,14 @@ class CommentsRepository {
/// [FeedsReactionData] or an error.
Future<Result<({CommentData comment, FeedsReactionData reaction})>> deleteCommentReaction(
String commentId,
String type,
) async {
final result = await _api.deleteCommentReaction(id: commentId, type: type);
String type, {
bool? deleteNotificationActivity,
}) async {
final result = await _api.deleteCommentReaction(
id: commentId,
type: type,
deleteNotificationActivity: deleteNotificationActivity,
);

return result.map(
(response) => (
Expand Down
8 changes: 6 additions & 2 deletions packages/stream_feeds/lib/src/state/activity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,12 @@ class Activity with Disposable {
Future<Result<void>> deleteComment(
String commentId, {
bool? hardDelete,
bool? deleteNotificationActivity,
}) async {
final result = await commentsRepository.deleteComment(
commentId,
hardDelete: hardDelete,
deleteNotificationActivity: deleteNotificationActivity,
);

result.onSuccess(
Expand Down Expand Up @@ -303,11 +305,13 @@ class Activity with Disposable {
/// Returns a [Result] containing the removed [FeedsReactionData] or an error.
Future<Result<FeedsReactionData>> deleteCommentReaction(
String commentId,
String type,
) async {
String type, {
bool? deleteNotificationActivity,
}) async {
final result = await commentsRepository.deleteCommentReaction(
commentId,
type,
deleteNotificationActivity: deleteNotificationActivity,
);

result.onSuccess(
Expand Down
16 changes: 14 additions & 2 deletions packages/stream_feeds/lib/src/state/feed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,12 @@ class Feed with Disposable {
Future<Result<void>> deleteActivity({
required String id,
bool hardDelete = false,
bool? deleteNotificationActivity,
}) async {
final result = await activitiesRepository.deleteActivity(
id,
hardDelete: hardDelete,
deleteNotificationActivity: deleteNotificationActivity,
);

result.onSuccess(
Expand Down Expand Up @@ -469,8 +471,14 @@ class Feed with Disposable {
///
/// The [commentId] is the unique identifier of the comment to remove.
/// Returns a [Result] indicating success or failure of the deletion operation.
Future<Result<void>> deleteComment({required String commentId}) async {
final result = await commentsRepository.deleteComment(commentId);
Future<Result<void>> deleteComment({
required String commentId,
bool? deleteNotificationActivity,
}) async {
final result = await commentsRepository.deleteComment(
commentId,
deleteNotificationActivity: deleteNotificationActivity,
);

result.onSuccess(
(pair) {
Expand Down Expand Up @@ -791,10 +799,12 @@ class Feed with Disposable {
Future<Result<FeedsReactionData>> deleteActivityReaction({
required String activityId,
required String type,
bool? deleteNotificationActivity,
}) async {
final result = await activitiesRepository.deleteActivityReaction(
activityId,
type,
deleteNotificationActivity: deleteNotificationActivity,
);

result.onSuccess(
Expand Down Expand Up @@ -848,10 +858,12 @@ class Feed with Disposable {
Future<Result<FeedsReactionData>> deleteCommentReaction({
required String commentId,
required String type,
bool? deleteNotificationActivity,
}) async {
final result = await commentsRepository.deleteCommentReaction(
commentId,
type,
deleteNotificationActivity: deleteNotificationActivity,
);

result.onSuccess(
Expand Down
101 changes: 101 additions & 0 deletions packages/stream_feeds/test/state/activity_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,51 @@ void main() {
),
);

activityTest(
'deleteComment - should pass deleteNotificationActivity to API',
build: (client) => client.activity(activityId: activityId, fid: fid),
setUp: (tester) => tester.get(
modifyCommentsResponse: (response) => response.copyWith(
comments: [
createDefaultThreadedCommentResponse(
id: commentId,
objectId: activityId,
objectType: 'activity',
text: 'Test comment',
userId: userId,
),
],
),
),
body: (tester) async {
tester.mockApi(
(api) => api.deleteComment(
id: commentId,
deleteNotificationActivity: true,
),
result: createDefaultDeleteCommentResponse(
commentId: commentId,
activityId: activityId,
objectId: activityId,
userId: userId,
),
);

final result = await tester.activity.deleteComment(
commentId,
deleteNotificationActivity: true,
);

expect(result.isSuccess, isTrue);
},
verify: (tester) => tester.verifyApi(
(api) => api.deleteComment(
id: commentId,
deleteNotificationActivity: true,
),
),
);

activityTest(
'updateComment - should update comment via API',
build: (client) => client.activity(activityId: activityId, fid: fid),
Expand Down Expand Up @@ -1011,6 +1056,62 @@ void main() {
),
),
);

activityTest(
'deleteCommentReaction - should pass deleteNotificationActivity to API',
build: (client) => client.activity(activityId: activityId, fid: fid),
setUp: (tester) => tester.get(
modifyCommentsResponse: (response) => response.copyWith(
comments: [
createDefaultThreadedCommentResponse(
id: commentId,
objectId: activityId,
objectType: 'activity',
text: 'Test comment',
userId: userId,
ownReactions: [
createDefaultReactionResponse(
reactionType: reactionType,
userId: userId,
activityId: activityId,
commentId: commentId,
),
],
),
],
),
),
body: (tester) async {
tester.mockApi(
(api) => api.deleteCommentReaction(
id: commentId,
type: reactionType,
deleteNotificationActivity: true,
),
result: createDefaultDeleteCommentReactionResponse(
commentId: commentId,
objectId: activityId,
userId: userId,
reactionType: reactionType,
),
);

final result = await tester.activity.deleteCommentReaction(
commentId,
reactionType,
deleteNotificationActivity: true,
);

expect(result.isSuccess, isTrue);
},
verify: (tester) => tester.verifyApi(
(api) => api.deleteCommentReaction(
id: commentId,
type: reactionType,
deleteNotificationActivity: true,
),
),
);
});

// ============================================================
Expand Down
Loading
Loading