diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 13cb44bcd..f021975fe 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -8,6 +8,15 @@ - [[#1774]](https://github.com/GetStream/stream-chat-flutter/issues/1774) Fixed failed to execute 'close' on 'WebSocket'. - [[#2016]](https://github.com/GetStream/stream-chat-flutter/issues/2016) Fix muted channel's unreadCount incorrectly updated. + +ЁЯФД Changed + +- Refactored identifying the `Attachment.uploadState` logic for local and remote attachments. Also updated the logic for determining the attachment type to check for ogScrapeUrl instead of `AttachmentType.giphy`. + +тЬЕ Added + +- [[#2101]](https://github.com/GetStream/stream-chat-flutter/issues/2101) Added support for system messages not updating `channel.lastMessageAt` +- Added support for sending private or restricted visibility messages. ## 9.4.0 diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index d16ea79ec..4280fa460 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1911,11 +1911,13 @@ class ChannelClientState { ChannelClientState( this._channel, ChannelState channelState, - //ignore: unnecessary_parenthesis - ) : _debouncedUpdatePersistenceChannelState = ((ChannelState state) => - _channel._client.chatPersistenceClient - ?.updateChannelState(state)) - .debounced(const Duration(seconds: 1)) { + ) : _debouncedUpdatePersistenceChannelState = debounce( + (ChannelState state) { + final persistenceClient = _channel._client.chatPersistenceClient; + return persistenceClient?.updateChannelState(state); + }, + const Duration(seconds: 1), + ) { _retryQueue = RetryQueue( channel: _channel, logger: _channel.client.detachedLogger( @@ -2495,6 +2497,25 @@ class ChannelClientState { })); } + // Logic taken from the backend SDK + // https://github.com/GetStream/chat/blob/9245c2b3f7e679267d57ee510c60e93de051cb8e/types/channel.go#L1136-L1150 + bool _shouldUpdateChannelLastMessageAt(Message message) { + if (message.shadowed) return false; + if (message.isEphemeral) return false; + + final config = channelState.channel?.config; + if (message.isSystem && config?.skipLastMsgUpdateForSystemMsgs == true) { + return false; + } + + final currentUserId = _channel._client.state.currentUser?.id; + if (currentUserId case final userId? when message.isNotVisibleTo(userId)) { + return false; + } + + return true; + } + /// Updates the [message] in the state if it exists. Adds it otherwise. void updateMessage(Message message) { // Determine if the message should be displayed in the channel view. @@ -2547,12 +2568,19 @@ class ChannelClientState { // Handle updates to pinned messages. final newPinnedMessages = _updatePinnedMessages(message); + // Calculate the new last message at time. + var lastMessageAt = _channelState.channel?.lastMessageAt; + lastMessageAt ??= message.createdAt; + if (_shouldUpdateChannelLastMessageAt(message)) { + lastMessageAt = [lastMessageAt, message.createdAt].max; + } + // Apply the updated lists to the channel state. _channelState = _channelState.copyWith( messages: newMessages.sorted(_sortByCreatedAt), pinnedMessages: newPinnedMessages, channel: _channelState.channel?.copyWith( - lastMessageAt: message.createdAt, + lastMessageAt: lastMessageAt, ), ); } diff --git a/packages/stream_chat/lib/src/core/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart index afc825b6a..00bd8d96f 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -51,11 +51,10 @@ class Attachment extends Equatable { this.originalHeight, Map extraData = const {}, this.file, - UploadState? uploadState, + this.uploadState = const UploadState.preparing(), }) : id = id ?? const Uuid().v4(), _type = type, title = title ?? file?.name, - _uploadState = uploadState, localUri = file?.path != null ? Uri.parse(file!.path!) : null, // For backwards compatibility, // set 'file_size', 'mime_type' in [extraData]. @@ -97,9 +96,9 @@ class Attachment extends Equatable { ///The attachment type based on the URL resource. This can be: audio, ///image or video String? get type { - // If the attachment contains titleLink but is not of type giphy, we - // consider it as a urlPreview. - if (_type != AttachmentType.giphy && titleLink != null) { + // If the attachment contains ogScrapeUrl as well as titleLink, we consider + // it as a urlPreview. + if (ogScrapeUrl != null && titleLink != null) { return AttachmentType.urlPreview; } @@ -163,15 +162,8 @@ class Attachment extends Equatable { final AttachmentFile? file; /// The current upload state of the attachment - UploadState get uploadState { - if (_uploadState case final state?) return state; - - return ((assetUrl != null || imageUrl != null || thumbUrl != null) - ? const UploadState.success() - : const UploadState.preparing()); - } - - final UploadState? _uploadState; + @JsonKey(defaultValue: UploadState.success) + final UploadState uploadState; /// Map of custom channel extraData final Map extraData; diff --git a/packages/stream_chat/lib/src/core/models/attachment.g.dart b/packages/stream_chat/lib/src/core/models/attachment.g.dart index 689f8871e..42d656b75 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.g.dart @@ -36,7 +36,7 @@ Attachment _$AttachmentFromJson(Map json) => Attachment( ? null : AttachmentFile.fromJson(json['file'] as Map), uploadState: json['upload_state'] == null - ? null + ? const UploadState.success() : UploadState.fromJson(json['upload_state'] as Map), ); diff --git a/packages/stream_chat/lib/src/core/models/channel_config.dart b/packages/stream_chat/lib/src/core/models/channel_config.dart index 7aa625a23..b15c10992 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.dart @@ -24,6 +24,7 @@ class ChannelConfig { this.typingEvents = false, this.uploads = false, this.urlEnrichment = false, + this.skipLastMsgUpdateForSystemMsgs = false, }) : createdAt = createdAt ?? DateTime.now(), updatedAt = updatedAt ?? DateTime.now(); @@ -79,6 +80,13 @@ class ChannelConfig { /// True if urls appears as attachments final bool urlEnrichment; + /// If true the last message at date will not be updated when a system message + /// is added. + /// + /// This is useful for scenarios where you want to track the last time a user + /// message was added to the channel. + final bool skipLastMsgUpdateForSystemMsgs; + /// Serialize to json Map toJson() => _$ChannelConfigToJson(this); } diff --git a/packages/stream_chat/lib/src/core/models/channel_config.g.dart b/packages/stream_chat/lib/src/core/models/channel_config.g.dart index efacc44db..11f188a61 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.g.dart @@ -31,6 +31,8 @@ ChannelConfig _$ChannelConfigFromJson(Map json) => typingEvents: json['typing_events'] as bool? ?? false, uploads: json['uploads'] as bool? ?? false, urlEnrichment: json['url_enrichment'] as bool? ?? false, + skipLastMsgUpdateForSystemMsgs: + json['skip_last_msg_update_for_system_msgs'] as bool? ?? false, ); Map _$ChannelConfigToJson(ChannelConfig instance) => @@ -51,4 +53,6 @@ Map _$ChannelConfigToJson(ChannelConfig instance) => 'typing_events': instance.typingEvents, 'uploads': instance.uploads, 'url_enrichment': instance.urlEnrichment, + 'skip_last_msg_update_for_system_msgs': + instance.skipLastMsgUpdateForSystemMsgs, }; diff --git a/packages/stream_chat/lib/src/core/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart index 99563e6eb..0be0dca50 100644 --- a/packages/stream_chat/lib/src/core/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -26,6 +26,7 @@ class Event { this.member, this.channelId, this.channelType, + this.channelLastMessageAt, this.parentId, this.hardDelete, this.aiState, @@ -58,6 +59,9 @@ class Event { /// The channel type to which the event belongs final String? channelType; + /// The dateTime at which the last message was sent in the channel. + final DateTime? channelLastMessageAt; + /// The connection id in which the event has been sent final String? connectionId; @@ -168,6 +172,7 @@ class Event { 'member', 'channel_id', 'channel_type', + 'channel_last_message_at', 'parent_id', 'hard_delete', 'is_local', @@ -190,6 +195,7 @@ class Event { String? cid, String? channelId, String? channelType, + DateTime? channelLastMessageAt, String? connectionId, DateTime? createdAt, OwnUser? me, @@ -231,6 +237,7 @@ class Event { member: member ?? this.member, channelId: channelId ?? this.channelId, channelType: channelType ?? this.channelType, + channelLastMessageAt: channelLastMessageAt ?? this.channelLastMessageAt, parentId: parentId ?? this.parentId, hardDelete: hardDelete ?? this.hardDelete, aiState: aiState ?? this.aiState, diff --git a/packages/stream_chat/lib/src/core/models/event.g.dart b/packages/stream_chat/lib/src/core/models/event.g.dart index ee1bf2051..e0923750f 100644 --- a/packages/stream_chat/lib/src/core/models/event.g.dart +++ b/packages/stream_chat/lib/src/core/models/event.g.dart @@ -42,6 +42,9 @@ Event _$EventFromJson(Map json) => Event( : Member.fromJson(json['member'] as Map), channelId: json['channel_id'] as String?, channelType: json['channel_type'] as String?, + channelLastMessageAt: json['channel_last_message_at'] == null + ? null + : DateTime.parse(json['channel_last_message_at'] as String), parentId: json['parent_id'] as String?, hardDelete: json['hard_delete'] as bool?, aiState: $enumDecodeNullable(_$AITypingStateEnumMap, json['ai_state'], @@ -62,6 +65,8 @@ Map _$EventToJson(Event instance) => { 'cid': instance.cid, 'channel_id': instance.channelId, 'channel_type': instance.channelType, + 'channel_last_message_at': + instance.channelLastMessageAt?.toIso8601String(), 'connection_id': instance.connectionId, 'created_at': instance.createdAt.toIso8601String(), 'me': instance.me?.toJson(), diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index ac1be2e49..75e45f092 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -56,6 +56,7 @@ class Message extends Equatable { this.extraData = const {}, this.state = const MessageState.initial(), this.i18n, + this.restrictedVisibility, }) : id = id ?? const Uuid().v4(), pinExpires = pinExpires?.toUtc(), remoteCreatedAt = createdAt, @@ -237,6 +238,14 @@ class Message extends Equatable { String? get pollId => _pollId ?? poll?.id; final String? _pollId; + /// The list of user ids that should be able to see the message. + /// + /// If null or empty, the message is visible to all users. + /// If populated, only users whose ids are included in this list can see + /// the message. + @JsonKey(includeIfNull: false) + final List? restrictedVisibility; + /// Message custom extraData. final Map extraData; @@ -291,6 +300,7 @@ class Message extends Equatable { 'i18n', 'poll', 'poll_id', + 'restricted_visibility', ]; /// Serialize to json. @@ -335,6 +345,7 @@ class Message extends Equatable { Map? extraData, MessageState? state, Map? i18n, + List? restrictedVisibility, }) { assert(() { if (pinExpires is! DateTime && @@ -408,6 +419,7 @@ class Message extends Equatable { extraData: extraData ?? this.extraData, state: state ?? this.state, i18n: i18n ?? this.i18n, + restrictedVisibility: restrictedVisibility ?? this.restrictedVisibility, ); } @@ -450,6 +462,7 @@ class Message extends Equatable { extraData: other.extraData, state: other.state, i18n: other.i18n, + restrictedVisibility: other.restrictedVisibility, ); } @@ -512,5 +525,58 @@ class Message extends Equatable { extraData, state, i18n, + restrictedVisibility, ]; } + +/// Extension that adds visibility control functionality to Message objects. +/// +/// This extension provides methods to determine if a message is visible to a +/// specific user based on the [Message.restrictedVisibility] list. +extension MessageVisibility on Message { + /// Checks if this message has any visibility restrictions applied. + /// + /// Returns true if the restrictedVisibility list exists and contains at + /// least one entry, indicating that visibility of this message is restricted + /// to specific users. + /// + /// Returns false if the restrictedVisibility list is null or empty, + /// indicating that this message is visible to all users. + bool get hasRestrictedVisibility { + final visibility = restrictedVisibility; + if (visibility == null || visibility.isEmpty) return false; + + return true; + } + + /// Determines if a message is visible to a specific user based on + /// restricted visibility settings. + /// + /// Returns true in the following cases: + /// - The restrictedVisibility list is null or empty (visible to everyone) + /// - The provided userId is found in the restrictedVisibility list + /// + /// Returns false if the restrictedVisibility list exists and doesn't + /// contain the provided userId. + /// + /// [userId] The unique identifier of the user to check visibility for. + bool isVisibleTo(String userId) { + final visibility = restrictedVisibility; + if (visibility == null || visibility.isEmpty) return true; + + return visibility.contains(userId); + } + + /// Determines if a message is not visible to a specific user based on + /// restricted visibility settings. + /// + /// Returns true if the restrictedVisibility list exists and doesn't + /// contain the provided userId. + /// + /// Returns false in the following cases: + /// - The restrictedVisibility list is null or empty (visible to everyone) + /// - The provided userId is found in the restrictedVisibility list + /// + /// [userId] The unique identifier of the user to check visibility for. + bool isNotVisibleTo(String userId) => !isVisibleTo(userId); +} diff --git a/packages/stream_chat/lib/src/core/models/message.g.dart b/packages/stream_chat/lib/src/core/models/message.g.dart index efb7d0dd3..168b5f678 100644 --- a/packages/stream_chat/lib/src/core/models/message.g.dart +++ b/packages/stream_chat/lib/src/core/models/message.g.dart @@ -76,6 +76,9 @@ Message _$MessageFromJson(Map json) => Message( i18n: (json['i18n'] as Map?)?.map( (k, e) => MapEntry(k, e as String), ), + restrictedVisibility: (json['restricted_visibility'] as List?) + ?.map((e) => e as String) + .toList(), ); Map _$MessageToJson(Message instance) => { @@ -91,5 +94,7 @@ Map _$MessageToJson(Message instance) => { 'pinned': instance.pinned, 'pin_expires': instance.pinExpires?.toIso8601String(), 'poll_id': instance.pollId, + if (instance.restrictedVisibility case final value?) + 'restricted_visibility': value, 'extra_data': instance.extraData, }; diff --git a/packages/stream_chat/test/fixtures/channel_state.json b/packages/stream_chat/test/fixtures/channel_state.json index 481465433..528259be0 100644 --- a/packages/stream_chat/test/fixtures/channel_state.json +++ b/packages/stream_chat/test/fixtures/channel_state.json @@ -1,4 +1,3 @@ - { "channel": { "id": "dev", @@ -79,7 +78,10 @@ "pinned_at": null, "pin_expires": null, "pinned_by": null, - "poll_id": null + "poll_id": null, + "restricted_visibility": [ + "user-id-3" + ] }, { "id": "dry-meadow-0-e8e74482-b4cd-48db-9d1e-30e6c191786f", diff --git a/packages/stream_chat/test/fixtures/channel_state_to_json.json b/packages/stream_chat/test/fixtures/channel_state_to_json.json index b305869e4..f15f7320d 100644 --- a/packages/stream_chat/test/fixtures/channel_state_to_json.json +++ b/packages/stream_chat/test/fixtures/channel_state_to_json.json @@ -25,7 +25,10 @@ "silent": false, "pinned": false, "pin_expires": null, - "poll_id": null + "poll_id": null, + "restricted_visibility": [ + "user-id-3" + ] }, { "id": "dry-meadow-0-e8e74482-b4cd-48db-9d1e-30e6c191786f", diff --git a/packages/stream_chat/test/fixtures/event.json b/packages/stream_chat/test/fixtures/event.json index cc567a9b8..4bcb90869 100644 --- a/packages/stream_chat/test/fixtures/event.json +++ b/packages/stream_chat/test/fixtures/event.json @@ -29,5 +29,6 @@ "ai_state": "AI_STATE_THINKING", "ai_message": "Some message", "unread_thread_messages": 2, - "unread_threads": 3 + "unread_threads": 3, + "channel_last_message_at": "2019-03-27T17:40:17.155892Z" } \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/message.json b/packages/stream_chat/test/fixtures/message.json index 97202415c..6b99f2fbc 100644 --- a/packages/stream_chat/test/fixtures/message.json +++ b/packages/stream_chat/test/fixtures/message.json @@ -62,5 +62,8 @@ "reply_count": 0, "created_at": "2020-01-28T22:17:31.107978Z", "updated_at": "2020-01-28T22:17:31.130506Z", - "mentioned_users": [] + "mentioned_users": [], + "restricted_visibility": [ + "user-id-3" + ] } \ No newline at end of file diff --git a/packages/stream_chat/test/fixtures/message_to_json.json b/packages/stream_chat/test/fixtures/message_to_json.json index 3cf86bd7d..c4568628d 100644 --- a/packages/stream_chat/test/fixtures/message_to_json.json +++ b/packages/stream_chat/test/fixtures/message_to_json.json @@ -10,7 +10,6 @@ "title": "The Lion King Disney GIF - Find & Share on GIPHY", "thumb_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", "text": "Discover & share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "og_scrape_url": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA", "image_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif", "author_name": "GIPHY", "asset_url": "https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4", @@ -24,5 +23,8 @@ "pin_expires": null, "poll_id": null, "show_in_channel": true, + "restricted_visibility": [ + "user-id-3" + ], "hey": "test" } \ No newline at end of file diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart index c61b458e2..aefc42c33 100644 --- a/packages/stream_chat/test/src/client/channel_test.dart +++ b/packages/stream_chat/test/src/client/channel_test.dart @@ -1,4 +1,7 @@ +// ignore_for_file: lines_longer_than_80_chars + import 'package:mocktail/mocktail.dart'; +import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/client/retry_policy.dart'; import 'package:stream_chat/src/core/models/banned_user.dart'; import 'package:stream_chat/stream_chat.dart'; @@ -12,6 +15,7 @@ void main() { ChannelState _generateChannelState( String channelId, String channelType, { + DateTime? lastMessageAt, bool mockChannelConfig = false, }) { ChannelConfig? config; @@ -24,6 +28,7 @@ void main() { id: channelId, type: channelType, config: config, + lastMessageAt: lastMessageAt, ); final state = ChannelState(channel: channel); return state; @@ -2851,4 +2856,245 @@ void main() { }); }); }); + + group('WS events', () { + late final client = MockStreamChatClient(); + + setUpAll(() { + // Fallback values + registerFallbackValue(FakeMessage()); + registerFallbackValue(FakeAttachmentFile()); + registerFallbackValue(FakeEvent()); + + // detached loggers + when(() => client.detachedLogger(any())).thenAnswer((invocation) { + final name = invocation.positionalArguments.first; + return _createLogger(name); + }); + + final retryPolicy = RetryPolicy( + shouldRetry: (_, __, ___) => false, + delayFactor: Duration.zero, + ); + when(() => client.retryPolicy).thenReturn(retryPolicy); + + // fake clientState + final clientState = FakeClientState(); + when(() => client.state).thenReturn(clientState); + + // client logger + when(() => client.logger).thenReturn(_createLogger('mock-client-logger')); + }); + + group( + '${EventType.messageNew} or ${EventType.notificationMessageNew}', + () { + final initialLastMessageAt = DateTime.now(); + late PublishSubject eventController; + const channelId = 'test-channel-id'; + const channelType = 'test-channel-type'; + late Channel channel; + + setUp(() { + final localEvent = Event(type: 'event.local'); + when(() => client.on(any(), any(), any(), any())) + .thenAnswer((_) => Stream.value(localEvent)); + + eventController = PublishSubject(); + when(() => client.on( + EventType.messageNew, + EventType.notificationMessageNew, + )).thenAnswer((_) => eventController.stream); + + final channelState = _generateChannelState( + channelId, + channelType, + mockChannelConfig: true, + lastMessageAt: initialLastMessageAt, + ); + + channel = Channel.fromState(client, channelState); + }); + + tearDown(() { + eventController.close(); + channel.dispose(); + }); + + Event createNewMessageEvent(Message message) { + return Event( + cid: channel.cid, + type: EventType.messageNew, + message: message, + ); + } + + test( + "should update 'channel.lastMessageAt'", + () async { + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + + final message = Message( + id: 'test-message-id', + user: client.state.currentUser, + createdAt: initialLastMessageAt.add(const Duration(seconds: 3)), + ); + + final newMessageEvent = createNewMessageEvent(message); + eventController.add(newMessageEvent); + + // Wait for the event to get processed + await Future.delayed(Duration.zero); + + expect(channel.lastMessageAt, equals(message.createdAt)); + expect(channel.lastMessageAt, isNot(initialLastMessageAt)); + }, + ); + + test( + "should update 'channel.lastMessageAt' when Message has restricted visibility only for the current user", + () async { + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + + final message = Message( + id: 'test-message-id', + user: client.state.currentUser, + // Message is visible to the current user. + restrictedVisibility: [client.state.currentUser!.id], + createdAt: initialLastMessageAt.add(const Duration(seconds: 3)), + ); + + final newMessageEvent = createNewMessageEvent(message); + eventController.add(newMessageEvent); + + // Wait for the event to get processed + await Future.delayed(Duration.zero); + + expect(channel.lastMessageAt, equals(message.createdAt)); + expect(channel.lastMessageAt, isNot(initialLastMessageAt)); + }, + ); + + test( + "should not update 'channel.lastMessageAt' when 'message.createdAt' is older", + () async { + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + + final message = Message( + id: 'test-message-id', + user: client.state.currentUser, + // Older than the current 'channel.lastMessageAt'. + createdAt: initialLastMessageAt.subtract(const Duration(days: 1)), + ); + + final newMessageEvent = createNewMessageEvent(message); + eventController.add(newMessageEvent); + + // Wait for the event to get processed + await Future.delayed(Duration.zero); + + expect(channel.lastMessageAt, isNot(message.createdAt)); + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + }, + ); + + test( + "should not update 'channel.lastMessageAt' when Message is shadowed", + () async { + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + + final message = Message( + id: 'test-message-id', + user: client.state.currentUser, + shadowed: true, + createdAt: initialLastMessageAt.add(const Duration(seconds: 3)), + ); + + final newMessageEvent = createNewMessageEvent(message); + eventController.add(newMessageEvent); + + // Wait for the event to get processed + await Future.delayed(Duration.zero); + + expect(channel.lastMessageAt, isNot(message.createdAt)); + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + }, + ); + + test( + "should not update 'channel.lastMessageAt' when Message is ephemeral", + () async { + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + + final message = Message( + type: 'ephemeral', + id: 'test-message-id', + user: client.state.currentUser, + createdAt: initialLastMessageAt.add(const Duration(seconds: 3)), + ); + + final newMessageEvent = createNewMessageEvent(message); + eventController.add(newMessageEvent); + + // Wait for the event to get processed + await Future.delayed(Duration.zero); + + expect(channel.lastMessageAt, isNot(message.createdAt)); + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + }, + ); + + test( + "should not update 'channel.lastMessageAt' when Message has restricted visibility but not for the current user", + () async { + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + + final message = Message( + id: 'test-message-id', + user: client.state.currentUser, + // Message is only visible to user-1 not the current user. + restrictedVisibility: const ['user-1'], + createdAt: initialLastMessageAt.add(const Duration(seconds: 3)), + ); + + final newMessageEvent = createNewMessageEvent(message); + eventController.add(newMessageEvent); + + // Wait for the event to get processed + await Future.delayed(Duration.zero); + + expect(channel.lastMessageAt, isNot(message.createdAt)); + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + }, + ); + + test( + "should not update 'channel.lastMessageAt' when Message is system and skip is enabled", + () async { + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + + when( + () => channel.config?.skipLastMsgUpdateForSystemMsgs, + ).thenReturn(true); + + final message = Message( + type: 'system', + id: 'test-message-id', + user: client.state.currentUser, + createdAt: initialLastMessageAt.add(const Duration(seconds: 3)), + ); + + final newMessageEvent = createNewMessageEvent(message); + eventController.add(newMessageEvent); + + // Wait for the event to get processed + await Future.delayed(Duration.zero); + + expect(channel.lastMessageAt, isNot(message.createdAt)); + expect(channel.lastMessageAt, equals(initialLastMessageAt)); + }, + ); + }, + ); + }); } diff --git a/packages/stream_chat/test/src/core/models/channel_state_test.dart b/packages/stream_chat/test/src/core/models/channel_state_test.dart index 269225c9d..6441ef465 100644 --- a/packages/stream_chat/test/src/core/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/core/models/channel_state_test.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:stream_chat/stream_chat.dart'; import 'package:test/test.dart'; @@ -42,6 +40,10 @@ void main() { DateTime.parse('2020-01-29T03:23:02.843948Z'), ); expect(channelState.messages![0].user, isA()); + expect( + channelState.messages![0].restrictedVisibility, + isA>(), + ); expect(channelState.watcherCount, 5); }); @@ -59,8 +61,6 @@ void main() { watchers: [], ); - print(jsonEncode(channelState.messages?.first)); - expect( channelState.toJson(), jsonFixture('channel_state_to_json.json'), diff --git a/packages/stream_chat/test/src/core/models/channel_test.dart b/packages/stream_chat/test/src/core/models/channel_test.dart index e67c185e9..04925fdd8 100644 --- a/packages/stream_chat/test/src/core/models/channel_test.dart +++ b/packages/stream_chat/test/src/core/models/channel_test.dart @@ -61,7 +61,6 @@ void main() { expect(channel.hidden, false); expect(channel.extraData['hidden'], false); - print(channel.toJson()); expect(channel.toJson(), { 'id': 'cid', 'type': 'test', @@ -105,7 +104,6 @@ void main() { expect(channel.disabled, false); expect(channel.extraData['disabled'], false); - print(channel.toJson()); expect(channel.toJson(), { 'id': 'cid', 'type': 'test', @@ -150,7 +148,6 @@ void main() { expect(channel.truncatedAt, currentDate); expect(channel.extraData['truncated_at'], currentDate.toIso8601String()); - print(channel.toJson()); expect(channel.toJson(), { 'id': 'cid', 'type': 'test', diff --git a/packages/stream_chat/test/src/core/models/event_test.dart b/packages/stream_chat/test/src/core/models/event_test.dart index 2293bbad9..8374df3af 100644 --- a/packages/stream_chat/test/src/core/models/event_test.dart +++ b/packages/stream_chat/test/src/core/models/event_test.dart @@ -18,6 +18,7 @@ void main() { expect(event.aiMessage, 'Some message'); expect(event.unreadThreadMessages, 2); expect(event.unreadThreads, 3); + expect(event.channelLastMessageAt, isA()); }); test('should serialize to json correctly', () { @@ -36,6 +37,7 @@ void main() { messageId: 'messageId', unreadThreadMessages: 2, unreadThreads: 3, + channelLastMessageAt: DateTime.parse('2019-03-27T17:40:17.155892Z'), ); expect( @@ -66,6 +68,7 @@ void main() { 'thread': null, 'unread_thread_messages': 2, 'unread_threads': 3, + 'channel_last_message_at': '2019-03-27T17:40:17.155892Z', }, ); }); @@ -82,6 +85,7 @@ void main() { expect(newEvent.isLocal, false); expect(newEvent.unreadThreadMessages, 2); expect(newEvent.unreadThreads, 3); + expect(newEvent.channelLastMessageAt, isA()); newEvent = event.copyWith( type: 'test', @@ -94,6 +98,7 @@ void main() { channelType: 'testtype', unreadThreadMessages: 6, unreadThreads: 7, + channelLastMessageAt: DateTime.parse('2020-01-29T03:22:47.636130Z'), ); expect(newEvent.channelType, 'testtype'); @@ -106,6 +111,10 @@ void main() { expect(newEvent.user!.id, 'test'); expect(newEvent.unreadThreadMessages, 6); expect(newEvent.unreadThreads, 7); + expect( + newEvent.channelLastMessageAt, + DateTime.parse('2020-01-29T03:22:47.636130Z'), + ); }); }); } diff --git a/packages/stream_chat/test/src/core/models/message_test.dart b/packages/stream_chat/test/src/core/models/message_test.dart index c627a739d..c55dd4fd7 100644 --- a/packages/stream_chat/test/src/core/models/message_test.dart +++ b/packages/stream_chat/test/src/core/models/message_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_redundant_argument_values + import 'package:stream_chat/src/core/models/attachment.dart'; import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/src/core/models/reaction.dart'; @@ -29,6 +31,7 @@ void main() { expect(message.pinExpires, null); expect(message.pinnedBy, null); expect(message.i18n, null); + expect(message.restrictedVisibility, isA>()); }); test('should serialize to json correctly', () { @@ -51,12 +54,11 @@ void main() { 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', 'asset_url': 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4', - 'og_scrape_url': - 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA' }) ], showInChannel: true, parentId: 'parentId', + restrictedVisibility: const ['user-id-3'], extraData: const {'hey': 'test'}, ); @@ -66,4 +68,86 @@ void main() { ); }); }); + + group('MessageVisibility Extension Tests', () { + group('hasRestrictedVisibility', () { + test('should return false when restrictedVisibility is null', () { + final message = Message(restrictedVisibility: null); + expect(message.hasRestrictedVisibility, false); + }); + + test('should return false when restrictedVisibility is empty', () { + final message = Message(restrictedVisibility: const []); + expect(message.hasRestrictedVisibility, false); + }); + + test('should return true when restrictedVisibility has entries', () { + final message = Message(restrictedVisibility: const ['user1', 'user2']); + expect(message.hasRestrictedVisibility, true); + }); + }); + + group('isVisibleTo', () { + test('should return true when restrictedVisibility is null', () { + final message = Message(restrictedVisibility: null); + expect(message.isVisibleTo('anyUser'), true); + }); + + test('should return true when restrictedVisibility is empty', () { + final message = Message(restrictedVisibility: const []); + expect(message.isVisibleTo('anyUser'), true); + }); + + test('should return true when user is in restrictedVisibility list', () { + final message = + Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + expect(message.isVisibleTo('user2'), true); + }); + + test('should return false when user is not in restrictedVisibility list', + () { + final message = + Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + expect(message.isVisibleTo('user4'), false); + }); + + test('should handle case sensitivity correctly', () { + final message = Message(restrictedVisibility: const ['User1', 'USER2']); + expect(message.isVisibleTo('user1'), false, + reason: 'Should be case sensitive'); + expect(message.isVisibleTo('User1'), true); + }); + }); + + group('isNotVisibleTo', () { + test('should return false when restrictedVisibility is null', () { + final message = Message(restrictedVisibility: null); + expect(message.isNotVisibleTo('anyUser'), false); + }); + + test('should return false when restrictedVisibility is empty', () { + final message = Message(restrictedVisibility: const []); + expect(message.isNotVisibleTo('anyUser'), false); + }); + + test('should return false when user is in restrictedVisibility list', () { + final message = + Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + expect(message.isNotVisibleTo('user2'), false); + }); + + test('should return true when user is not in restrictedVisibility list', + () { + final message = + Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + expect(message.isNotVisibleTo('user4'), true); + }); + + test('should be the exact opposite of isVisibleTo', () { + final message = Message(restrictedVisibility: const ['user1', 'user2']); + const userId = 'testUser'; + expect(message.isNotVisibleTo(userId), !message.isVisibleTo(userId)); + }); + }); + }); } diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 1dfc7c925..12a88c94c 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,14 @@ +## Upcoming + +ЁЯРЮ Fixed + +- Fixed `StreamMessageInput` not able to edit the ogAttachments. +- Fixed `MessageWidget` showing pinned background for deleted messages. + +ЁЯФД Changed + +- Updated the message list view to prevent pinning messages that have restricted visibility. + ## 9.4.0 ЁЯФД Changed diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 01d7ecfe2..10921a3b7 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -480,16 +480,18 @@ class StreamMessageInputState extends State assert(_controller != null, ''); registerForRestoration(_controller!, 'messageInputController'); - _effectiveController - ..removeListener(_onChangedDebounced) - ..addListener(_onChangedDebounced); - if (!_isEditing && _timeOut <= 0) _startSlowMode(); + _initialiseEffectiveController(); } void _initialiseEffectiveController() { _effectiveController ..removeListener(_onChangedDebounced) ..addListener(_onChangedDebounced); + + // Call the listener once to make sure the initial state is reflected + // correctly in the UI. + _onChangedDebounced.call(); + if (!_isEditing && _timeOut <= 0) _startSlowMode(); } diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index 9f86679fa..8c788880e 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -1095,7 +1095,12 @@ class _StreamMessageListViewState extends State { FocusScope.of(context).unfocus(); }, showPinButton: currentUserMember != null && - _userPermissions.contains(PermissionType.pinMessage), + _userPermissions.contains(PermissionType.pinMessage) && + // Pinning a restricted visibility message is not allowed, simply + // because pinning a message is meant to bring attention to that + // message, that is not possible with a message that is only visible + // to a subset of users. + !message.hasRestrictedVisibility, ); if (widget.parentMessageBuilder != null) { @@ -1453,7 +1458,12 @@ class _StreamMessageListViewState extends State { FocusScope.of(context).unfocus(); }, showPinButton: currentUserMember != null && - _userPermissions.contains(PermissionType.pinMessage), + _userPermissions.contains(PermissionType.pinMessage) && + // Pinning a restricted visibility message is not allowed, simply + // because pinning a message is meant to bring attention to that + // message, that is not possible with a message that is only visible + // to a subset of users. + !message.hasRestrictedVisibility, ); if (widget.messageBuilder != null) { diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 44c003a06..5260c51f2 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -605,7 +605,7 @@ class _StreamMessageWidgetState extends State /// {@template isPinned} /// Whether [StreamMessageWidget.message] is pinned or not. /// {@endtemplate} - bool get isPinned => widget.message.pinned; + bool get isPinned => widget.message.pinned && !widget.message.isDeleted; /// {@template shouldShowReactions} /// Should show message reactions if [StreamMessageWidget.showReactions] is @@ -680,7 +680,7 @@ class _StreamMessageWidgetState extends State type: MaterialType.transparency, child: AnimatedContainer( duration: const Duration(seconds: 1), - color: widget.message.pinned && widget.showPinHighlight + color: isPinned && widget.showPinHighlight ? _streamChatTheme.colorTheme.highlight // ignore: deprecated_member_use : _streamChatTheme.colorTheme.barsBg.withOpacity(0), @@ -891,13 +891,13 @@ class _StreamMessageWidgetState extends State ), title: Text( context.translations.togglePinUnpinText( - pinned: widget.message.pinned, + pinned: isPinned, ), ), onClick: () async { Navigator.of(context, rootNavigator: true).pop(); try { - if (!widget.message.pinned) { + if (!isPinned) { await channel.pinMessage(widget.message); } else { await channel.unpinMessage(widget.message); diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index 963b33f87..a31a6f602 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -259,9 +259,7 @@ class MessageWidgetContent extends StatelessWidget { reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - if (message.pinned && - message.pinnedBy != null && - showPinHighlight) + if (isPinned && message.pinnedBy != null && showPinHighlight) PinnedMessage( pinnedBy: message.pinnedBy!, currentUser: streamChat.currentUser!, diff --git a/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart index 755686c71..d947bd2ed 100644 --- a/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart @@ -38,6 +38,7 @@ void main() { extraData: const { 'mime_type': 'gif', }, + uploadState: const UploadState.success(), ), ), ), diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png index 035e74551..3983228cc 100644 Binary files a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png and b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png differ diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index 0f30c7d00..c13e7929a 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -15,7 +15,10 @@ class MockClientState extends Mock implements ClientState {} class MockChannel extends Mock implements Channel { MockChannel({ - this.ownCapabilities = const ['send-message'], + this.ownCapabilities = const [ + PermissionType.sendMessage, + PermissionType.uploadFile, + ], }); @override diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index e8fa547b1..6b09d32c4 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,10 @@ +## Upcoming + +ЁЯФД Changed + +- Simplified the logic for setting and clearing OG attachments by removing the `_ogAttachment` field + and directly working with the attachments list. + ## 9.4.0 - Updated minimum Flutter version to 3.27.4 for the SDK. @@ -42,7 +49,7 @@ - Changed minimum Flutter version to 3.22 for the SDK. - Updated `stream_chat` dependency to [`8.1.0`](https://pub.dev/packages/stream_chat/changelog). -## 8.0.0 +## 8.0.0 ЁЯРЮ Fixed @@ -56,7 +63,7 @@ ЁЯФД Changed -- Changed minimum Flutter version to 3.19 for the SDK. +- Changed minimum Flutter version to 3.19 for the SDK. - Updated `stream_chat` dependency to [`7.3.0`](https://pub.dev/packages/stream_chat/changelog). ## 7.2.2 @@ -65,12 +72,12 @@ ## 7.2.1 - - Updated `stream_chat` dependency to [`7.2.1`](https://pub.dev/packages/stream_chat/changelog). +- Updated `stream_chat` dependency to [`7.2.1`](https://pub.dev/packages/stream_chat/changelog). ## 7.2.0-hotfix.1 - - Updated `stream_chat` dependency to [`7.2.0-hotfix.1`](https://pub.dev/packages/stream_chat/changelog). - - Reverted the `connectivity_plus` dependency bump causing [1889](https://github.com/GetStream/stream-chat-flutter/issues/1889) +- Updated `stream_chat` dependency to [`7.2.0-hotfix.1`](https://pub.dev/packages/stream_chat/changelog). +- Reverted the `connectivity_plus` dependency bump causing [1889](https://github.com/GetStream/stream-chat-flutter/issues/1889) ## 7.2.0 @@ -99,7 +106,7 @@ ЁЯРЮ Fixed -- Fixed video attachment uploading. [#1754](https://github.com/GetStream/stream-chat-flutter/pull/1754) +- [[#1754]](https://github.com/GetStream/stream-chat-flutter/pull/1754) Fixed video attachment uploading. ## 6.10.0 diff --git a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart index 874311a4a..497d9cbab 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart @@ -199,28 +199,31 @@ class StreamMessageInputController extends ValueNotifier { attachments = []; } - // Only used to store the value locally in order to remove it if we call - // [clearOGAttachment] or [setOGAttachment] again. - Attachment? _ogAttachment; - /// Returns the og attachment of the message if set - Attachment? get ogAttachment => - attachments.firstWhereOrNull((it) => it.id == _ogAttachment?.id); + Attachment? get ogAttachment { + return attachments.firstWhereOrNull((it) => it.ogScrapeUrl != null); + } /// Sets the og attachment in the message. void setOGAttachment(Attachment attachment) { - attachments = [...attachments] - ..remove(_ogAttachment) - ..insert(0, attachment); - _ogAttachment = attachment; + final updatedAttachments = [...attachments]; + // Remove the existing og attachment if it exists. + if (ogAttachment case final existingOGAttachment?) { + updatedAttachments.remove(existingOGAttachment); + } + + // Add the new og attachment at the beginning of the list. + updatedAttachments.insert(0, attachment); + + // Update the attachments list. + attachments = updatedAttachments; } /// Removes the og attachment. void clearOGAttachment() { - if (_ogAttachment != null) { - removeAttachment(_ogAttachment!); + if (ogAttachment case final existingOGAttachment?) { + removeAttachment(existingOGAttachment); } - _ogAttachment = null; } /// Returns the poll in the message. diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index f1a685f12..e47a60362 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,8 +1,7 @@ ## Upcoming -тЬЕ Added - -- Add member.extraData field +- Added support for `Message.restrictedVisibility` field. +- Added support for `Member.extraData` field. ## 9.4.0 diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart index 5acd58f15..4ad14290a 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart @@ -53,7 +53,7 @@ class DriftChatDatabase extends _$DriftChatDatabase { // you should bump this number whenever you change or add a table definition. @override - int get schemaVersion => 16; + int get schemaVersion => 17; @override MigrationStrategy get migration => MigrationStrategy( diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart index 2c03dcd15..96e4b701b 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart @@ -863,6 +863,15 @@ class $MessagesTable extends Messages i18n = GeneratedColumn('i18n', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false) .withConverter?>($MessagesTable.$converteri18n); + static const VerificationMeta _restrictedVisibilityMeta = + const VerificationMeta('restrictedVisibility'); + @override + late final GeneratedColumnWithTypeConverter?, String> + restrictedVisibility = GeneratedColumn( + 'restricted_visibility', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter?>( + $MessagesTable.$converterrestrictedVisibilityn); static const VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); @override @@ -902,6 +911,7 @@ class $MessagesTable extends Messages pinnedByUserId, channelCid, i18n, + restrictedVisibility, extraData ]; @override @@ -1048,6 +1058,8 @@ class $MessagesTable extends Messages context.missing(_channelCidMeta); } context.handle(_i18nMeta, const VerificationResult.success()); + context.handle( + _restrictedVisibilityMeta, const VerificationResult.success()); context.handle(_extraDataMeta, const VerificationResult.success()); return context; } @@ -1121,6 +1133,9 @@ class $MessagesTable extends Messages .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, i18n: $MessagesTable.$converteri18n.fromSql(attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}i18n'])), + restrictedVisibility: $MessagesTable.$converterrestrictedVisibilityn + .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, + data['${effectivePrefix}restricted_visibility'])), extraData: $MessagesTable.$converterextraDatan.fromSql(attachedDatabase .typeMapping .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), @@ -1146,6 +1161,10 @@ class $MessagesTable extends Messages NullAwareTypeConverter.wrap($converterreactionScores); static TypeConverter?, String?> $converteri18n = NullableMapConverter(); + static TypeConverter, String> $converterrestrictedVisibility = + ListConverter(); + static TypeConverter?, String?> $converterrestrictedVisibilityn = + NullAwareTypeConverter.wrap($converterrestrictedVisibility); static TypeConverter, String> $converterextraData = MapConverter(); static TypeConverter?, String?> $converterextraDatan = @@ -1241,6 +1260,9 @@ class MessageEntity extends DataClass implements Insertable { /// A Map of [messageText] translations. final Map? i18n; + /// The list of user ids that should be able to see the message. + final List? restrictedVisibility; + /// Message custom extraData final Map? extraData; const MessageEntity( @@ -1273,6 +1295,7 @@ class MessageEntity extends DataClass implements Insertable { this.pinnedByUserId, required this.channelCid, this.i18n, + this.restrictedVisibility, this.extraData}); @override Map toColumns(bool nullToAbsent) { @@ -1356,6 +1379,11 @@ class MessageEntity extends DataClass implements Insertable { if (!nullToAbsent || i18n != null) { map['i18n'] = Variable($MessagesTable.$converteri18n.toSql(i18n)); } + if (!nullToAbsent || restrictedVisibility != null) { + map['restricted_visibility'] = Variable($MessagesTable + .$converterrestrictedVisibilityn + .toSql(restrictedVisibility)); + } if (!nullToAbsent || extraData != null) { map['extra_data'] = Variable( $MessagesTable.$converterextraDatan.toSql(extraData)); @@ -1399,6 +1427,8 @@ class MessageEntity extends DataClass implements Insertable { pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), i18n: serializer.fromJson?>(json['i18n']), + restrictedVisibility: + serializer.fromJson?>(json['restrictedVisibility']), extraData: serializer.fromJson?>(json['extraData']), ); } @@ -1436,6 +1466,8 @@ class MessageEntity extends DataClass implements Insertable { 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), 'i18n': serializer.toJson?>(i18n), + 'restrictedVisibility': + serializer.toJson?>(restrictedVisibility), 'extraData': serializer.toJson?>(extraData), }; } @@ -1470,6 +1502,7 @@ class MessageEntity extends DataClass implements Insertable { Value pinnedByUserId = const Value.absent(), String? channelCid, Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), Value?> extraData = const Value.absent()}) => MessageEntity( id: id ?? this.id, @@ -1518,6 +1551,9 @@ class MessageEntity extends DataClass implements Insertable { pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, channelCid: channelCid ?? this.channelCid, i18n: i18n.present ? i18n.value : this.i18n, + restrictedVisibility: restrictedVisibility.present + ? restrictedVisibility.value + : this.restrictedVisibility, extraData: extraData.present ? extraData.value : this.extraData, ); MessageEntity copyWithCompanion(MessagesCompanion data) { @@ -1582,6 +1618,9 @@ class MessageEntity extends DataClass implements Insertable { channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, i18n: data.i18n.present ? data.i18n.value : this.i18n, + restrictedVisibility: data.restrictedVisibility.present + ? data.restrictedVisibility.value + : this.restrictedVisibility, extraData: data.extraData.present ? data.extraData.value : this.extraData, ); } @@ -1618,6 +1657,7 @@ class MessageEntity extends DataClass implements Insertable { ..write('pinnedByUserId: $pinnedByUserId, ') ..write('channelCid: $channelCid, ') ..write('i18n: $i18n, ') + ..write('restrictedVisibility: $restrictedVisibility, ') ..write('extraData: $extraData') ..write(')')) .toString(); @@ -1654,6 +1694,7 @@ class MessageEntity extends DataClass implements Insertable { pinnedByUserId, channelCid, i18n, + restrictedVisibility, extraData ]); @override @@ -1689,6 +1730,7 @@ class MessageEntity extends DataClass implements Insertable { other.pinnedByUserId == this.pinnedByUserId && other.channelCid == this.channelCid && other.i18n == this.i18n && + other.restrictedVisibility == this.restrictedVisibility && other.extraData == this.extraData); } @@ -1722,6 +1764,7 @@ class MessagesCompanion extends UpdateCompanion { final Value pinnedByUserId; final Value channelCid; final Value?> i18n; + final Value?> restrictedVisibility; final Value?> extraData; final Value rowid; const MessagesCompanion({ @@ -1754,6 +1797,7 @@ class MessagesCompanion extends UpdateCompanion { this.pinnedByUserId = const Value.absent(), this.channelCid = const Value.absent(), this.i18n = const Value.absent(), + this.restrictedVisibility = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), }); @@ -1787,6 +1831,7 @@ class MessagesCompanion extends UpdateCompanion { this.pinnedByUserId = const Value.absent(), required String channelCid, this.i18n = const Value.absent(), + this.restrictedVisibility = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), }) : id = Value(id), @@ -1824,6 +1869,7 @@ class MessagesCompanion extends UpdateCompanion { Expression? pinnedByUserId, Expression? channelCid, Expression? i18n, + Expression? restrictedVisibility, Expression? extraData, Expression? rowid, }) { @@ -1858,6 +1904,8 @@ class MessagesCompanion extends UpdateCompanion { if (pinnedByUserId != null) 'pinned_by_user_id': pinnedByUserId, if (channelCid != null) 'channel_cid': channelCid, if (i18n != null) 'i18n': i18n, + if (restrictedVisibility != null) + 'restricted_visibility': restrictedVisibility, if (extraData != null) 'extra_data': extraData, if (rowid != null) 'rowid': rowid, }); @@ -1893,6 +1941,7 @@ class MessagesCompanion extends UpdateCompanion { Value? pinnedByUserId, Value? channelCid, Value?>? i18n, + Value?>? restrictedVisibility, Value?>? extraData, Value? rowid}) { return MessagesCompanion( @@ -1925,6 +1974,7 @@ class MessagesCompanion extends UpdateCompanion { pinnedByUserId: pinnedByUserId ?? this.pinnedByUserId, channelCid: channelCid ?? this.channelCid, i18n: i18n ?? this.i18n, + restrictedVisibility: restrictedVisibility ?? this.restrictedVisibility, extraData: extraData ?? this.extraData, rowid: rowid ?? this.rowid, ); @@ -2026,6 +2076,11 @@ class MessagesCompanion extends UpdateCompanion { map['i18n'] = Variable($MessagesTable.$converteri18n.toSql(i18n.value)); } + if (restrictedVisibility.present) { + map['restricted_visibility'] = Variable($MessagesTable + .$converterrestrictedVisibilityn + .toSql(restrictedVisibility.value)); + } if (extraData.present) { map['extra_data'] = Variable( $MessagesTable.$converterextraDatan.toSql(extraData.value)); @@ -2068,6 +2123,7 @@ class MessagesCompanion extends UpdateCompanion { ..write('pinnedByUserId: $pinnedByUserId, ') ..write('channelCid: $channelCid, ') ..write('i18n: $i18n, ') + ..write('restrictedVisibility: $restrictedVisibility, ') ..write('extraData: $extraData, ') ..write('rowid: $rowid') ..write(')')) @@ -2274,6 +2330,15 @@ class $PinnedMessagesTable extends PinnedMessages type: DriftSqlType.string, requiredDuringInsert: false) .withConverter?>( $PinnedMessagesTable.$converteri18n); + static const VerificationMeta _restrictedVisibilityMeta = + const VerificationMeta('restrictedVisibility'); + @override + late final GeneratedColumnWithTypeConverter?, String> + restrictedVisibility = GeneratedColumn( + 'restricted_visibility', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter?>( + $PinnedMessagesTable.$converterrestrictedVisibilityn); static const VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); @override @@ -2313,6 +2378,7 @@ class $PinnedMessagesTable extends PinnedMessages pinnedByUserId, channelCid, i18n, + restrictedVisibility, extraData ]; @override @@ -2460,6 +2526,8 @@ class $PinnedMessagesTable extends PinnedMessages context.missing(_channelCidMeta); } context.handle(_i18nMeta, const VerificationResult.success()); + context.handle( + _restrictedVisibilityMeta, const VerificationResult.success()); context.handle(_extraDataMeta, const VerificationResult.success()); return context; } @@ -2534,6 +2602,9 @@ class $PinnedMessagesTable extends PinnedMessages i18n: $PinnedMessagesTable.$converteri18n.fromSql(attachedDatabase .typeMapping .read(DriftSqlType.string, data['${effectivePrefix}i18n'])), + restrictedVisibility: $PinnedMessagesTable.$converterrestrictedVisibilityn + .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, + data['${effectivePrefix}restricted_visibility'])), extraData: $PinnedMessagesTable.$converterextraDatan.fromSql( attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), @@ -2559,6 +2630,10 @@ class $PinnedMessagesTable extends PinnedMessages NullAwareTypeConverter.wrap($converterreactionScores); static TypeConverter?, String?> $converteri18n = NullableMapConverter(); + static TypeConverter, String> $converterrestrictedVisibility = + ListConverter(); + static TypeConverter?, String?> $converterrestrictedVisibilityn = + NullAwareTypeConverter.wrap($converterrestrictedVisibility); static TypeConverter, String> $converterextraData = MapConverter(); static TypeConverter?, String?> $converterextraDatan = @@ -2655,6 +2730,9 @@ class PinnedMessageEntity extends DataClass /// A Map of [messageText] translations. final Map? i18n; + /// The list of user ids that should be able to see the message. + final List? restrictedVisibility; + /// Message custom extraData final Map? extraData; const PinnedMessageEntity( @@ -2687,6 +2765,7 @@ class PinnedMessageEntity extends DataClass this.pinnedByUserId, required this.channelCid, this.i18n, + this.restrictedVisibility, this.extraData}); @override Map toColumns(bool nullToAbsent) { @@ -2771,6 +2850,11 @@ class PinnedMessageEntity extends DataClass map['i18n'] = Variable($PinnedMessagesTable.$converteri18n.toSql(i18n)); } + if (!nullToAbsent || restrictedVisibility != null) { + map['restricted_visibility'] = Variable($PinnedMessagesTable + .$converterrestrictedVisibilityn + .toSql(restrictedVisibility)); + } if (!nullToAbsent || extraData != null) { map['extra_data'] = Variable( $PinnedMessagesTable.$converterextraDatan.toSql(extraData)); @@ -2814,6 +2898,8 @@ class PinnedMessageEntity extends DataClass pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), i18n: serializer.fromJson?>(json['i18n']), + restrictedVisibility: + serializer.fromJson?>(json['restrictedVisibility']), extraData: serializer.fromJson?>(json['extraData']), ); } @@ -2851,6 +2937,8 @@ class PinnedMessageEntity extends DataClass 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), 'i18n': serializer.toJson?>(i18n), + 'restrictedVisibility': + serializer.toJson?>(restrictedVisibility), 'extraData': serializer.toJson?>(extraData), }; } @@ -2885,6 +2973,7 @@ class PinnedMessageEntity extends DataClass Value pinnedByUserId = const Value.absent(), String? channelCid, Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), Value?> extraData = const Value.absent()}) => PinnedMessageEntity( id: id ?? this.id, @@ -2933,6 +3022,9 @@ class PinnedMessageEntity extends DataClass pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, channelCid: channelCid ?? this.channelCid, i18n: i18n.present ? i18n.value : this.i18n, + restrictedVisibility: restrictedVisibility.present + ? restrictedVisibility.value + : this.restrictedVisibility, extraData: extraData.present ? extraData.value : this.extraData, ); PinnedMessageEntity copyWithCompanion(PinnedMessagesCompanion data) { @@ -2997,6 +3089,9 @@ class PinnedMessageEntity extends DataClass channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, i18n: data.i18n.present ? data.i18n.value : this.i18n, + restrictedVisibility: data.restrictedVisibility.present + ? data.restrictedVisibility.value + : this.restrictedVisibility, extraData: data.extraData.present ? data.extraData.value : this.extraData, ); } @@ -3033,6 +3128,7 @@ class PinnedMessageEntity extends DataClass ..write('pinnedByUserId: $pinnedByUserId, ') ..write('channelCid: $channelCid, ') ..write('i18n: $i18n, ') + ..write('restrictedVisibility: $restrictedVisibility, ') ..write('extraData: $extraData') ..write(')')) .toString(); @@ -3069,6 +3165,7 @@ class PinnedMessageEntity extends DataClass pinnedByUserId, channelCid, i18n, + restrictedVisibility, extraData ]); @override @@ -3104,6 +3201,7 @@ class PinnedMessageEntity extends DataClass other.pinnedByUserId == this.pinnedByUserId && other.channelCid == this.channelCid && other.i18n == this.i18n && + other.restrictedVisibility == this.restrictedVisibility && other.extraData == this.extraData); } @@ -3137,6 +3235,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { final Value pinnedByUserId; final Value channelCid; final Value?> i18n; + final Value?> restrictedVisibility; final Value?> extraData; final Value rowid; const PinnedMessagesCompanion({ @@ -3169,6 +3268,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.pinnedByUserId = const Value.absent(), this.channelCid = const Value.absent(), this.i18n = const Value.absent(), + this.restrictedVisibility = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), }); @@ -3202,6 +3302,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.pinnedByUserId = const Value.absent(), required String channelCid, this.i18n = const Value.absent(), + this.restrictedVisibility = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), }) : id = Value(id), @@ -3239,6 +3340,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { Expression? pinnedByUserId, Expression? channelCid, Expression? i18n, + Expression? restrictedVisibility, Expression? extraData, Expression? rowid, }) { @@ -3273,6 +3375,8 @@ class PinnedMessagesCompanion extends UpdateCompanion { if (pinnedByUserId != null) 'pinned_by_user_id': pinnedByUserId, if (channelCid != null) 'channel_cid': channelCid, if (i18n != null) 'i18n': i18n, + if (restrictedVisibility != null) + 'restricted_visibility': restrictedVisibility, if (extraData != null) 'extra_data': extraData, if (rowid != null) 'rowid': rowid, }); @@ -3308,6 +3412,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { Value? pinnedByUserId, Value? channelCid, Value?>? i18n, + Value?>? restrictedVisibility, Value?>? extraData, Value? rowid}) { return PinnedMessagesCompanion( @@ -3340,6 +3445,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { pinnedByUserId: pinnedByUserId ?? this.pinnedByUserId, channelCid: channelCid ?? this.channelCid, i18n: i18n ?? this.i18n, + restrictedVisibility: restrictedVisibility ?? this.restrictedVisibility, extraData: extraData ?? this.extraData, rowid: rowid ?? this.rowid, ); @@ -3444,6 +3550,11 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['i18n'] = Variable( $PinnedMessagesTable.$converteri18n.toSql(i18n.value)); } + if (restrictedVisibility.present) { + map['restricted_visibility'] = Variable($PinnedMessagesTable + .$converterrestrictedVisibilityn + .toSql(restrictedVisibility.value)); + } if (extraData.present) { map['extra_data'] = Variable( $PinnedMessagesTable.$converterextraDatan.toSql(extraData.value)); @@ -3486,6 +3597,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { ..write('pinnedByUserId: $pinnedByUserId, ') ..write('channelCid: $channelCid, ') ..write('i18n: $i18n, ') + ..write('restrictedVisibility: $restrictedVisibility, ') ..write('extraData: $extraData, ') ..write('rowid: $rowid') ..write(')')) @@ -8137,6 +8249,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({ Value pinnedByUserId, required String channelCid, Value?> i18n, + Value?> restrictedVisibility, Value?> extraData, Value rowid, }); @@ -8170,6 +8283,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({ Value pinnedByUserId, Value channelCid, Value?> i18n, + Value?> restrictedVisibility, Value?> extraData, Value rowid, }); @@ -8322,6 +8436,11 @@ class $$MessagesTableFilterComposer column: $table.i18n, builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> + get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, + builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => $composableBuilder( @@ -8476,6 +8595,10 @@ class $$MessagesTableOrderingComposer ColumnOrderings get i18n => $composableBuilder( column: $table.i18n, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, + builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => $composableBuilder( column: $table.extraData, builder: (column) => ColumnOrderings(column)); @@ -8597,6 +8720,10 @@ class $$MessagesTableAnnotationComposer GeneratedColumnWithTypeConverter?, String> get i18n => $composableBuilder(column: $table.i18n, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> + get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => $composableBuilder( column: $table.extraData, builder: (column) => column); @@ -8695,6 +8822,7 @@ class $$MessagesTableTableManager extends RootTableManager< Value pinnedByUserId = const Value.absent(), Value channelCid = const Value.absent(), Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), Value?> extraData = const Value.absent(), Value rowid = const Value.absent(), }) => @@ -8728,6 +8856,7 @@ class $$MessagesTableTableManager extends RootTableManager< pinnedByUserId: pinnedByUserId, channelCid: channelCid, i18n: i18n, + restrictedVisibility: restrictedVisibility, extraData: extraData, rowid: rowid, ), @@ -8761,6 +8890,7 @@ class $$MessagesTableTableManager extends RootTableManager< Value pinnedByUserId = const Value.absent(), required String channelCid, Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), Value?> extraData = const Value.absent(), Value rowid = const Value.absent(), }) => @@ -8794,6 +8924,7 @@ class $$MessagesTableTableManager extends RootTableManager< pinnedByUserId: pinnedByUserId, channelCid: channelCid, i18n: i18n, + restrictedVisibility: restrictedVisibility, extraData: extraData, rowid: rowid, ), @@ -8895,6 +9026,7 @@ typedef $$PinnedMessagesTableCreateCompanionBuilder = PinnedMessagesCompanion Value pinnedByUserId, required String channelCid, Value?> i18n, + Value?> restrictedVisibility, Value?> extraData, Value rowid, }); @@ -8929,6 +9061,7 @@ typedef $$PinnedMessagesTableUpdateCompanionBuilder = PinnedMessagesCompanion Value pinnedByUserId, Value channelCid, Value?> i18n, + Value?> restrictedVisibility, Value?> extraData, Value rowid, }); @@ -9074,6 +9207,11 @@ class $$PinnedMessagesTableFilterComposer column: $table.i18n, builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> + get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, + builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => $composableBuilder( @@ -9213,6 +9351,10 @@ class $$PinnedMessagesTableOrderingComposer ColumnOrderings get i18n => $composableBuilder( column: $table.i18n, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, + builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => $composableBuilder( column: $table.extraData, builder: (column) => ColumnOrderings(column)); } @@ -9317,6 +9459,10 @@ class $$PinnedMessagesTableAnnotationComposer GeneratedColumnWithTypeConverter?, String> get i18n => $composableBuilder(column: $table.i18n, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> + get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => $composableBuilder( column: $table.extraData, builder: (column) => column); @@ -9398,6 +9544,7 @@ class $$PinnedMessagesTableTableManager extends RootTableManager< Value pinnedByUserId = const Value.absent(), Value channelCid = const Value.absent(), Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), Value?> extraData = const Value.absent(), Value rowid = const Value.absent(), }) => @@ -9431,6 +9578,7 @@ class $$PinnedMessagesTableTableManager extends RootTableManager< pinnedByUserId: pinnedByUserId, channelCid: channelCid, i18n: i18n, + restrictedVisibility: restrictedVisibility, extraData: extraData, rowid: rowid, ), @@ -9464,6 +9612,7 @@ class $$PinnedMessagesTableTableManager extends RootTableManager< Value pinnedByUserId = const Value.absent(), required String channelCid, Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), Value?> extraData = const Value.absent(), Value rowid = const Value.absent(), }) => @@ -9497,6 +9646,7 @@ class $$PinnedMessagesTableTableManager extends RootTableManager< pinnedByUserId: pinnedByUserId, channelCid: channelCid, i18n: i18n, + restrictedVisibility: restrictedVisibility, extraData: extraData, rowid: rowid, ), diff --git a/packages/stream_chat_persistence/lib/src/entity/messages.dart b/packages/stream_chat_persistence/lib/src/entity/messages.dart index 69c38f0c8..30f7e45a2 100644 --- a/packages/stream_chat_persistence/lib/src/entity/messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/messages.dart @@ -126,6 +126,10 @@ class Messages extends Table { TextColumn get i18n => text().nullable().map(NullableMapConverter())(); + /// The list of user ids that should be able to see the message. + TextColumn get restrictedVisibility => + text().nullable().map(ListConverter())(); + /// Message custom extraData TextColumn get extraData => text().nullable().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart index 33e307141..faedfda3e 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart @@ -52,6 +52,7 @@ extension MessageEntityX on MessageEntity { mentionedUsers: mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), i18n: i18n, + restrictedVisibility: restrictedVisibility, ); } @@ -89,5 +90,6 @@ extension MessageX on Message { pinExpires: pinExpires, pinnedByUserId: pinnedBy?.id, i18n: i18n, + restrictedVisibility: restrictedVisibility, ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart index 1f0b8d4fb..a41bdd4eb 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart @@ -52,6 +52,7 @@ extension PinnedMessageEntityX on PinnedMessageEntity { mentionedUsers: mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), i18n: i18n, + restrictedVisibility: restrictedVisibility, ); } @@ -90,5 +91,6 @@ extension PMessageX on Message { pinExpires: pinExpires, pinnedByUserId: pinnedBy?.id, i18n: i18n, + restrictedVisibility: restrictedVisibility, ); } diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index 76dc763fc..4bdf284cb 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -81,6 +81,7 @@ void main() { 'hi_text': 'рдирдорд╕реНрддреЗ', 'language': 'en', }, + restrictedVisibility: const ['user-id-2'], ); final message = entity.toMessage( user: user, @@ -137,6 +138,7 @@ void main() { expect(messageAttachment.type, entityAttachment.type); expect(messageAttachment.assetUrl, entityAttachment.assetUrl); } + expect(message.restrictedVisibility, entity.restrictedVisibility); }); test('toEntity should map message into MessageEntity', () { @@ -210,6 +212,7 @@ void main() { 'hi_text': 'рдирдорд╕реНрддреЗ', 'language': 'en', }, + restrictedVisibility: const ['user-id-2'], ); final entity = message.toEntity(cid: cid); expect(entity, isA()); @@ -251,5 +254,6 @@ void main() { message.attachments.map((it) => jsonEncode(it.toData())).toList(), ); expect(entity.i18n, message.i18n); + expect(entity.restrictedVisibility, message.restrictedVisibility); }); } diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index 9d54faea5..2095a3fdb 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -81,6 +81,7 @@ void main() { 'hi_text': 'рдирдорд╕реНрддреЗ', 'language': 'en', }, + restrictedVisibility: const ['user-id-2'], ); final message = entity.toMessage( user: user, @@ -137,6 +138,7 @@ void main() { expect(messageAttachment.type, entityAttachment.type); expect(messageAttachment.assetUrl, entityAttachment.assetUrl); } + expect(message.restrictedVisibility, entity.restrictedVisibility); }); test('toEntity should map message into MessageEntity', () { @@ -210,6 +212,7 @@ void main() { 'hi_text': 'рдирдорд╕реНрддреЗ', 'language': 'en', }, + restrictedVisibility: const ['user-id-2'], ); final entity = message.toPinnedEntity(cid: cid); expect(entity, isA()); @@ -251,5 +254,6 @@ void main() { message.attachments.map((it) => jsonEncode(it.toData())).toList(), ); expect(entity.i18n, message.i18n); + expect(entity.restrictedVisibility, message.restrictedVisibility); }); }