Skip to content

Commit 3999d71

Browse files
renefloorxsahil03x
andauthored
feat(llc): add ownFollows on feed data (#69)
Co-authored-by: Sahil Kumar <[email protected]>
1 parent 3bc9565 commit 3999d71

File tree

8 files changed

+165
-26
lines changed

8 files changed

+165
-26
lines changed

packages/stream_feeds/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Add location filtering support for activities with `ActivitiesFilterField.near` and `ActivitiesFilterField.withinBounds` filter fields.
1010
- Add new activity filter fields: `ActivitiesFilterField.feed` and `ActivitiesFilterField.interestTags`.
1111
- Export previously missing public APIs: models, state objects, and queries.
12+
- Add `ownFollows` field to `FeedData` to store the follow relationships of the current user in the feed.
1213

1314
## 0.4.0
1415
- [BREAKING] Change `queryFollowSuggestions` return type to `List<FeedSuggestionData>`.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,12 @@ extension ActivityDataMutations on ActivityData {
332332
ownBookmarks: ownBookmarks ?? this.ownBookmarks,
333333
ownReactions: ownReactions ?? this.ownReactions,
334334
poll: updated.poll?.let((it) => poll?.updateWith(it) ?? it),
335+
// Workaround until the backend fixes the issue with missing currentFeed
336+
// in some WS events
337+
currentFeed: switch (updated.currentFeed) {
338+
final it? => currentFeed?.updateWith(it) ?? it,
339+
_ => currentFeed,
340+
},
335341
);
336342
}
337343

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
33
import '../generated/api/models.dart';
44
import 'feed_id.dart';
55
import 'feed_member_data.dart';
6+
import 'follow_data.dart';
67
import 'user_data.dart';
78

89
part 'feed_data.freezed.dart';
@@ -30,6 +31,7 @@ class FeedData with _$FeedData {
3031
required this.name,
3132
required this.ownCapabilities,
3233
this.ownMembership,
34+
this.ownFollows,
3335
required this.pinCount,
3436
required this.updatedAt,
3537
this.visibility,
@@ -92,6 +94,10 @@ class FeedData with _$FeedData {
9294
@override
9395
final FeedMemberData? ownMembership;
9496

97+
/// The follow relationships of the current user in the feed.
98+
@override
99+
final List<FollowData>? ownFollows;
100+
95101
/// The number of pinned items in the feed.
96102
@override
97103
final int pinCount;
@@ -131,10 +137,36 @@ extension FeedResponseMapper on FeedResponse {
131137
name: name,
132138
ownCapabilities: ownCapabilities ?? const [],
133139
ownMembership: ownMembership?.toModel(),
140+
ownFollows: ownFollows?.map((f) => f.toModel()).toList(),
134141
pinCount: pinCount,
135142
updatedAt: updatedAt,
136143
visibility: visibility,
137144
custom: custom,
138145
);
139146
}
140147
}
148+
149+
/// Extension functions for [FeedData] to handle common operations.
150+
extension FeedDataMutations on FeedData {
151+
/// Updates this feed with new data while preserving own data.
152+
///
153+
/// Merges [updated] feed data with this instance, preserving [ownCapabilities],
154+
/// [ownMembership], and [ownFollows] from this instance when not provided. This
155+
/// ensures that user-specific data is not lost when updating from WebSocket events.
156+
///
157+
/// Returns a new [FeedData] instance with the merged data.
158+
FeedData updateWith(
159+
FeedData updated, {
160+
List<FeedOwnCapability>? ownCapabilities,
161+
FeedMemberData? ownMembership,
162+
List<FollowData>? ownFollows,
163+
}) {
164+
return updated.copyWith(
165+
// Preserve own data from the current instance if not provided
166+
// as they may not be reliable from WS events.
167+
ownCapabilities: ownCapabilities ?? this.ownCapabilities,
168+
ownMembership: ownMembership ?? this.ownMembership,
169+
ownFollows: ownFollows ?? this.ownFollows,
170+
);
171+
}
172+
}

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

Lines changed: 32 additions & 21 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/feed_list_state.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,12 @@ class FeedListStateNotifier extends StateNotifier<FeedListState> {
4545

4646
/// Handles updates to a specific feed.
4747
void onFeedUpdated(FeedData feed) {
48-
final updatedFeeds = state.feeds.map((it) {
49-
if (it.fid.rawValue != feed.fid.rawValue) return it;
50-
return feed;
51-
}).toList();
48+
final updatedFeeds = state.feeds.sortedUpsert(
49+
feed,
50+
key: (it) => it.fid.rawValue,
51+
compare: feedsSort.compare,
52+
update: (existing, updated) => existing.updateWith(updated),
53+
);
5254

5355
state = state.copyWith(feeds: updatedFeeds);
5456
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,11 @@ class FeedStateNotifier extends StateNotifier<FeedState> {
341341

342342
/// Handles updates to the feed state when the feed is updated.
343343
void onFeedUpdated(FeedData feed) {
344+
final currentFeed = state.feed;
345+
final updatedFeed = currentFeed?.updateWith(feed) ?? feed;
346+
344347
// Update the feed data in the state
345-
state = state.copyWith(feed: feed);
348+
state = state.copyWith(feed: updatedFeed);
346349
}
347350

348351
/// Handles updates to the feed state when a follow is added.

packages/stream_feeds/test/state/feed_test.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,4 +2102,82 @@ void main() {
21022102
},
21032103
);
21042104
});
2105+
2106+
// ============================================================
2107+
// FEATURE: Feed Updated Event
2108+
// ============================================================
2109+
2110+
group('FeedUpdatedEvent', () {
2111+
const feedId = FeedId(group: 'user', id: 'john');
2112+
const currentUser = User(id: 'luke_skywalker');
2113+
2114+
feedTest(
2115+
'should preserve own fields when feed is updated',
2116+
user: currentUser,
2117+
build: (client) => client.feedFromId(feedId),
2118+
setUp: (tester) => tester.getOrCreate(
2119+
modifyResponse: (it) => it.copyWith(
2120+
feed: createDefaultFeedResponse(
2121+
id: feedId.id,
2122+
groupId: feedId.group,
2123+
ownCapabilities: [
2124+
FeedOwnCapability.createFeed,
2125+
FeedOwnCapability.deleteFeed,
2126+
],
2127+
ownMembership: createDefaultFeedMemberResponse(
2128+
id: currentUser.id,
2129+
role: 'admin',
2130+
),
2131+
ownFollows: [
2132+
createDefaultFollowResponse(id: 'follow-1'),
2133+
createDefaultFollowResponse(id: 'follow-2'),
2134+
],
2135+
),
2136+
),
2137+
),
2138+
body: (tester) async {
2139+
// Verify initial state has own fields
2140+
final initialFeed = tester.feedState.feed;
2141+
expect(initialFeed, isNotNull);
2142+
expect(initialFeed!.ownCapabilities, hasLength(2));
2143+
expect(initialFeed.ownMembership, isNotNull);
2144+
expect(initialFeed.ownFollows, hasLength(2));
2145+
2146+
final originalCapabilities = initialFeed.ownCapabilities;
2147+
final originalMembership = initialFeed.ownMembership;
2148+
final originalFollows = initialFeed.ownFollows;
2149+
2150+
// Emit FeedUpdatedEvent without own fields
2151+
await tester.emitEvent(
2152+
FeedUpdatedEvent(
2153+
type: EventTypes.feedUpdated,
2154+
createdAt: DateTime.timestamp(),
2155+
custom: const {},
2156+
fid: feedId.rawValue,
2157+
feed: createDefaultFeedResponse(
2158+
id: feedId.id,
2159+
groupId: feedId.group,
2160+
).copyWith(
2161+
name: 'Updated Name',
2162+
description: 'Updated Description',
2163+
followerCount: 100,
2164+
// Note: ownCapabilities, ownMembership, ownFollows are not included
2165+
),
2166+
),
2167+
);
2168+
2169+
// Verify own fields are preserved
2170+
final updatedFeed = tester.feedState.feed;
2171+
expect(updatedFeed, isNotNull);
2172+
expect(updatedFeed!.name, equals('Updated Name'));
2173+
expect(updatedFeed.description, equals('Updated Description'));
2174+
expect(updatedFeed.followerCount, equals(100));
2175+
2176+
// Own fields should be preserved
2177+
expect(updatedFeed.ownCapabilities, equals(originalCapabilities));
2178+
expect(updatedFeed.ownMembership, equals(originalMembership));
2179+
expect(updatedFeed.ownFollows, equals(originalFollows));
2180+
},
2181+
);
2182+
});
21052183
}

packages/stream_feeds_test/lib/src/helpers/test_data.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ FeedResponse createDefaultFeedResponse({
209209
String groupId = 'group',
210210
int followerCount = 0,
211211
int followingCount = 0,
212+
List<FeedOwnCapability>? ownCapabilities,
213+
FeedMemberResponse? ownMembership,
214+
List<FollowResponse>? ownFollows,
212215
}) {
213216
return FeedResponse(
214217
id: id,
@@ -224,6 +227,9 @@ FeedResponse createDefaultFeedResponse({
224227
memberCount: 0,
225228
pinCount: 0,
226229
updatedAt: DateTime.now(),
230+
ownCapabilities: ownCapabilities,
231+
ownMembership: ownMembership,
232+
ownFollows: ownFollows,
227233
);
228234
}
229235

0 commit comments

Comments
 (0)