Skip to content

Commit 0efe9d1

Browse files
committed
feat(llc): add activity feedback support
This commit introduces the ability to submit feedback for activities, such as marking an activity as hidden. It adds an `activityFeedback` method to the `Activity` and `Feed` classes and handles real-time state updates via WebSocket events. Key changes: - **API**: Added `activityFeedback` method to `ActivitiesRepository`, `Activity`, and `Feed` to allow submitting feedback (e.g., hide/unhide). - **State Management**: Implemented `onActivityHidden` handlers in `ActivityState`, `ActivityListState`, and `FeedState` to update the `hidden` status of activities in response to `ActivityFeedbackEvent` from WebSockets. - **Event Handling**: `ActivityEventHandler`, `ActivityListEventHandler`, and `FeedEventHandler` now process `ActivityFeedbackEvent` to trigger state updates, ensuring the UI reflects whether an activity is hidden. - **Testing**: - Introduced a new suite of test utilities (`BaseTester`, `ActivityTester`, `FeedTester`, `ActivityListTester`, `ApiMockerMixin`) to streamline testing of stateful objects with API and WebSocket interactions. - Added comprehensive tests for the new activity feedback functionality, covering both API calls and real-time event handling. - **Refactor**: Minor refactoring in test helpers (`fakes.dart`) to improve consistency.
1 parent dc37933 commit 0efe9d1

24 files changed

+1389
-113
lines changed

packages/stream_feeds/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## unreleased
22
- [BREAKING] Change `queryFollowSuggestions` return type to `List<FeedSuggestionData>`.
33
- [BREAKING] Remove `activitySelectorOptions` from `FeedQuery`.
4+
- Add `activityFeedback` method to `Feed` and `Activity` for submitting activity feedback.
45
- Add `hidden` and `preview` fields to `ActivityData`.
56
- Update follower and following counts on the feed state when receiving follow websocket events.
67
- Fix FeedsReactionData id for updating reactions in the feed state.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
tags:
2+
feed:
3+
activity:
4+
activity-list:

packages/stream_feeds/lib/src/repository/activities_repository.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,20 @@ class ActivitiesRepository {
248248
return PaginationResult(items: reactions, pagination: pagination);
249249
});
250250
}
251+
252+
/// Submits activity feedback.
253+
///
254+
/// Submits feedback for the activity with the specified [activityId] using
255+
/// the provided [request].
256+
///
257+
/// Returns a [Result] containing void or an error.
258+
Future<Result<void>> activityFeedback(
259+
String activityId,
260+
api.ActivityFeedbackRequest request,
261+
) {
262+
return _api.activityFeedback(
263+
activityId: activityId,
264+
activityFeedbackRequest: request,
265+
);
266+
}
251267
}

packages/stream_feeds/lib/src/state.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export 'state/activity.dart';
22
export 'state/activity_comment_list.dart';
3+
export 'state/activity_list.dart';
34
export 'state/comment_list.dart';
45
export 'state/comment_reaction_list.dart';
56
export 'state/comment_reply_list.dart';

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,13 @@ class Activity with Disposable {
6868
);
6969

7070
// Attach event handlers for real-time updates
71-
final handler = ActivityEventHandler(fid: fid, state: _stateNotifier);
71+
final handler = ActivityEventHandler(
72+
fid: fid,
73+
state: _stateNotifier,
74+
activityId: activityId,
75+
currentUserId: currentUserId,
76+
);
77+
7278
_eventsSubscription = eventsEmitter.listen(handler.handleEvent);
7379
}
7480

@@ -117,6 +123,20 @@ class Activity with Disposable {
117123
return result;
118124
}
119125

126+
/// Submits feedback for this activity.
127+
///
128+
/// Submits feedback for this activity using the provided [request].
129+
///
130+
/// Returns a [Result] indicating success or failure of the operation.
131+
Future<Result<void>> activityFeedback({
132+
required api.ActivityFeedbackRequest activityFeedbackRequest,
133+
}) {
134+
return activitiesRepository.activityFeedback(
135+
activityId,
136+
activityFeedbackRequest,
137+
);
138+
}
139+
120140
/// Queries the comments for this activity.
121141
///
122142
/// Returns a [Result] containing a list of [ThreadedCommentData] or an error.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ class ActivityList with Disposable {
3939
final handler = ActivityListEventHandler(
4040
query: query,
4141
state: _stateNotifier,
42+
currentUserId: currentUserId,
4243
capabilitiesRepository: capabilitiesRepository,
4344
);
45+
4446
_eventsSubscription = eventsEmitter.listen(handler.handleEvent);
4547
}
4648

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ class ActivityListStateNotifier extends StateNotifier<ActivityListState> {
6969
state = state.copyWith(activities: updatedActivities);
7070
}
7171

72+
/// Handles updates to the activity list state when an activity is hidden.
73+
void onActivityHidden({
74+
required String activityId,
75+
required bool hidden,
76+
}) {
77+
final updatedActivities = state.activities.map((activity) {
78+
if (activity.id != activityId) return activity;
79+
// Update the hidden status of the activity
80+
return activity.copyWith(hidden: hidden);
81+
}).toList();
82+
83+
state = state.copyWith(activities: updatedActivities);
84+
}
85+
7286
/// Handles the addition of a bookmark.
7387
void onBookmarkAdded(BookmarkData bookmark) {
7488
final updatedActivities = state.activities.map((activity) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ class ActivityStateNotifier extends StateNotifier<ActivityState> {
4747
);
4848
}
4949

50+
/// Handles updates to the activity's hidden status.
51+
void onActivityHidden({
52+
required bool hidden,
53+
}) {
54+
final currentActivity = state.activity;
55+
final updatedActivity = currentActivity?.copyWith(hidden: hidden);
56+
57+
state = state.copyWith(activity: updatedActivity);
58+
}
59+
5060
/// Handles when a poll is closed.
5161
void onPollClosed(PollData poll) {
5262
if (state.poll?.id != poll.id) return;

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ class ActivityEventHandler implements StateEventHandler {
1717
const ActivityEventHandler({
1818
required this.fid,
1919
required this.state,
20+
required this.activityId,
21+
required this.currentUserId,
2022
});
2123

2224
final FeedId fid;
2325
final ActivityStateNotifier state;
26+
final String activityId;
27+
final String currentUserId;
2428

2529
@override
2630
void handleEvent(WsEvent event) {
@@ -68,6 +72,21 @@ class ActivityEventHandler implements StateEventHandler {
6872
return state.onPollVoteRemoved(vote, poll);
6973
}
7074

75+
if (event is api.ActivityFeedbackEvent) {
76+
final payload = event.activityFeedback;
77+
78+
// Only process events for this activity and current user
79+
if (payload.activityId != activityId) return;
80+
if (payload.user.id != currentUserId) return;
81+
82+
// Only handle hide action for now
83+
if (payload.action == api.ActivityFeedbackEventPayloadAction.hide) {
84+
return state.onActivityHidden(
85+
hidden: payload.value == 'true',
86+
);
87+
}
88+
}
89+
7190
// Handle other activity events here as needed
7291
}
7392
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ class ActivityListEventHandler
2121
const ActivityListEventHandler({
2222
required this.query,
2323
required this.state,
24+
required this.currentUserId,
2425
required this.capabilitiesRepository,
2526
});
2627

2728
final ActivitiesQuery query;
2829
final ActivityListStateNotifier state;
30+
final String currentUserId;
2931

3032
@override
3133
final CapabilitiesRepository capabilitiesRepository;
@@ -112,6 +114,21 @@ class ActivityListEventHandler
112114
return state.onCommentRemoved(event.comment.toModel());
113115
}
114116

117+
if (event is api.ActivityFeedbackEvent) {
118+
final payload = event.activityFeedback;
119+
120+
// Only process events for the current user
121+
if (payload.user.id != currentUserId) return;
122+
123+
// Only handle hide action for now
124+
if (payload.action == api.ActivityFeedbackEventPayloadAction.hide) {
125+
return state.onActivityHidden(
126+
activityId: payload.activityId,
127+
hidden: payload.value == 'true',
128+
);
129+
}
130+
}
131+
115132
// Handle other activity list events here as needed
116133
}
117134
}

0 commit comments

Comments
 (0)