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
3 changes: 3 additions & 0 deletions packages/stream_feeds/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## unreleased
- Update follower and following counts on the feed state when receiving follow websocket events.

## 0.3.1
- Update API client with renaming `addReaction` to `addActivityReaction` and `deleteReaction` to `deleteActivityReaction`.
- Update `activity.currentFeed` capabilities when adding or updating activity from websocket events.
Expand Down
15 changes: 15 additions & 0 deletions packages/stream_feeds/lib/src/models/feed_id.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ class FeedId with _$FeedId {
required this.id,
}) : rawValue = '$group:$id';

/// The user's timeline feed containing posts from followed users
const FeedId.timeline(String id) : this(group: 'timeline', id: id);

/// Notifications feed
const FeedId.notification(String id) : this(group: 'notification', id: id);

/// The user's feed containing stories from followed users
const FeedId.stories(String id) : this(group: 'stories', id: id);

/// The user's own stories
const FeedId.story(String id) : this(group: 'story', id: id);

/// TThe user's own posts
const FeedId.user(String id) : this(group: 'user', id: id);

/// Creates a feed identifier from a raw string value.
///
/// The string should be in the format `"group:id"`. If the string
Expand Down
24 changes: 22 additions & 2 deletions packages/stream_feeds/lib/src/state/feed_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -349,21 +349,29 @@ class FeedStateNotifier extends StateNotifier<FeedState> {
}

if (follow.isFollowingFeed(state.fid)) {
final updatedCount = follow.sourceFeed.followingCount;
final updatedFollowing = state.following.upsert(
follow,
key: (it) => it.id,
);

return state.copyWith(following: updatedFollowing);
return state.copyWith(
following: updatedFollowing,
feed: state.feed?.copyWith(followingCount: updatedCount),
);
}

if (follow.isFollowerOf(state.fid)) {
final updatedCount = follow.targetFeed.followerCount;
final updatedFollowers = state.followers.upsert(
follow,
key: (it) => it.id,
);

return state.copyWith(followers: updatedFollowers);
return state.copyWith(
followers: updatedFollowers,
feed: state.feed?.copyWith(followerCount: updatedCount),
);
}

// If the follow doesn't match any known categories,
Expand All @@ -372,6 +380,17 @@ class FeedStateNotifier extends StateNotifier<FeedState> {
}

FeedState _removeFollow(FollowData follow, FeedState state) {
var feed = state.feed;

if (follow.isFollowerOf(state.fid)) {
final followerCount = follow.targetFeed.followerCount;
feed = feed?.copyWith(followerCount: followerCount);
}
if (follow.isFollowingFeed(state.fid)) {
final followingCount = follow.sourceFeed.followingCount;
feed = feed?.copyWith(followingCount: followingCount);
}

final updatedFollowing = state.following.where((it) {
return it.id != follow.id;
}).toList();
Expand All @@ -385,6 +404,7 @@ class FeedStateNotifier extends StateNotifier<FeedState> {
}).toList();

return state.copyWith(
feed: feed,
following: updatedFollowing,
followers: updatedFollowers,
followRequests: updatedFollowRequests,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:stream_feeds/src/client/feeds_client_impl.dart';
import 'package:stream_feeds/stream_feeds.dart';
import 'package:test/test.dart';

import '../mocks.dart';
import '../test_utils.dart';

void main() {
test('Create a feeds client', () {
Expand Down
46 changes: 46 additions & 0 deletions packages/stream_feeds/test/models/feed_id_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:stream_feeds/stream_feeds.dart';
import 'package:test/test.dart';

void main() {
test('feed id should be created with group and id', () {
const feedId = FeedId(group: 'group', id: 'id');
expect(feedId.group, 'group');
expect(feedId.id, 'id');
expect(feedId.rawValue, 'group:id');
});

test('feed id should be created with timeline group and id', () {
const feedId = FeedId.timeline('id');
expect(feedId.group, 'timeline');
expect(feedId.id, 'id');
expect(feedId.rawValue, 'timeline:id');
});

test('feed id should be created with notification group and id', () {
const feedId = FeedId.notification('id');
expect(feedId.group, 'notification');
expect(feedId.id, 'id');
expect(feedId.rawValue, 'notification:id');
});

test('feed id should be created with stories group and id', () {
const feedId = FeedId.stories('id');
expect(feedId.group, 'stories');
expect(feedId.id, 'id');
expect(feedId.rawValue, 'stories:id');
});

test('feed id should be created with story group and id', () {
const feedId = FeedId.story('id');
expect(feedId.group, 'story');
expect(feedId.id, 'id');
expect(feedId.rawValue, 'story:id');
});

test('feed id should be created with user group and id', () {
const feedId = FeedId.user('id');
expect(feedId.group, 'user');
expect(feedId.id, 'id');
expect(feedId.rawValue, 'user:id');
});
}
117 changes: 9 additions & 108 deletions packages/stream_feeds/test/state/activity_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// ignore_for_file: avoid_redundant_argument_values

import 'dart:async';
import 'dart:convert';

Expand All @@ -9,8 +7,7 @@ import 'package:stream_feeds/src/state/activity_state.dart';
import 'package:stream_feeds/stream_feeds.dart';
import 'package:test/test.dart';

import '../mocks.dart';
import '../ws_test_helpers.dart';
import '../test_utils.dart';

void main() {
late StreamFeedsClientImpl client;
Expand Down Expand Up @@ -47,7 +44,7 @@ void main() {
depth: 3,
),
).thenAnswer(
(_) async => const Result.success(defaultCommentsResponse),
(_) async => Result.success(createDefaultCommentsResponse()),
);

final activity = client.activity(
Expand Down Expand Up @@ -102,7 +99,7 @@ void main() {
depth: 3,
),
).thenAnswer(
(_) async => const Result.success(defaultCommentsResponse),
(_) async => Result.success(createDefaultCommentsResponse()),
);
}

Expand Down Expand Up @@ -146,7 +143,7 @@ void main() {
optionId: firstOptionId,
pollId: pollId,
),
type: 'feeds.poll.vote_casted',
type: EventTypes.pollVoteCasted,
).toJson(),
),
);
Expand Down Expand Up @@ -191,7 +188,7 @@ void main() {
optionId: 'optionId1',
pollId: 'pollId1',
),
type: 'feeds.poll.vote_casted',
type: EventTypes.pollVoteCasted,
),
),
);
Expand Down Expand Up @@ -251,7 +248,7 @@ void main() {
optionId: 'optionId1',
pollId: 'pollId1',
),
type: 'feeds.poll.vote_removed',
type: EventTypes.pollVoteRemoved,
),
),
);
Expand Down Expand Up @@ -309,7 +306,7 @@ void main() {
optionId: 'optionId1',
pollId: pollId,
),
type: 'feeds.poll.vote_removed',
type: EventTypes.pollVoteRemoved,
),
),
);
Expand Down Expand Up @@ -344,7 +341,7 @@ void main() {
custom: const {},
fid: 'fid',
poll: poll.copyWith(isClosed: true),
type: 'feeds.poll.closed',
type: EventTypes.pollClosed,
),
),
);
Expand Down Expand Up @@ -378,106 +375,10 @@ void main() {
custom: const {},
fid: 'fid',
poll: poll,
type: 'feeds.poll.deleted',
type: EventTypes.pollDeleted,
),
),
);
});
});
}

const defaultCommentsResponse = GetCommentsResponse(
comments: [],
next: null,
prev: null,
duration: 'duration',
);

GetActivityResponse createDefaultActivityResponse({PollResponseData? poll}) =>
GetActivityResponse(
activity: ActivityResponse(
id: 'id',
attachments: const [],
bookmarkCount: 0,
commentCount: 0,
comments: const [],
createdAt: DateTime(2021, 1, 1),
custom: const {},
feeds: const [],
filterTags: const [],
interestTags: const [],
latestReactions: const [],
mentionedUsers: const [],
moderation: null,
notificationContext: null,
ownBookmarks: const [],
ownReactions: const [],
parent: null,
poll: poll,
popularity: 0,
reactionCount: 0,
reactionGroups: const {},
score: 0,
searchData: const {},
shareCount: 0,
text: null,
type: 'type',
updatedAt: DateTime(2021, 2, 1),
user: UserResponse(
id: 'id',
name: 'name',
banned: false,
blockedUserIds: const [],
createdAt: DateTime(2021, 1, 1),
custom: const {},
language: 'language',
online: false,
role: 'role',
teams: const [],
updatedAt: DateTime(2021, 2, 1),
),
visibility: ActivityResponseVisibility.public,
visibilityTag: null,
),
duration: 'duration',
);

PollResponseData createDefaultPollResponseData({
List<PollVoteResponseData> latestAnswers = const [],
Map<String, List<PollVoteResponseData>> latestVotesByOption = const {},
}) =>
PollResponseData(
id: 'id',
name: 'name',
allowAnswers: true,
allowUserSuggestedOptions: true,
answersCount: latestAnswers.length,
createdAt: DateTime.now(),
createdById: 'id',
custom: const {},
description: 'description',
enforceUniqueVote: true,
latestAnswers: latestAnswers,
latestVotesByOption: latestVotesByOption,
ownVotes: const [],
updatedAt: DateTime.now(),
voteCount: latestVotesByOption.values
.map((e) => e.length)
.fold(0, (v, e) => v + e),
voteCountsByOption: latestVotesByOption.map(
(k, e) => MapEntry(k, e.length),
),
votingVisibility: 'visibility',
options: const [
PollOptionResponseData(
id: 'id1',
text: 'text1',
custom: {},
),
PollOptionResponseData(
id: 'id2',
text: 'text2',
custom: {},
),
],
);
Loading