diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 67cdcfb0..ae525f7b 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -330,7 +330,7 @@ class Channel { state?.addMessage(message); try { - if (message.attachments?.isNotEmpty == true) { + if (message.attachments?.any((it) => !it.uploadState.isSuccess) == true) { final attachmentsUploadCompleter = Completer(); _messageAttachmentsUploadCompleter[message.id] = attachmentsUploadCompleter; @@ -383,7 +383,7 @@ class Channel { state?.addMessage(message); try { - if (message.attachments?.isNotEmpty == true) { + if (message.attachments?.any((it) => !it.uploadState.isSuccess) == true) { final attachmentsUploadCompleter = Completer(); _messageAttachmentsUploadCompleter[message.id] = attachmentsUploadCompleter; @@ -449,6 +449,41 @@ class Channel { } } + /// Pins provided message + Future pinMessage( + Message message, + Object timeoutOrExpirationDate, + ) { + assert(() { + if (timeoutOrExpirationDate is! DateTime && + timeoutOrExpirationDate is! num && + timeoutOrExpirationDate != null) { + throw ArgumentError('Invalid timeout or Expiration date'); + } + return true; + }()); + + DateTime pinExpires; + if (timeoutOrExpirationDate is DateTime) { + pinExpires = timeoutOrExpirationDate; + } else if (timeoutOrExpirationDate is num) { + pinExpires = DateTime.now().add( + Duration(seconds: timeoutOrExpirationDate.toInt()), + ); + } + return updateMessage( + message.copyWith( + pinned: true, + pinExpires: pinExpires, + ), + ); + } + + /// Unpins provided message + Future unpinMessage(Message message) { + return updateMessage(message.copyWith(pinned: false)); + } + /// Send a file to this channel Future sendFile( AttachmentFile file, { @@ -479,6 +514,26 @@ class Channel { ); } + /// A message search. + Future search({ + String query, + Map messageFilters, + List sort, + PaginationParams paginationParams, + }) { + return _client.search( + { + 'cid': { + r'$in': [cid], + }, + }, + sort: sort, + query: query, + paginationParams: paginationParams, + messageFilters: messageFilters, + ); + } + /// Delete a file from this channel Future deleteFile( String url, { diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 2cc3fd62..8b8d7c11 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -24,6 +24,7 @@ import 'exceptions.dart'; import 'models/event.dart'; import 'models/message.dart'; import 'models/user.dart'; +import 'extensions/map_extension.dart'; /// Handler function used for logging records. Function requires a single [LogRecord] /// as the only parameter. @@ -1021,27 +1022,39 @@ class StreamChatClient { /// A message search. Future search( - Map filters, - List sort, + Map filters, { String query, - PaginationParams paginationParams, { + List sort, + PaginationParams paginationParams, Map messageFilters, }) async { + assert(() { + if (filters == null || filters.isEmpty) { + throw ArgumentError('`filters` cannot be set as null or empty'); + } + if (query == null && messageFilters == null) { + throw ArgumentError('Provide at least `query` or `messageFilters`'); + } + if (query != null && messageFilters != null) { + throw ArgumentError( + "Can't provide both `query` and `messageFilters` at the same time", + ); + } + return true; + }()); + final payload = { 'filter_conditions': filters, - if (messageFilters != null) ...{ - 'message_filter_conditions': messageFilters, - }, + 'message_filter_conditions': messageFilters, 'query': query, 'sort': sort, - }; + if (paginationParams != null) ...paginationParams.toJson(), + }.nullProtected; - if (paginationParams != null) { - payload.addAll(paginationParams.toJson()); - } + final response = await get('/search', queryParameters: { + 'payload': json.encode(payload), + }); - final response = await get('/search', - queryParameters: {'payload': json.encode(payload)}); return decode( response.data, SearchMessagesResponse.fromJson); } @@ -1295,7 +1308,7 @@ class StreamChatClient { Future updateMessage(Message message) async { final response = await post( '/messages/${message.id}', - data: {'message': message}, + data: {'message': message.toJson()}, ); return decode(response.data, UpdateMessageResponse.fromJson); } @@ -1311,6 +1324,38 @@ class StreamChatClient { final response = await get('/messages/$messageId'); return decode(response.data, GetMessageResponse.fromJson); } + + /// Pins provided message + Future pinMessage( + Message message, + Object timeoutOrExpirationDate, + ) { + assert(() { + if (timeoutOrExpirationDate is! DateTime && + timeoutOrExpirationDate is! num && + timeoutOrExpirationDate != null) { + throw ArgumentError('Invalid timeout or Expiration date'); + } + return true; + }()); + + DateTime pinExpires; + if (timeoutOrExpirationDate is DateTime) { + pinExpires = timeoutOrExpirationDate.toUtc(); + } else if (timeoutOrExpirationDate is num) { + pinExpires = DateTime.now().add( + Duration(seconds: timeoutOrExpirationDate.toInt()), + ); + } + return updateMessage( + message.copyWith(pinned: true, pinExpires: pinExpires), + ); + } + + /// Unpins provided message + Future unpinMessage(Message message) { + return updateMessage(message.copyWith(pinned: false)); + } } /// The class that handles the state of the channel listening to the events diff --git a/packages/stream_chat/lib/src/extensions/map_extension.dart b/packages/stream_chat/lib/src/extensions/map_extension.dart new file mode 100644 index 00000000..3028c20a --- /dev/null +++ b/packages/stream_chat/lib/src/extensions/map_extension.dart @@ -0,0 +1,7 @@ +/// Useful extension functions for [Map] +extension MapX on Map { + /// Returns a new map with null keys or values removed + Map get nullProtected { + return {...this}..removeWhere((key, value) => key == null || value == null); + } +} diff --git a/packages/stream_chat/lib/src/models/channel_state.dart b/packages/stream_chat/lib/src/models/channel_state.dart index 59e7a4f1..36d68a0c 100644 --- a/packages/stream_chat/lib/src/models/channel_state.dart +++ b/packages/stream_chat/lib/src/models/channel_state.dart @@ -20,6 +20,9 @@ class ChannelState { /// A paginated list of channel members final List members; + /// A paginated list of pinned messages + final List pinnedMessages; + /// The count of users watching the channel final int watcherCount; @@ -34,6 +37,7 @@ class ChannelState { this.channel, this.messages = const [], this.members = const [], + this.pinnedMessages = const [], this.watcherCount, this.watchers = const [], this.read = const [], @@ -51,6 +55,7 @@ class ChannelState { ChannelModel channel, List messages, List members, + List pinnedMessages, int watcherCount, List watchers, List read, @@ -59,6 +64,7 @@ class ChannelState { channel: channel ?? this.channel, messages: messages ?? this.messages, members: members ?? this.members, + pinnedMessages: pinnedMessages ?? this.pinnedMessages, watcherCount: watcherCount ?? this.watcherCount, watchers: watchers ?? this.watchers, read: read ?? this.read, diff --git a/packages/stream_chat/lib/src/models/channel_state.g.dart b/packages/stream_chat/lib/src/models/channel_state.g.dart index d053768e..a66899a4 100644 --- a/packages/stream_chat/lib/src/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/models/channel_state.g.dart @@ -27,6 +27,13 @@ ChannelState _$ChannelStateFromJson(Map json) { (k, e) => MapEntry(k as String, e), ))) ?.toList(), + pinnedMessages: (json['pinned_messages'] as List) + ?.map((e) => e == null + ? null + : Message.fromJson((e as Map)?.map( + (k, e) => MapEntry(k as String, e), + ))) + ?.toList(), watcherCount: json['watcher_count'] as int, watchers: (json['watchers'] as List) ?.map((e) => e == null @@ -50,6 +57,8 @@ Map _$ChannelStateToJson(ChannelState instance) => 'channel': instance.channel?.toJson(), 'messages': instance.messages?.map((e) => e?.toJson())?.toList(), 'members': instance.members?.map((e) => e?.toJson())?.toList(), + 'pinned_messages': + instance.pinnedMessages?.map((e) => e?.toJson())?.toList(), 'watcher_count': instance.watcherCount, 'watchers': instance.watchers?.map((e) => e?.toJson())?.toList(), 'read': instance.read?.map((e) => e?.toJson())?.toList(), diff --git a/packages/stream_chat/lib/src/models/message.dart b/packages/stream_chat/lib/src/models/message.dart index 3127bba3..df1906f3 100644 --- a/packages/stream_chat/lib/src/models/message.dart +++ b/packages/stream_chat/lib/src/models/message.dart @@ -8,6 +8,12 @@ import 'user.dart'; part 'message.g.dart'; +class _PinExpires { + const _PinExpires(); +} + +const _pinExpires = _PinExpires(); + /// Enum defining the status of a sending message enum MessageSendingStatus { /// Message is being sent @@ -117,6 +123,22 @@ class Message { @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final User user; + /// If true the message is pinned + final bool pinned; + + /// Reserved field indicating when the message was pinned + @JsonKey(toJson: Serialization.readOnly) + final DateTime pinnedAt; + + /// Reserved field indicating when the message will expire + /// + /// if `null` message has no expiry + final DateTime pinExpires; + + /// Reserved field indicating who pinned the message + @JsonKey(toJson: Serialization.readOnly) + final User pinnedBy; + /// Message custom extraData @JsonKey(includeIfNull: false) final Map extraData; @@ -160,6 +182,10 @@ class Message { 'updated_at', 'deleted_at', 'user', + 'pinned', + 'pinned_at', + 'pin_expires', + 'pinned_by', ]; /// Constructor used for json serialization @@ -185,10 +211,15 @@ class Message { this.createdAt, this.updatedAt, this.user, + this.pinned = false, + this.pinnedAt, + DateTime pinExpires, + this.pinnedBy, this.extraData, this.deletedAt, this.status = MessageSendingStatus.sent, - }) : id = id ?? Uuid().v4(); + }) : id = id ?? Uuid().v4(), + pinExpires = pinExpires?.toUtc(); /// Create a new instance from a json factory Message.fromJson(Map json) => _$MessageFromJson( @@ -222,35 +253,52 @@ class Message { DateTime updatedAt, DateTime deletedAt, User user, + bool pinned, + DateTime pinnedAt, + Object pinExpires = _pinExpires, + User pinnedBy, Map extraData, MessageSendingStatus status, - }) => - Message( - id: id ?? this.id, - text: text ?? this.text, - type: type ?? this.type, - attachments: attachments ?? this.attachments, - mentionedUsers: mentionedUsers ?? this.mentionedUsers, - reactionCounts: reactionCounts ?? this.reactionCounts, - reactionScores: reactionScores ?? this.reactionScores, - latestReactions: latestReactions ?? this.latestReactions, - ownReactions: ownReactions ?? this.ownReactions, - parentId: parentId ?? this.parentId, - quotedMessage: quotedMessage ?? this.quotedMessage, - quotedMessageId: quotedMessageId ?? this.quotedMessageId, - replyCount: replyCount ?? this.replyCount, - threadParticipants: threadParticipants ?? this.threadParticipants, - showInChannel: showInChannel ?? this.showInChannel, - command: command ?? this.command, - createdAt: createdAt ?? this.createdAt, - silent: silent ?? this.silent, - extraData: extraData ?? this.extraData, - user: user ?? this.user, - shadowed: shadowed ?? this.shadowed, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt ?? this.deletedAt, - status: status ?? this.status, - ); + }) { + assert(() { + if (pinExpires is! DateTime && + pinExpires != null && + pinExpires is! _PinExpires) { + throw ArgumentError('`pinExpires` can only be set as DateTime or null'); + } + return true; + }()); + return Message( + id: id ?? this.id, + text: text ?? this.text, + type: type ?? this.type, + attachments: attachments ?? this.attachments, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, + reactionCounts: reactionCounts ?? this.reactionCounts, + reactionScores: reactionScores ?? this.reactionScores, + latestReactions: latestReactions ?? this.latestReactions, + ownReactions: ownReactions ?? this.ownReactions, + parentId: parentId ?? this.parentId, + quotedMessage: quotedMessage ?? this.quotedMessage, + quotedMessageId: quotedMessageId ?? this.quotedMessageId, + replyCount: replyCount ?? this.replyCount, + threadParticipants: threadParticipants ?? this.threadParticipants, + showInChannel: showInChannel ?? this.showInChannel, + command: command ?? this.command, + createdAt: createdAt ?? this.createdAt, + silent: silent ?? this.silent, + extraData: extraData ?? this.extraData, + user: user ?? this.user, + shadowed: shadowed ?? this.shadowed, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + status: status ?? this.status, + pinned: pinned ?? this.pinned, + pinnedAt: pinnedAt ?? this.pinnedAt, + pinnedBy: pinnedBy ?? this.pinnedBy, + pinExpires: pinExpires == _pinExpires ? this.pinExpires : pinExpires, + ); + } /// Returns a new [Message] that is a combination of this message and the given /// [other] message. @@ -281,6 +329,10 @@ class Message { updatedAt: other.updatedAt, deletedAt: other.deletedAt, status: other.status, + pinned: other.pinned, + pinnedAt: other.pinnedAt, + pinExpires: other.pinExpires, + pinnedBy: other.pinnedBy, ); } } diff --git a/packages/stream_chat/lib/src/models/message.g.dart b/packages/stream_chat/lib/src/models/message.g.dart index c30d8fe5..f8ac0c3d 100644 --- a/packages/stream_chat/lib/src/models/message.g.dart +++ b/packages/stream_chat/lib/src/models/message.g.dart @@ -75,6 +75,18 @@ Message _$MessageFromJson(Map json) { : User.fromJson((json['user'] as Map)?.map( (k, e) => MapEntry(k as String, e), )), + pinned: json['pinned'] as bool, + pinnedAt: json['pinned_at'] == null + ? null + : DateTime.parse(json['pinned_at'] as String), + pinExpires: json['pin_expires'] == null + ? null + : DateTime.parse(json['pin_expires'] as String), + pinnedBy: json['pinned_by'] == null + ? null + : User.fromJson((json['pinned_by'] as Map)?.map( + (k, e) => MapEntry(k as String, e), + )), extraData: (json['extra_data'] as Map)?.map( (k, e) => MapEntry(k as String, e), ), @@ -116,6 +128,10 @@ Map _$MessageToJson(Message instance) { writeNotNull('created_at', readonly(instance.createdAt)); writeNotNull('updated_at', readonly(instance.updatedAt)); writeNotNull('user', readonly(instance.user)); + val['pinned'] = instance.pinned; + val['pinned_at'] = readonly(instance.pinnedAt); + val['pin_expires'] = instance.pinExpires?.toIso8601String(); + val['pinned_by'] = readonly(instance.pinnedBy); writeNotNull('extra_data', instance.extraData); writeNotNull('deleted_at', readonly(instance.deletedAt)); return val; diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 937113da..755a3f3b 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -273,6 +273,79 @@ void main() { verify(mockDio.delete('/channels/messaging/testid/image', queryParameters: {'url': url})).called(1); }); + + test('pinMessage should throw argument error', () { + final client = StreamChatClient('api-key'); + + final channelClient = client.channel('messaging', id: 'testid'); + + final message = Message(text: 'Hello'); + + expect( + () => channelClient.pinMessage(message, 'InvalidType'), + throwsArgumentError, + ); + }); + + test('should be pinned successfully', () async { + final mockDio = MockDio(); + + when(mockDio.options).thenReturn(BaseOptions()); + when(mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient( + 'api-key', + httpClient: mockDio, + tokenProvider: (_) async => '', + ); + + final channelClient = client.channel('messaging', id: 'testid'); + + final message = Message( + text: 'Hello', + id: 'test', + ); + + when(mockDio.post( + '/messages/${message.id}', + data: anything, + )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + + await channelClient.pinMessage(message, 30); + + verify(mockDio.post('/messages/${message.id}', data: anything)) + .called(1); + }); + + test('should be unpinned successfully', () async { + final mockDio = MockDio(); + + when(mockDio.options).thenReturn(BaseOptions()); + when(mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient( + 'api-key', + httpClient: mockDio, + tokenProvider: (_) async => '', + ); + + final channelClient = client.channel('messaging', id: 'testid'); + + final message = Message( + text: 'Hello', + id: 'test', + ); + + when(mockDio.post( + '/messages/${message.id}', + data: anything, + )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + + await channelClient.unpinMessage(message); + + verify(mockDio.post('/messages/${message.id}', data: anything)) + .called(1); + }); }); test('sendEvent', () async { diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index c9fca7b1..b38185b5 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -180,18 +180,25 @@ void main() { httpClient: mockDio, ); + final filter = { + 'cid': { + r'$in': ['messaging:testId'] + } + }; + + final query = 'hello'; + final queryParams = { 'payload': json.encode({ - "filter_conditions": null, - 'query': null, - 'sort': null, + 'filter_conditions': filter, + 'query': query, }), }; when(mockDio.get('/search', queryParameters: queryParams)) .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); - await client.search(null, null, null, null); + await client.search(filter, query: query); verify(mockDio.get('/search', queryParameters: queryParams)) .called(1); @@ -218,10 +225,10 @@ void main() { final queryParams = { 'payload': json.encode({ - "filter_conditions": filters, + 'filter_conditions': filters, 'query': query, 'sort': sortOptions, - "limit": 10, + 'limit': 10, }), }; @@ -230,9 +237,9 @@ void main() { await client.search( filters, - sortOptions, - query, - PaginationParams(), + sort: sortOptions, + query: query, + paginationParams: PaginationParams(), ); verify(mockDio.get('/search', queryParameters: queryParams)) @@ -648,7 +655,7 @@ void main() { when(mockDio.post( '/messages/${message.id}', - data: {'message': message}, + data: {'message': anything}, )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); await client.updateMessage(message); @@ -934,6 +941,56 @@ void main() { expect(client.delete('/test'), throwsA(ApiError('test error', 400))); }); }); + + group('pin message', () { + final mockDio = MockDio(); + + when(mockDio.options).thenReturn(BaseOptions()); + when(mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient( + 'api-key', + httpClient: mockDio, + ); + + test('should throw argument error', () { + final message = Message(text: 'Hello'); + expect( + () => client.pinMessage(message, 'InvalidType'), + throwsArgumentError, + ); + }); + + test('should complete successfully', () async { + final timeout = 30; + final message = Message(text: 'Hello'); + + when(mockDio.post( + '/messages/${message.id}', + data: anything, + )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + + await client.pinMessage(message, timeout); + + verify(mockDio.post('/messages/${message.id}', + data: {'message': anything})).called(1); + }); + + test('should unpin message successfully', () async { + final message = Message(text: 'Hello'); + + when(mockDio.post( + '/messages/${message.id}', + data: anything, + )).thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + + await client.unpinMessage(message); + + verify(mockDio.post('/messages/${message.id}', + data: anything)) + .called(1); + }); + }); }); group('channel', () { diff --git a/packages/stream_chat/test/src/models/channel_state_test.dart b/packages/stream_chat/test/src/models/channel_state_test.dart index b8ce3c80..538f1812 100644 --- a/packages/stream_chat/test/src/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/models/channel_state_test.dart @@ -87,7 +87,11 @@ void main() { "updated_at": "2020-01-29T03:23:02.843949Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-e8e74482-b4cd-48db-9d1e-30e6c191786f", @@ -114,7 +118,11 @@ void main() { "updated_at": "2020-01-29T03:23:07.981091Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0", @@ -141,7 +149,11 @@ void main() { "updated_at": "2020-01-29T03:23:11.568022Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-80925be0-786e-40a5-b225-486518dafd35", @@ -168,7 +180,11 @@ void main() { "updated_at": "2020-01-29T03:32:57.403566Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe", @@ -195,7 +211,11 @@ void main() { "updated_at": "2020-01-29T03:33:35.294802Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "withered-cell-0-84cbd760-cf55-4f7e-9207-c5f66cccc6dc", @@ -222,7 +242,11 @@ void main() { "updated_at": "2020-01-29T03:34:27.393296Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-e9203588-43c3-40b1-91f7-f217fc42aa53", @@ -249,7 +273,11 @@ void main() { "updated_at": "2020-01-29T03:34:37.638376Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "withered-cell-0-7e3552d7-7a0d-45f2-a856-e91b23a7e240", @@ -276,7 +304,11 @@ void main() { "updated_at": "2020-01-29T03:35:04.301566Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-1ffeafd4-e4fc-4c84-9394-9d7cb10fff42", @@ -303,7 +335,11 @@ void main() { "updated_at": "2020-01-29T03:35:24.939085Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-3f147324-12c8-4b41-9fb5-2db88d065efa", @@ -330,7 +366,11 @@ void main() { "updated_at": "2020-01-29T03:35:33.101566Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-51a348ae-0c0a-44de-a556-eac7891c0cf0", @@ -357,7 +397,11 @@ void main() { "updated_at": "2020-01-29T03:35:45.458685Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "icy-recipe-7-a29e237b-8d81-4a97-9bc8-d42bca3f1356", @@ -384,7 +428,11 @@ void main() { "updated_at": "2020-01-29T07:02:11.535395Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055", @@ -411,7 +459,11 @@ void main() { "updated_at": "2020-01-29T07:02:22.485136Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "throbbing-boat-5-1e4d5730-5ff0-4d25-9948-9f34ffda43e4", @@ -438,7 +490,11 @@ void main() { "updated_at": "2020-01-29T14:12:04.688552Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21", @@ -465,7 +521,11 @@ void main() { "updated_at": "2020-01-29T15:29:36.011316Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "snowy-credit-3-3319537e-2d0e-4876-8170-a54f046e4b7d", @@ -492,7 +552,11 @@ void main() { "updated_at": "2020-01-29T15:29:41.677819Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d", @@ -519,7 +583,11 @@ void main() { "updated_at": "2020-01-29T15:29:43.354177Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "snowy-credit-3-cebe25a7-a3a3-49fc-9919-91c6725e81f3", @@ -546,7 +614,11 @@ void main() { "updated_at": "2020-01-29T15:29:44.754713Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "divine-glade-9-0cea9262-5766-48e9-8b22-311870aed3bf", @@ -573,7 +645,11 @@ void main() { "updated_at": "2020-01-29T17:02:36.933852Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "red-firefly-9-c4e9007b-bb7d-4238-ae08-5f8e3cd03d73", @@ -600,7 +676,11 @@ void main() { "updated_at": "2020-01-29T22:14:08.54062Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "bitter-glade-2-02aee4eb-4093-4736-808b-2de75820e854", @@ -627,7 +707,11 @@ void main() { "updated_at": "2020-01-30T13:11:37.191293Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "morning-sea-1-0c700bcb-46dd-4224-b590-e77bdbccc480", @@ -654,7 +738,11 @@ void main() { "updated_at": "2020-01-30T13:33:16.853116Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "ancient-salad-0-53e8b4e6-5b7b-43ad-aeee-8bfb6a9ed0be", @@ -681,7 +769,11 @@ void main() { "updated_at": "2020-01-30T13:36:52.749732Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "ancient-salad-0-8c225075-bd4c-42e2-8024-530aae13cd40", @@ -708,7 +800,11 @@ void main() { "updated_at": "2020-01-30T13:37:41.631056Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "proud-sea-7-17802096-cbf8-4e3c-addd-4ee31f4c8b5c", @@ -735,7 +831,11 @@ void main() { "updated_at": "2020-01-30T13:43:41.062362Z", "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null } ], "watcher_count": 5, @@ -797,7 +897,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-e8e74482-b4cd-48db-9d1e-30e6c191786f", @@ -809,7 +913,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0", @@ -821,7 +929,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-80925be0-786e-40a5-b225-486518dafd35", @@ -833,7 +945,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe", @@ -845,7 +961,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "withered-cell-0-84cbd760-cf55-4f7e-9207-c5f66cccc6dc", @@ -857,7 +977,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-e9203588-43c3-40b1-91f7-f217fc42aa53", @@ -869,7 +993,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "withered-cell-0-7e3552d7-7a0d-45f2-a856-e91b23a7e240", @@ -881,7 +1009,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-1ffeafd4-e4fc-4c84-9394-9d7cb10fff42", @@ -893,7 +1025,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-3f147324-12c8-4b41-9fb5-2db88d065efa", @@ -905,7 +1041,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "dry-meadow-0-51a348ae-0c0a-44de-a556-eac7891c0cf0", @@ -917,7 +1057,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "icy-recipe-7-a29e237b-8d81-4a97-9bc8-d42bca3f1356", @@ -929,7 +1073,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055", @@ -941,7 +1089,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "throbbing-boat-5-1e4d5730-5ff0-4d25-9948-9f34ffda43e4", @@ -953,7 +1105,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21", @@ -965,7 +1121,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "snowy-credit-3-3319537e-2d0e-4876-8170-a54f046e4b7d", @@ -977,7 +1137,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d", @@ -989,7 +1153,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "snowy-credit-3-cebe25a7-a3a3-49fc-9919-91c6725e81f3", @@ -1001,7 +1169,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "divine-glade-9-0cea9262-5766-48e9-8b22-311870aed3bf", @@ -1013,7 +1185,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "red-firefly-9-c4e9007b-bb7d-4238-ae08-5f8e3cd03d73", @@ -1025,7 +1201,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "bitter-glade-2-02aee4eb-4093-4736-808b-2de75820e854", @@ -1037,7 +1217,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "morning-sea-1-0c700bcb-46dd-4224-b590-e77bdbccc480", @@ -1049,7 +1233,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "ancient-salad-0-53e8b4e6-5b7b-43ad-aeee-8bfb6a9ed0be", @@ -1061,7 +1249,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "ancient-salad-0-8c225075-bd4c-42e2-8024-530aae13cd40", @@ -1073,7 +1265,11 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null }, { "id": "proud-sea-7-17802096-cbf8-4e3c-addd-4ee31f4c8b5c", @@ -1085,9 +1281,14 @@ void main() { "show_in_channel": null, "mentioned_users": [], "status": "SENT", - "silent": false + "silent": false, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null } ], + "pinned_messages": [], "members": [], "watcher_count": 5 } @@ -1100,6 +1301,7 @@ void main() { (j['messages'] as List).map((m) => Message.fromJson(m)).toList(), read: null, watcherCount: 5, + pinnedMessages: [], watchers: null, ); diff --git a/packages/stream_chat/test/src/models/message_test.dart b/packages/stream_chat/test/src/models/message_test.dart index 7541f722..fe8fc44a 100644 --- a/packages/stream_chat/test/src/models/message_test.dart +++ b/packages/stream_chat/test/src/models/message_test.dart @@ -64,6 +64,10 @@ void main() { "reaction_scores": { "love": 1 }, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null, "reply_count": 0, "created_at": "2020-01-28T22:17:31.107978Z", "updated_at": "2020-01-28T22:17:31.130506Z", @@ -86,6 +90,10 @@ void main() { expect(message.createdAt, DateTime.parse("2020-01-28T22:17:31.107978Z")); expect(message.updatedAt, DateTime.parse("2020-01-28T22:17:31.130506Z")); expect(message.mentionedUsers, isA>()); + expect(message.pinned, false); + expect(message.pinnedAt, null); + expect(message.pinExpires, null); + expect(message.pinnedBy, null); }); test('should serialize to json correctly', () { @@ -143,6 +151,10 @@ void main() { "parent_id": "parentId", "quoted_message": null, "quoted_message_id": null, + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null, "show_in_channel": true, "hey": "test" } diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart index 9839994f..71488cc8 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart @@ -132,9 +132,9 @@ class MessageSearchBlocState extends State final client = StreamChatCore.of(context).client; return client.search( filter, - sort, - query, - pagination, + sort: sort, + query: query, + paginationParams: pagination, messageFilters: messageFilter, ); }