Skip to content
Merged
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
1 change: 1 addition & 0 deletions packages/stream_feeds/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Add location filtering support for activities with `ActivitiesFilterField.near` and `ActivitiesFilterField.withinBounds` filter fields.
- Add new activity filter fields: `ActivitiesFilterField.feed` and `ActivitiesFilterField.interestTags`.
- Export previously missing public APIs: models, state objects, and queries.
- Add `ownFollows` field to `FeedData` to store the follow relationships of the current user in the feed.

## 0.4.0
- [BREAKING] Change `queryFollowSuggestions` return type to `List<FeedSuggestionData>`.
Expand Down
6 changes: 6 additions & 0 deletions packages/stream_feeds/lib/src/models/activity_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,12 @@ extension ActivityDataMutations on ActivityData {
ownBookmarks: ownBookmarks ?? this.ownBookmarks,
ownReactions: ownReactions ?? this.ownReactions,
poll: updated.poll?.let((it) => poll?.updateWith(it) ?? it),
// Workaround until the backend fixes the issue with missing currentFeed
// in some WS events
currentFeed: switch (updated.currentFeed) {
final it? => currentFeed?.updateWith(it) ?? it,
_ => currentFeed,
},
);
}

Expand Down
32 changes: 32 additions & 0 deletions packages/stream_feeds/lib/src/models/feed_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import '../generated/api/models.dart';
import 'feed_id.dart';
import 'feed_member_data.dart';
import 'follow_data.dart';
import 'user_data.dart';

part 'feed_data.freezed.dart';
Expand Down Expand Up @@ -30,6 +31,7 @@ class FeedData with _$FeedData {
required this.name,
required this.ownCapabilities,
this.ownMembership,
this.ownFollows,
required this.pinCount,
required this.updatedAt,
this.visibility,
Expand Down Expand Up @@ -92,6 +94,10 @@ class FeedData with _$FeedData {
@override
final FeedMemberData? ownMembership;

/// The follow relationships of the current user in the feed.
@override
final List<FollowData>? ownFollows;

/// The number of pinned items in the feed.
@override
final int pinCount;
Expand Down Expand Up @@ -131,10 +137,36 @@ extension FeedResponseMapper on FeedResponse {
name: name,
ownCapabilities: ownCapabilities ?? const [],
ownMembership: ownMembership?.toModel(),
ownFollows: ownFollows?.map((f) => f.toModel()).toList(),
pinCount: pinCount,
updatedAt: updatedAt,
visibility: visibility,
custom: custom,
);
}
}

/// Extension functions for [FeedData] to handle common operations.
extension FeedDataMutations on FeedData {
/// Updates this feed with new data while preserving own data.
///
/// Merges [updated] feed data with this instance, preserving [ownCapabilities],
/// [ownMembership], and [ownFollows] from this instance when not provided. This
/// ensures that user-specific data is not lost when updating from WebSocket events.
///
/// Returns a new [FeedData] instance with the merged data.
FeedData updateWith(
FeedData updated, {
List<FeedOwnCapability>? ownCapabilities,
FeedMemberData? ownMembership,
List<FollowData>? ownFollows,
}) {
return updated.copyWith(
// Preserve own data from the current instance if not provided
// as they may not be reliable from WS events.
ownCapabilities: ownCapabilities ?? this.ownCapabilities,
ownMembership: ownMembership ?? this.ownMembership,
ownFollows: ownFollows ?? this.ownFollows,
);
}
}
53 changes: 32 additions & 21 deletions packages/stream_feeds/lib/src/models/feed_data.freezed.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions packages/stream_feeds/lib/src/state/feed_list_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ class FeedListStateNotifier extends StateNotifier<FeedListState> {

/// Handles updates to a specific feed.
void onFeedUpdated(FeedData feed) {
final updatedFeeds = state.feeds.map((it) {
if (it.fid.rawValue != feed.fid.rawValue) return it;
return feed;
}).toList();
final updatedFeeds = state.feeds.sortedUpsert(
feed,
key: (it) => it.fid.rawValue,
compare: feedsSort.compare,
update: (existing, updated) => existing.updateWith(updated),
);

state = state.copyWith(feeds: updatedFeeds);
}
Expand Down
5 changes: 4 additions & 1 deletion packages/stream_feeds/lib/src/state/feed_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,11 @@ class FeedStateNotifier extends StateNotifier<FeedState> {

/// Handles updates to the feed state when the feed is updated.
void onFeedUpdated(FeedData feed) {
final currentFeed = state.feed;
final updatedFeed = currentFeed?.updateWith(feed) ?? feed;

// Update the feed data in the state
state = state.copyWith(feed: feed);
state = state.copyWith(feed: updatedFeed);
}

/// Handles updates to the feed state when a follow is added.
Expand Down
78 changes: 78 additions & 0 deletions packages/stream_feeds/test/state/feed_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2102,4 +2102,82 @@ void main() {
},
);
});

// ============================================================
// FEATURE: Feed Updated Event
// ============================================================

group('FeedUpdatedEvent', () {
const feedId = FeedId(group: 'user', id: 'john');
const currentUser = User(id: 'luke_skywalker');

feedTest(
'should preserve own fields when feed is updated',
user: currentUser,
build: (client) => client.feedFromId(feedId),
setUp: (tester) => tester.getOrCreate(
modifyResponse: (it) => it.copyWith(
feed: createDefaultFeedResponse(
id: feedId.id,
groupId: feedId.group,
ownCapabilities: [
FeedOwnCapability.createFeed,
FeedOwnCapability.deleteFeed,
],
ownMembership: createDefaultFeedMemberResponse(
id: currentUser.id,
role: 'admin',
),
ownFollows: [
createDefaultFollowResponse(id: 'follow-1'),
createDefaultFollowResponse(id: 'follow-2'),
],
),
),
),
body: (tester) async {
// Verify initial state has own fields
final initialFeed = tester.feedState.feed;
expect(initialFeed, isNotNull);
expect(initialFeed!.ownCapabilities, hasLength(2));
expect(initialFeed.ownMembership, isNotNull);
expect(initialFeed.ownFollows, hasLength(2));

final originalCapabilities = initialFeed.ownCapabilities;
final originalMembership = initialFeed.ownMembership;
final originalFollows = initialFeed.ownFollows;

// Emit FeedUpdatedEvent without own fields
await tester.emitEvent(
FeedUpdatedEvent(
type: EventTypes.feedUpdated,
createdAt: DateTime.timestamp(),
custom: const {},
fid: feedId.rawValue,
feed: createDefaultFeedResponse(
id: feedId.id,
groupId: feedId.group,
).copyWith(
name: 'Updated Name',
description: 'Updated Description',
followerCount: 100,
// Note: ownCapabilities, ownMembership, ownFollows are not included
),
),
);

// Verify own fields are preserved
final updatedFeed = tester.feedState.feed;
expect(updatedFeed, isNotNull);
expect(updatedFeed!.name, equals('Updated Name'));
expect(updatedFeed.description, equals('Updated Description'));
expect(updatedFeed.followerCount, equals(100));

// Own fields should be preserved
expect(updatedFeed.ownCapabilities, equals(originalCapabilities));
expect(updatedFeed.ownMembership, equals(originalMembership));
expect(updatedFeed.ownFollows, equals(originalFollows));
},
);
});
}
6 changes: 6 additions & 0 deletions packages/stream_feeds_test/lib/src/helpers/test_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ FeedResponse createDefaultFeedResponse({
String groupId = 'group',
int followerCount = 0,
int followingCount = 0,
List<FeedOwnCapability>? ownCapabilities,
FeedMemberResponse? ownMembership,
List<FollowResponse>? ownFollows,
}) {
return FeedResponse(
id: id,
Expand All @@ -224,6 +227,9 @@ FeedResponse createDefaultFeedResponse({
memberCount: 0,
pinCount: 0,
updatedAt: DateTime.now(),
ownCapabilities: ownCapabilities,
ownMembership: ownMembership,
ownFollows: ownFollows,
);
}

Expand Down