diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 28bdb9e34..8d28da70e 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -6,6 +6,7 @@ - Added user-group APIs on `StreamChatClient`: `listUserGroups`, `searchUserGroups`, `getUserGroup`, `createUserGroup`, `updateUserGroup`, `deleteUserGroup`, `addUserGroupMembers`, `removeUserGroupMembers`. - Added enhanced-mention fields on `Message`: `mentionedChannel`, `mentionedHere`, `mentionedRoles`, `mentionedGroupIds`, and `mentionedGroups`. - Added `ChannelCapability.notifyChannel`, `notifyHere`, `notifyRole`, and `notifyGroup` capabilities, with matching `Channel.canNotifyChannel` / `canNotifyHere` / `canNotifyRole` / `canNotifyGroup` getters. +- Added `ChannelCapability.createMention` capability with a matching `Channel.canCreateMention` getter. - Added `ChannelConfig.pushLevel`, `pushNotifications`, and `chatPreferences` for per-channel-type push configuration. - Added `PushLevel` and `ChatPreferenceLevel` extension types and the `ChatPreferences` model — granular per-category push preferences (direct, channel, here, role, group mentions, thread replies, default). - Added `chatPreferences` field to `PushPreferenceInput`, `PushPreference`, and `ChannelPushPreference`. diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 3974dede6..975654f9c 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -4589,4 +4589,9 @@ extension ChannelCapabilityCheck on Channel { bool get canNotifyGroup { return ownCapabilities.contains(ChannelCapability.notifyGroup); } + + /// True, if the current user can mention a user in a message. + bool get canCreateMention { + return ownCapabilities.contains(ChannelCapability.createMention); + } } diff --git a/packages/stream_chat/lib/src/core/models/channel_model.dart b/packages/stream_chat/lib/src/core/models/channel_model.dart index 41c0e5756..8fb6bf742 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -407,4 +407,7 @@ extension type const ChannelCapability(String capability) implements String { /// Ability to mention one or more user groups in a message. static const notifyGroup = ChannelCapability('notify-group'); + + /// Ability to mention a user in a message. + static const createMention = ChannelCapability('create-mention'); } diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart index e04c6799b..92d5cb80f 100644 --- a/packages/stream_chat/test/src/client/channel_test.dart +++ b/packages/stream_chat/test/src/client/channel_test.dart @@ -8529,6 +8529,12 @@ void main() { (channel) => channel.canNotifyGroup, ); + testCapability( + 'CreateMentions', + ChannelCapability.createMention, + (channel) => channel.canCreateMention, + ); + test('returns correct values with multiple capabilities', () { final channelState = _generateChannelState( channelId, diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index b8e76a472..3b7242abc 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -7,6 +7,7 @@ ✅ Added - Added support for `@channel`, `@here`, role, and user-group mentions — parsed on incoming messages, rendered as styled tappable spans in message text, and selectable from the composer's `@` autocomplete. +- `StreamMentionAutocompleteOptions` now hides user suggestions when the channel lacks the `create-mention` capability, skipping the `queryMembers` / `queryUsers` calls. - Added a single `mentionItemBuilder` on `StreamMessageComposer` and `StreamMentionAutocompleteOptions` that receives `StreamMentionItemProps` and covers every mention kind. Customise globally via `streamChatComponentBuilders(mentionItem: ...)` or per-instance via the new constructor parameter. Defaults are rendered by `DefaultStreamMentionItem`. Also added `onMention*Tap` callbacks on `StreamMentionAutocompleteOptions`. - Added `StreamMessageListView.onMentionTap` and `StreamMessageItem.onMentionTap` — receives a typed `StreamMention` (`StreamUserMention`, `StreamChannelMention`, `StreamHereMention`, `StreamRoleMention`, or `StreamGroupMention`). - `StreamMessageComposer` now surfaces the hold-to-record hint through `StreamSnackbar` anchored above the composer, and `StreamChat` provides an app-wide `StreamSnackbarScope` fallback. diff --git a/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart b/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart index 01c43f439..c9a19f6f9 100644 --- a/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart +++ b/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart @@ -242,6 +242,7 @@ class _StreamMentionAutocompleteOptionsState extends State> _fetchUsers(String query) async { + if (!widget.channel.canCreateMention) return const []; try { if (widget.mentionAllAppUsers) { return await _queryUsers(query); diff --git a/packages/stream_chat_flutter/test/src/autocomplete/stream_mention_autocomplete_options_test.dart b/packages/stream_chat_flutter/test/src/autocomplete/stream_mention_autocomplete_options_test.dart index 4f61e1b11..d89c61c5c 100644 --- a/packages/stream_chat_flutter/test/src/autocomplete/stream_mention_autocomplete_options_test.dart +++ b/packages/stream_chat_flutter/test/src/autocomplete/stream_mention_autocomplete_options_test.dart @@ -511,11 +511,39 @@ void main() { user: User(id: id, name: name), ); + testWidgets('query does not call queryUser or queryMembers when createMention capability is missing', ( + tester, + ) async { + final mocks = _setupMocks(ownCapabilities: const []); + + await _pumpMentionOptions( + tester, + client: mocks.client, + channel: mocks.channel, + query: 'ali', + ); + + verifyNever( + () => mocks.channel.queryMembers( + filter: any(named: 'filter'), + pagination: any(named: 'pagination'), + ), + ); + verifyNever( + () => mocks.client.queryUsers( + presence: any(named: 'presence'), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ); + }); + testWidgets( 'all members cached use in-memory search and skip queryMembers API call', (tester) async { final mocks = _setupMocks( - ownCapabilities: const [], + ownCapabilities: const [ChannelCapability.createMention], members: [ buildMember('alice-id', 'Alice'), buildMember('bob-id', 'Bob'), @@ -546,7 +574,7 @@ void main() { 'partial cache (memberCount > cached members) triggers queryMembers API call', (tester) async { final mocks = _setupMocks( - ownCapabilities: const [], + ownCapabilities: const [ChannelCapability.createMention], members: [ buildMember('user-1', 'User 1'), buildMember('user-2', 'User 2'), @@ -583,7 +611,7 @@ void main() { 'null memberCount falls back to remote queryMembers', (tester) async { final mocks = _setupMocks( - ownCapabilities: const [], + ownCapabilities: const [ChannelCapability.createMention], members: [buildMember('cached-id', 'Cached')], memberCount: null, ); @@ -618,7 +646,7 @@ void main() { 'local search includes watchers', (tester) async { final mocks = _setupMocks( - ownCapabilities: const [], + ownCapabilities: const [ChannelCapability.createMention], members: [buildMember('alice-id', 'Alice')], watchers: [User(id: 'wally-id', name: 'Wally')], ); @@ -638,7 +666,7 @@ void main() { 'local results render alphabetically by name', (tester) async { final mocks = _setupMocks( - ownCapabilities: const [], + ownCapabilities: const [ChannelCapability.createMention], members: [ buildMember('c-id', 'Charlie'), buildMember('a-id', 'Alice'), @@ -665,7 +693,7 @@ void main() { 'remote queryMembers results render in server order', (tester) async { final mocks = _setupMocks( - ownCapabilities: const [], + ownCapabilities: const [ChannelCapability.createMention], members: [buildMember('cached-id', 'Cached')], memberCount: 50, ); @@ -701,7 +729,7 @@ void main() { testWidgets( 'mentionAllAppUsers calls client.queryUsers instead of channel.queryMembers', (tester) async { - final mocks = _setupMocks(ownCapabilities: const []); + final mocks = _setupMocks(ownCapabilities: const [ChannelCapability.createMention]); when( () => mocks.client.queryUsers( presence: any(named: 'presence'), @@ -743,7 +771,10 @@ void main() { 'queryMembers error is swallowed and does not blank the list', (tester) async { final mocks = _setupMocks( - ownCapabilities: const [ChannelCapability.notifyHere], + ownCapabilities: const [ + ChannelCapability.notifyHere, + ChannelCapability.createMention, + ], members: [buildMember('cached-id', 'Cached')], memberCount: 50, ); @@ -775,6 +806,7 @@ void main() { ChannelCapability.notifyHere, ChannelCapability.notifyRole, ChannelCapability.notifyGroup, + ChannelCapability.createMention, ], members: [ Member( @@ -858,6 +890,7 @@ void main() { ChannelCapability.notifyHere, ChannelCapability.notifyRole, ChannelCapability.notifyGroup, + ChannelCapability.createMention, ], members: [ Member(