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
12 changes: 12 additions & 0 deletions packages/stream_chat/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
## Upcoming

✅ Added

- Added `Event.channelMemberCount`, exposing the server-provided `channel_member_count` field on channel events (e.g. `member.added`, `member.removed`, `member.updated`).

🔄 Changed

- `Channel` and `ClientState` streams that expose a single primitive value are now distinct, so they only emit when the value actually changes. Affects `Channel.memberCountStream`, `messageCountStream`, `watcherCountStream`, `cooldownStream`, `nameStream`, `imageStream`, `frozenStream`, `disabledStream`, `hiddenStream`, `isPinnedStream`, `isArchivedStream`, `createdAtStream`, `updatedAtStream`, `deletedAtStream`, `truncatedAtStream`, `lastMessageAtStream`, and `ClientState.totalUnreadCountStream`, `unreadChannelsStream`, `unreadThreadsStream`.

⚠️ Deprecated

- Deprecated `StreamChatClient.unflagMessage` and `StreamChatClient.unflagUser`. The `/moderation/unflag` endpoint is no longer supported by the server and the calls have no effect; both methods will be removed in a future major release.

🐞 Fixed

- Fixed `Channel.memberCount` / `memberCountStream` staying stale for the rest of the session after members joined or left; channel events now apply the server-provided member count, the same way `messageCount` already did.

## 9.28.0

✅ Added
Expand Down
66 changes: 47 additions & 19 deletions packages/stream_chat/lib/src/client/channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ class Channel {
/// Channel frozen status as a stream.
Stream<bool> get frozenStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.frozen == true);
return state!.channelStateStream
.map((cs) => cs.channel?.frozen == true)
.distinct();
}

/// Channel disabled status.
Expand All @@ -219,7 +221,9 @@ class Channel {
/// Channel disabled status as a stream.
Stream<bool> get disabledStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.disabled == true);
return state!.channelStateStream
.map((cs) => cs.channel?.disabled == true)
.distinct();
}

/// Channel hidden status.
Expand All @@ -231,7 +235,9 @@ class Channel {
/// Channel hidden status as a stream.
Stream<bool> get hiddenStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.hidden == true);
return state!.channelStateStream
.map((cs) => cs.channel?.hidden == true)
.distinct();
}

/// Channel pinned status.
Expand All @@ -244,7 +250,7 @@ class Channel {
/// Channel pinned status as a stream.
/// Status is specific to the current user.
Stream<bool> get isPinnedStream {
return membershipStream.map((m) => m?.pinnedAt != null);
return membershipStream.map((m) => m?.pinnedAt != null).distinct();
}

/// Channel archived status.
Expand All @@ -257,7 +263,7 @@ class Channel {
/// Channel archived status as a stream.
/// Status is specific to the current user.
Stream<bool> get isArchivedStream {
return membershipStream.map((m) => m?.archivedAt != null);
return membershipStream.map((m) => m?.archivedAt != null).distinct();
}

/// The last date at which the channel got truncated.
Expand All @@ -269,7 +275,9 @@ class Channel {
/// The last date at which the channel got truncated as a stream.
Stream<DateTime?> get truncatedAtStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.truncatedAt);
return state!.channelStateStream
.map((cs) => cs.channel?.truncatedAt)
.distinct();
}

/// Cooldown count
Expand All @@ -281,7 +289,9 @@ class Channel {
/// Cooldown count as a stream
Stream<int> get cooldownStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.cooldown ?? 0);
return state!.channelStateStream
.map((cs) => cs.channel?.cooldown ?? 0)
.distinct();
}

/// Remaining cooldown duration in seconds for the channel.
Expand Down Expand Up @@ -322,7 +332,9 @@ class Channel {
/// Channel creation date as a stream.
Stream<DateTime?> get createdAtStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.createdAt);
return state!.channelStateStream
.map((cs) => cs.channel?.createdAt)
.distinct();
}

/// Channel last message date.
Expand All @@ -334,7 +346,9 @@ class Channel {
/// Channel last message date as a stream.
Stream<DateTime?> get lastMessageAtStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.lastMessageAt);
return state!.channelStateStream
.map((cs) => cs.channel?.lastMessageAt)
.distinct();
}

DateTime? _currentUserLastMessageAt(List<Message>? messages) {
Expand Down Expand Up @@ -387,7 +401,9 @@ class Channel {
/// Channel updated date as a stream.
Stream<DateTime?> get updatedAtStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.updatedAt);
return state!.channelStateStream
.map((cs) => cs.channel?.updatedAt)
.distinct();
}

/// Channel deletion date.
Expand All @@ -399,7 +415,9 @@ class Channel {
/// Channel deletion date as a stream.
Stream<DateTime?> get deletedAtStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.deletedAt);
return state!.channelStateStream
.map((cs) => cs.channel?.deletedAt)
.distinct();
}

/// Channel member count.
Expand All @@ -411,7 +429,9 @@ class Channel {
/// Channel member count as a stream.
Stream<int?> get memberCountStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.memberCount);
return state!.channelStateStream
.map((cs) => cs.channel?.memberCount)
.distinct();
}

/// Channel message count.
Expand All @@ -429,7 +449,9 @@ class Channel {
/// enabled for your app.
Stream<int?> get messageCountStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.messageCount);
return state!.channelStateStream
.map((cs) => cs.channel?.messageCount)
.distinct();
}

/// List of filter tags applied to this channel.
Expand Down Expand Up @@ -496,7 +518,7 @@ class Channel {
/// {@macro name}
Stream<String?> get nameStream {
_checkInitialized();
return extraDataStream.map((it) => it['name'] as String?);
return extraDataStream.map((it) => it['name'] as String?).distinct();
}

/// Shortcut to get channel image.
Expand All @@ -511,7 +533,7 @@ class Channel {
/// {@macro image}
Stream<String?> get imageStream {
_checkInitialized();
return extraDataStream.map((it) => it['image'] as String?);
return extraDataStream.map((it) => it['image'] as String?).distinct();
}

/// The main Stream chat client.
Expand Down Expand Up @@ -2246,7 +2268,7 @@ class ChannelClientState {

_listenChannelUpdated();

_listenChannelMessageCount();
_listenChannelCounts();

_listenMemberAdded();

Expand Down Expand Up @@ -2381,15 +2403,21 @@ class ChannelClientState {
}));
}

void _listenChannelMessageCount() {
// Most channel events carry the channel's member and message counts as
// event metadata, reflecting the authoritative values after the change.
// Applying them keeps the counts fresh for the whole session instead of
// only right after a `query` / `watch`.
void _listenChannelCounts() {
_subscriptions.add(_channel.on().listen(
(Event e) {
final memberCount = e.channelMemberCount;
final messageCount = e.channelMessageCount;
if (messageCount == null) return;
if (memberCount == null && messageCount == null) return;

updateChannelState(
channelState.copyWith(
channel: channelState.channel?.copyWith(
memberCount: memberCount,
messageCount: messageCount,
),
),
Expand Down Expand Up @@ -3265,7 +3293,7 @@ class ChannelClientState {

/// Channel watcher count as a stream.
Stream<int?> get watcherCountStream =>
channelStateStream.map((cs) => cs.watcherCount);
channelStateStream.map((cs) => cs.watcherCount).distinct();

/// Channel watchers list.
List<User> get watchers => (_channelState.watchers ?? <User>[])
Expand Down
9 changes: 6 additions & 3 deletions packages/stream_chat/lib/src/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2588,19 +2588,22 @@ class ClientState {
int get unreadChannels => _unreadChannelsController.value;

/// The current unread channels count as a stream
Stream<int> get unreadChannelsStream => _unreadChannelsController.stream;
Stream<int> get unreadChannelsStream =>
_unreadChannelsController.stream.distinct();

/// The current unread thread count.
int get unreadThreads => _unreadThreadsController.value;

/// The current unread threads count as a stream.
Stream<int> get unreadThreadsStream => _unreadThreadsController.stream;
Stream<int> get unreadThreadsStream =>
_unreadThreadsController.stream.distinct();

/// The current total unread messages count
int get totalUnreadCount => _totalUnreadCountController.value;

/// The current total unread messages count as a stream
Stream<int> get totalUnreadCountStream => _totalUnreadCountController.stream;
Stream<int> get totalUnreadCountStream =>
_totalUnreadCountController.stream.distinct();

/// The current list of channels in memory as a stream
Stream<Map<String, Channel>> get channelsStream => _channelsController.stream;
Expand Down
11 changes: 11 additions & 0 deletions packages/stream_chat/lib/src/core/models/event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Event {
this.reminder,
this.pushPreference,
this.channelPushPreference,
this.channelMemberCount,
this.channelMessageCount,
this.watcherCount,
this.lastDeliveredAt,
Expand Down Expand Up @@ -166,6 +167,13 @@ class Event {
/// Push notification preferences for the current user for this channel.
final ChannelPushPreference? channelPushPreference;

/// The total number of members in the channel.
///
/// Sent with channel events such as `member.added`, `member.removed` and
/// `member.updated`, reflecting the authoritative member count after the
/// change.
final int? channelMemberCount;

/// The total number of messages in the channel.
final int? channelMessageCount;

Expand Down Expand Up @@ -222,6 +230,7 @@ class Event {
'reminder',
'push_preference',
'channel_push_preference',
'channel_member_count',
'channel_message_count',
'watcher_count',
'last_delivered_at',
Expand Down Expand Up @@ -269,6 +278,7 @@ class Event {
MessageReminder? reminder,
PushPreference? pushPreference,
ChannelPushPreference? channelPushPreference,
int? channelMemberCount,
int? channelMessageCount,
int? watcherCount,
DateTime? lastDeliveredAt,
Expand Down Expand Up @@ -311,6 +321,7 @@ class Event {
pushPreference: pushPreference ?? this.pushPreference,
channelPushPreference:
channelPushPreference ?? this.channelPushPreference,
channelMemberCount: channelMemberCount ?? this.channelMemberCount,
channelMessageCount: channelMessageCount ?? this.channelMessageCount,
watcherCount: watcherCount ?? this.watcherCount,
lastDeliveredAt: lastDeliveredAt ?? this.lastDeliveredAt,
Expand Down
3 changes: 3 additions & 0 deletions packages/stream_chat/lib/src/core/models/event.g.dart

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

3 changes: 2 additions & 1 deletion packages/stream_chat/test/fixtures/event.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@
"unread_thread_messages": 2,
"unread_threads": 3,
"channel_last_message_at": "2019-03-27T17:40:17.155892Z",
"watcher_count": 12
"watcher_count": 12,
"channel_member_count": 4
}
Loading
Loading