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

✅ Added

- Added support for `Channel.messageCount` field.

🐞 Fixed

- Fixed `ChannelState.memberCount`, `ChannelState.config` and `ChannelState.extraData` getting reset
Expand Down
37 changes: 37 additions & 0 deletions packages/stream_chat/lib/src/client/channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,24 @@ class Channel {
return state!.channelStateStream.map((cs) => cs.channel?.memberCount);
}

/// Channel message count.
///
/// Note: This field is only populated if the `count_messages` option is
/// enabled for your app.
int? get messageCount {
_checkInitialized();
return state!._channelState.channel?.messageCount;
}

/// Channel message count as a stream.
///
/// Note: This field is only populated if the `count_messages` option is
/// enabled for your app.
Stream<int?> get messageCountStream {
_checkInitialized();
return state!.channelStateStream.map((cs) => cs.channel?.messageCount);
}

/// Channel id.
String? get id => state?._channelState.channel?.id ?? _id;

Expand Down Expand Up @@ -2165,6 +2183,8 @@ class ChannelClientState {

_listenChannelUpdated();

_listenChannelMessageCount();

_listenMemberAdded();

_listenMemberRemoved();
Expand Down Expand Up @@ -2334,6 +2354,23 @@ class ChannelClientState {
}));
}

void _listenChannelMessageCount() {
_subscriptions.add(_channel.on().listen(
(Event e) {
final messageCount = e.channelMessageCount;
if (messageCount == null) return;

updateChannelState(
channelState.copyWith(
channel: channelState.channel?.copyWith(
messageCount: messageCount,
),
),
);
},
));
}

void _listenChannelTruncated() {
_subscriptions.add(_channel
.on(EventType.channelTruncated, EventType.notificationChannelTruncated)
Expand Down
12 changes: 12 additions & 0 deletions packages/stream_chat/lib/src/core/models/channel_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class ChannelModel {
bool? disabled,
bool? hidden,
DateTime? truncatedAt,
this.messageCount,
}) : assert(
(cid != null && cid.contains(':')) || (id != null && type != null),
'provide either a cid or an id and type',
Expand Down Expand Up @@ -151,6 +152,13 @@ class ChannelModel {
return null;
}

/// The total number of messages in the channel.
///
/// Note: This field is only populated if the `count_messages` option is
/// enabled for your app.
@JsonKey(includeToJson: false)
final int? messageCount;

/// Known top level fields.
/// Useful for [Serializer] methods.
static const topLevelFields = [
Expand All @@ -169,6 +177,7 @@ class ChannelModel {
'members',
'team',
'cooldown',
'message_count',
];

/// Serialize to json
Expand Down Expand Up @@ -197,6 +206,7 @@ class ChannelModel {
bool? disabled,
bool? hidden,
DateTime? truncatedAt,
int? messageCount,
}) =>
ChannelModel(
id: id ?? this.id,
Expand All @@ -223,6 +233,7 @@ class ChannelModel {
// ignore: cast_nullable_to_non_nullable
: DateTime.parse(extraData?['truncated_at'] as String)) ??
this.truncatedAt,
messageCount: messageCount ?? this.messageCount,
);

/// Returns a new [ChannelModel] that is a combination of this channelModel
Expand All @@ -249,6 +260,7 @@ class ChannelModel {
disabled: other.disabled,
hidden: other.hidden,
truncatedAt: other.truncatedAt,
messageCount: other.messageCount,
);
}
}
Expand Down

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

7 changes: 7 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.channelMessageCount,
this.extraData = const {},
this.isLocal = true,
}) : createdAt = createdAt?.toUtc() ?? DateTime.now().toUtc();
Expand Down Expand Up @@ -162,6 +163,9 @@ class Event {
/// Push notification preferences for the current user for this channel.
final ChannelPushPreference? channelPushPreference;

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

/// Map of custom channel extraData
final Map<String, Object?> extraData;

Expand Down Expand Up @@ -203,6 +207,7 @@ class Event {
'reminder',
'push_preference',
'channel_push_preference',
'channel_message_count',
];

/// Serialize to json
Expand Down Expand Up @@ -246,6 +251,7 @@ class Event {
MessageReminder? reminder,
PushPreference? pushPreference,
ChannelPushPreference? channelPushPreference,
int? channelMessageCount,
Map<String, Object?>? extraData,
}) =>
Event(
Expand Down Expand Up @@ -284,6 +290,7 @@ class Event {
pushPreference: pushPreference ?? this.pushPreference,
channelPushPreference:
channelPushPreference ?? this.channelPushPreference,
channelMessageCount: channelMessageCount ?? this.channelMessageCount,
isLocal: isLocal,
extraData: extraData ?? this.extraData,
);
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.

160 changes: 160 additions & 0 deletions packages/stream_chat/test/src/client/channel_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5637,5 +5637,165 @@ void main() {
},
);
});

group('Channel message count events', () {
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
late Channel channel;

setUp(() {
final channelState = _generateChannelState(channelId, channelType);
channel = Channel.fromState(client, channelState);
});

tearDown(() {
channel.dispose();
});

test(
'should update channel messageCount when event contains channelMessageCount',
() async {
// Verify initial state - no messageCount
expect(channel.messageCount, isNull);

// Create event with channelMessageCount
final messageCountEvent = Event(
cid: channel.cid,
type: EventType.messageNew,
channelMessageCount: 42,
);

// Dispatch event
client.addEvent(messageCountEvent);

// Wait for the event to be processed
await Future.delayed(Duration.zero);

// Verify channel messageCount was updated
expect(channel.messageCount, equals(42));
},
);

test(
'should update channel messageCount from message.new and message.deleted events',
() async {
// Test with message.new event - count increases
final messageNewEvent = Event(
cid: channel.cid,
type: EventType.messageNew,
message: Message(
id: 'new-message-1',
text: 'Hello world!',
user: User(id: 'user-1'),
),
channelMessageCount: 1,
);

client.addEvent(messageNewEvent);
await Future.delayed(Duration.zero);
expect(channel.messageCount, equals(1));

// Test with another message.new event - count increases
final messageNewEvent2 = Event(
cid: channel.cid,
type: EventType.messageNew,
message: Message(
id: 'new-message-2',
text: 'Second message',
user: User(id: 'user-2'),
),
channelMessageCount: 2,
);

client.addEvent(messageNewEvent2);
await Future.delayed(Duration.zero);
expect(channel.messageCount, equals(2));

// Test with message.deleted event - count decreases
final messageDeletedEvent = Event(
cid: channel.cid,
type: EventType.messageDeleted,
message: Message(
id: 'new-message-1',
text: 'Hello world!',
user: User(id: 'user-1'),
),
channelMessageCount: 1,
);

client.addEvent(messageDeletedEvent);
await Future.delayed(Duration.zero);
expect(channel.messageCount, equals(1));
},
);

test(
'should preserve other channel properties when updating messageCount',
() async {
// Set initial channel state with some properties
final initialChannel = channel.state?.channelState.channel?.copyWith(
extraData: {'name': 'Test Channel'},
memberCount: 5,
frozen: true,
);

if (initialChannel != null) {
channel.state?.updateChannelState(
channel.state!.channelState.copyWith(channel: initialChannel),
);
}

// Verify initial state
expect(channel.name, 'Test Channel');
expect(channel.memberCount, equals(5));
expect(channel.frozen, equals(true));
expect(channel.messageCount, isNull);

// Update messageCount via event
final messageCountEvent = Event(
cid: channel.cid,
type: EventType.messageNew,
channelMessageCount: 100,
);

client.addEvent(messageCountEvent);
await Future.delayed(Duration.zero);

// Verify messageCount was updated while preserving other properties
expect(channel.messageCount, equals(100));
expect(channel.name, 'Test Channel');
expect(channel.memberCount, equals(5));
expect(channel.frozen, equals(true));
},
);

test(
'should provide messageCountStream for reactive updates',
() async {
expectLater(
channel.messageCountStream.distinct(),
emitsInOrder([null, 1, 5, 10]),
);

// Update messageCount multiple times
final counts = [1, 5, 10];
for (final count in counts) {
final event = Event(
cid: channel.cid,
type: EventType.messageNew,
message: Message(
id: 'msg-$count',
text: 'Message $count',
user: User(id: 'user-1'),
),
channelMessageCount: count,
);

client.addEvent(event);
await Future.delayed(Duration.zero);
}
},
);
});
});
}
4 changes: 4 additions & 0 deletions packages/stream_chat_persistence/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Upcoming

- Added support for `Channel.messageCount` field.

## 9.17.0

- Updated `stream_chat` dependency to [`9.17.0`](https://pub.dev/packages/stream_chat/changelog).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class DriftChatDatabase extends _$DriftChatDatabase {

// you should bump this number whenever you change or add a table definition.
@override
int get schemaVersion => 23;
int get schemaVersion => 24;

@override
MigrationStrategy get migration => MigrationStrategy(
Expand Down
Loading