From aa223ce25877d9c9ba5aec6f247f27a40f0a7f3b Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Mon, 9 Aug 2021 14:30:07 +0200 Subject: [PATCH 01/11] chore: format comments --- .../stream_chat/lib/src/client/channel.dart | 232 ++++++++++-------- 1 file changed, 123 insertions(+), 109 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index a05587ab..4d4a3858 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -63,145 +63,145 @@ class Channel { _extraData.addAll(extraData); } - /// Returns true if the channel is muted + /// Returns true if the channel is muted. bool get isMuted => _client.state.currentUser?.channelMutes .any((element) => element.channel.cid == cid) == true; - /// Returns true if the channel is muted as a stream + /// Returns true if the channel is muted, as a stream. Stream? get isMutedStream => _client.state.currentUserStream .map((event) => event!.channelMutes.any((element) => element.channel.cid == cid) == true) .distinct(); - /// True if the channel is a group + /// True if the channel is a group. bool get isGroup => memberCount != 2; - /// True if the channel is distinct + /// True if the channel is distinct. bool get isDistinct => id?.startsWith('!members') == true; - /// Channel configuration + /// Channel configuration. ChannelConfig? get config { _checkInitialized(); return state?._channelState.channel?.config; } - /// Channel configuration as a stream + /// Channel configuration as a stream. Stream? get configStream { _checkInitialized(); return state?.channelStateStream.map((cs) => cs.channel?.config); } - /// Channel user creator + /// Channel user creator. User? get createdBy { _checkInitialized(); return state?._channelState.channel?.createdBy; } - /// Channel user creator as a stream + /// Channel user creator as a stream. Stream? get createdByStream { _checkInitialized(); return state?.channelStateStream.map((cs) => cs.channel?.createdBy); } - /// Channel frozen status + /// Channel frozen status. bool? get frozen { _checkInitialized(); return state?._channelState.channel?.frozen; } - /// Channel frozen status as a stream + /// Channel frozen status as a stream. Stream? get frozenStream { _checkInitialized(); return state?.channelStateStream.map((cs) => cs.channel?.frozen); } - /// Channel creation date + /// Channel creation date. DateTime? get createdAt { _checkInitialized(); return state?._channelState.channel?.createdAt; } - /// Channel creation date as a stream + /// Channel creation date as a stream. Stream? get createdAtStream { _checkInitialized(); return state?.channelStateStream.map((cs) => cs.channel?.createdAt); } - /// Channel last message date + /// Channel last message date. DateTime? get lastMessageAt { _checkInitialized(); return state?._channelState.channel?.lastMessageAt; } - /// Channel last message date as a stream + /// Channel last message date as a stream. Stream? get lastMessageAtStream { _checkInitialized(); return state?.channelStateStream.map((cs) => cs.channel?.lastMessageAt); } - /// Channel updated date + /// Channel updated date. DateTime? get updatedAt { _checkInitialized(); return state?._channelState.channel?.updatedAt; } - /// Channel updated date as a stream + /// Channel updated date as a stream. Stream? get updatedAtStream { _checkInitialized(); return state?.channelStateStream.map((cs) => cs.channel?.updatedAt); } - /// Channel deletion date + /// Channel deletion date. DateTime? get deletedAt { _checkInitialized(); return state?._channelState.channel?.deletedAt; } - /// Channel deletion date as a stream + /// Channel deletion date as a stream. Stream? get deletedAtStream { _checkInitialized(); return state?.channelStateStream.map((cs) => cs.channel?.deletedAt); } - /// Channel member count + /// Channel member count. int? get memberCount { _checkInitialized(); return state?._channelState.channel?.memberCount; } - /// Channel member count as a stream + /// Channel member count as a stream. Stream? get memberCountStream { _checkInitialized(); return state?.channelStateStream.map((cs) => cs.channel?.memberCount); } - /// Channel id + /// Channel id. String? get id => state?._channelState.channel?.id ?? _id; - /// Channel type + /// Channel type. String get type => state?._channelState.channel?.type ?? _type; - /// Channel cid + /// Channel cid. String? get cid => state?._channelState.channel?.cid ?? _cid; - /// Channel team + /// Channel team. String? get team { _checkInitialized(); return state?._channelState.channel?.team; } - /// Channel extra data + /// Channel extra data. Map get extraData { var data = state?._channelState.channel?.extraData; if (data == null || data.isEmpty) { @@ -210,7 +210,7 @@ class Channel { return data; } - /// Channel extra data as a stream + /// Channel extra data as a stream. Stream> get extraDataStream { _checkInitialized(); return state!.channelStateStream.map( @@ -224,9 +224,10 @@ class Channel { final Completer _initializedCompleter = Completer(); - /// True if this is initialized + /// True if this is initialized. + /// /// Call [watch] to initialize the client or instantiate it using - /// [Channel.fromState] + /// [Channel.fromState]. Future get initialized => _initializedCompleter.future; final _cancelableAttachmentUploadRequest = {}; @@ -362,7 +363,9 @@ class Channel { } /// Send a [message] to this channel. - /// If [skipPush] is true the message will not send a push notification + /// + /// If [skipPush] is true the message will not send a push notification. + /// /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually sending the message. Future sendMessage( @@ -427,6 +430,7 @@ class Channel { } /// Updates the [message] in this channel. + /// /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually updating the message. Future updateMessage(Message message) async { @@ -489,8 +493,10 @@ class Channel { } /// Partially updates the [message] in this channel. - /// Use [set] to define values to be set - /// Use [unset] to define values to be unset + /// + /// Use [set] to define values to be set. + /// + /// Use [unset] to define values to be unset. Future partialUpdateMessage( Message message, { Map? set, @@ -590,7 +596,7 @@ class Channel { ); } - /// Unpins provided message + /// Unpins provided message. Future unpinMessage(Message message) => partialUpdateMessage( message, @@ -599,7 +605,7 @@ class Channel { }, ); - /// Send a file to this channel + /// Send a file to this channel. Future sendFile( AttachmentFile file, { ProgressCallback? onSendProgress, @@ -615,7 +621,7 @@ class Channel { ); } - /// Send an image to this channel + /// Send an image to this channel. Future sendImage( AttachmentFile file, { ProgressCallback? onSendProgress, @@ -631,7 +637,7 @@ class Channel { ); } - /// A message search. + /// Search for a message with the given options. Future search({ String? query, Filter? messageFilters, @@ -648,7 +654,7 @@ class Channel { ); } - /// Delete a file from this channel + /// Delete a file from this channel. Future deleteFile( String url, { CancelToken? cancelToken, @@ -662,7 +668,7 @@ class Channel { ); } - /// Delete an image from this channel + /// Delete an image from this channel. Future deleteImage( String url, { CancelToken? cancelToken, @@ -676,14 +682,15 @@ class Channel { ); } - /// Send an event on this channel + /// Send an event on this channel. Future sendEvent(Event event) { _checkInitialized(); return _client.sendEvent(id!, type, event); } - /// Send a reaction to this channel - /// Set [enforceUnique] to true to remove the existing user reaction + /// Send a reaction to this channel. + /// + /// Set [enforceUnique] to true to remove the existing user reaction. Future sendReaction( Message message, String type, { @@ -746,7 +753,7 @@ class Channel { } } - /// Delete a reaction from this channel + /// Delete a reaction from this channel. Future deleteReaction( Message message, Reaction reaction) async { final type = reaction.type; @@ -821,25 +828,25 @@ class Channel { return _client.deleteChannel(id!, type); } - /// Removes all messages from the channel + /// Removes all messages from the channel. Future truncate() async { _checkInitialized(); return _client.truncateChannel(id!, type); } - /// Accept invitation to the channel + /// Accept invitation to the channel. Future acceptInvite([Message? message]) async { _checkInitialized(); return _client.acceptChannelInvite(id!, type, message: message); } - /// Reject invitation to the channel + /// Reject invitation to the channel. Future rejectInvite([Message? message]) async { _checkInitialized(); return _client.rejectChannelInvite(id!, type, message: message); } - /// Add members to the channel + /// Add members to the channel. Future addMembers( List memberIds, [ Message? message, @@ -848,7 +855,7 @@ class Channel { return _client.addChannelMembers(id!, type, memberIds, message: message); } - /// Invite members to the channel + /// Invite members to the channel. Future inviteMembers( List memberIds, [ Message? message, @@ -857,7 +864,7 @@ class Channel { return _client.inviteChannelMembers(id!, type, memberIds, message: message); } - /// Remove members from the channel + /// Remove members from the channel. Future removeMembers( List memberIds, [ Message? message, @@ -866,7 +873,7 @@ class Channel { return _client.removeChannelMembers(id!, type, memberIds, message: message); } - /// Send action for a specific message of this channel + /// Send action for a specific message of this channel. Future sendAction( Message message, Map formData, @@ -913,9 +920,10 @@ class Channel { return res; } - /// Mark all messages as read + /// Mark all messages as read. + /// /// Optionally provide a [messageId] if you want to mark a - /// particular message as read + /// particular message as read. Future markRead({String? messageId}) async { _checkInitialized(); client.state.totalUnreadCount = @@ -924,7 +932,7 @@ class Channel { return _client.markChannelRead(id!, type, messageId: messageId); } - /// Loads the initial channel state and watches for changes + /// Loads the initial channel state and watches for changes. Future watch() async { ChannelState response; @@ -955,15 +963,16 @@ class Channel { } } - /// Stop watching the channel + /// Stop watching the channel. Future stopWatching() async { _checkInitialized(); return _client.stopChannelWatching(id!, type); } - /// List the message replies for a parent message + /// List the message replies for a parent message. + /// /// Set [preferOffline] to true to avoid the api call if the data is already - /// in the offline storage + /// in the offline storage. Future getReplies( String parentId, { PaginationParams? options, @@ -987,7 +996,7 @@ class Channel { return repliesResponse; } - /// List the reactions for a message in the channel + /// List the reactions for a message in the channel. Future getReactions( String messageId, { PaginationParams? pagination, @@ -997,7 +1006,7 @@ class Channel { pagination: pagination, ); - /// Retrieves a list of messages by ID + /// Retrieves a list of messages by given [messageIDs]. Future getMessagesById( List messageIDs, ) async { @@ -1008,7 +1017,7 @@ class Channel { return res; } - /// Retrieves a list of messages by ID + /// Translate a message by given [messageId] and [language]. Future translateMessage( String messageId, String language, @@ -1018,12 +1027,13 @@ class Channel { language, ); - /// Creates a new channel + /// Creates a new channel. Future create() async => query(state: false); - /// Query the API, get messages, members or other channel fields - /// Set [preferOffline] to true to avoid the api call if the data is already - /// in the offline storage + /// Query the API, get messages, members or other channel fields. + /// + /// Set [preferOffline] to true to avoid the API call if the data is already + /// in the offline storage. Future query({ bool state = true, bool watch = false, @@ -1077,7 +1087,7 @@ class Channel { } } - /// Query channel members + /// Query channel members. Future queryMembers({ Filter? filter, List? sort, @@ -1092,19 +1102,19 @@ class Channel { pagination: pagination, ); - /// Mutes the channel + /// Mutes the channel. Future mute({Duration? expiration}) { _checkInitialized(); return _client.muteChannel(cid!, expiration: expiration); } - /// Unmutes the channel + /// Unmute the channel. Future unmute() { _checkInitialized(); return _client.unmuteChannel(cid!); } - /// Bans a user from the channel + /// Bans the user with given [userID] from the channel. Future banUser( String userID, Map options, @@ -1118,7 +1128,7 @@ class Channel { return _client.banUser(userID, opts); } - /// Remove the ban for a user in the channel + /// Remove the ban for the user with given [userID] in the channel. Future unbanUser(String userID) async { _checkInitialized(); return _client.unbanUser(userID, { @@ -1127,7 +1137,7 @@ class Channel { }); } - /// Shadow bans a user from the channel + /// Shadow bans the user with the given [userID] from the channel. Future shadowBan( String userID, Map options, @@ -1141,7 +1151,7 @@ class Channel { return _client.shadowBan(userID, opts); } - /// Remove the shadow ban for a user in the channel + /// Remove the shadow ban for the user with the given [userID] in the channel. Future removeShadowBan(String userID) async { _checkInitialized(); return _client.removeShadowBan(userID, { @@ -1151,8 +1161,10 @@ class Channel { } /// Hides the channel from [StreamChatClient.queryChannels] for the user - /// until a message is added If [clearHistory] is set to true - all messages - /// will be removed for the user + /// until a message is added. + /// + /// If [clearHistory] is set to true - all messages + /// will be removed for the user. Future hide({bool clearHistory = false}) async { _checkInitialized(); final response = await _client.hideChannel( @@ -1170,7 +1182,7 @@ class Channel { return response; } - /// Removes the hidden status for the channel + /// Removes the hidden status for the channel. Future show() async { _checkInitialized(); return _client.showChannel(id!, type); @@ -1178,7 +1190,7 @@ class Channel { /// Stream of [Event] coming from websocket connection specific for the /// channel. Pass an eventType as parameter in order to filter just a type - /// of event + /// of event. Stream on([ String? eventType, String? eventType2, @@ -1216,7 +1228,7 @@ class Channel { } } - /// Sets last typing to null and sends the typing.stop event + /// Sets last typing to null and sends the typing.stop event. Future stopTyping([String? parentId]) async { if (config?.typingEvents == false) { return; @@ -1230,7 +1242,7 @@ class Channel { )); } - /// Call this method to dispose the channel client + /// Call this method to dispose the channel client. void dispose() { state?.dispose(); } @@ -1244,9 +1256,9 @@ class Channel { } } -/// The class that handles the state of the channel listening to the events +/// The class that handles the state of the channel listening to the events. class ChannelClientState { - /// Creates a new instance listening to events and updating the state + /// Creates a new instance listening to events and updating the state. ChannelClientState( this._channel, ChannelState channelState, @@ -1393,23 +1405,25 @@ class ChannelClientState { } /// Flag which indicates if [ChannelClientState] contain latest/recent messages or not. + /// /// This flag should be managed by UI sdks. - /// When false, any new message (received by WebSocket event - /// - [EventType.messageNew]) will not be pushed on to message list. + /// + /// When false, any new message received by WebSocket event + /// [EventType.messageNew] will not be pushed on to message list. bool get isUpToDate => _isUpToDateController.value; set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate); - /// [isUpToDate] flag count as a stream + /// [isUpToDate] flag count as a stream. Stream get isUpToDateStream => _isUpToDateController.stream; final BehaviorSubject _isUpToDateController = BehaviorSubject.seeded(true); - /// The retry queue associated to this channel + /// The retry queue associated to this channel. late final RetryQueue _retryQueue; - /// Retry failed message + /// Retry failed message. Future retryFailedMessages() async { final failedMessages = [...messages, ...threads.values.expand((v) => v)] @@ -1502,7 +1516,7 @@ class ChannelClientState { })); } - /// Add a message to this channel + /// Add a message to this channel. void addMessage(Message message) { if (message.parentId == null || message.showInChannel == true) { final newMessages = List.from(_channelState.messages); @@ -1567,36 +1581,36 @@ class ChannelClientState { ); } - /// Channel message list + /// Channel message list. List get messages => _channelState.messages; - /// Channel message list as a stream + /// Channel message list as a stream. Stream?> get messagesStream => channelStateStream .map((cs) => cs.messages) .distinct(const ListEquality().equals); - /// Channel pinned message list + /// Channel pinned message list. List? get pinnedMessages => _channelState.pinnedMessages.toList(); - /// Channel pinned message list as a stream + /// Channel pinned message list as a stream. Stream?> get pinnedMessagesStream => channelStateStream.map((cs) => cs.pinnedMessages.toList()); - /// Get channel last message + /// Get channel last message. Message? get lastMessage => _channelState.messages.isNotEmpty == true ? _channelState.messages.last : null; - /// Get channel last message + /// Get channel last message. Stream get lastMessageStream => messagesStream .map((event) => event?.isNotEmpty == true ? event!.last : null); - /// Channel members list + /// Channel members list. List get members => _channelState.members .map((e) => e.copyWith(user: _channel.client.state.users[e.user!.id])) .toList(); - /// Channel members list as a stream + /// Channel members list as a stream. Stream> get membersStream => CombineLatestStream.combine2< List?, Map, List>( channelStateStream.map((cs) => cs.members), @@ -1605,19 +1619,19 @@ class ChannelClientState { members!.map((e) => e!.copyWith(user: users[e.user!.id])).toList(), ).distinct(const ListEquality().equals); - /// Channel watcher count + /// Channel watcher count. int? get watcherCount => _channelState.watcherCount; - /// Channel watcher count as a stream + /// Channel watcher count as a stream. Stream get watcherCountStream => channelStateStream.map((cs) => cs.watcherCount); - /// Channel watchers list + /// Channel watchers list. List get watchers => _channelState.watchers .map((e) => _channel.client.state.users[e.id] ?? e) .toList(); - /// Channel watchers list as a stream + /// Channel watchers list as a stream. Stream> get watchersStream => CombineLatestStream.combine2< List?, Map, List>( channelStateStream.map((cs) => cs.watchers), @@ -1625,20 +1639,20 @@ class ChannelClientState { (watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(), ); - /// Channel read list + /// Channel read list. List? get read => _channelState.read; - /// Channel read list as a stream + /// Channel read list as a stream. Stream?> get readStream => channelStateStream.map((cs) => cs.read); final BehaviorSubject _unreadCountController = BehaviorSubject.seeded(0); set unreadCount(int value) => _unreadCountController.add(value); - /// Unread count getter as a stream + /// Unread count getter as a stream. Stream get unreadCountStream => _unreadCountController.stream.distinct(); - /// Unread count getter + /// Unread count getter. int get unreadCount => _unreadCountController.value; bool _countMessageAsUnread(Message message) { @@ -1654,7 +1668,7 @@ class ChannelClientState { !userIsMuted; } - /// Update threads with updated information about messages + /// Update threads with updated information about messages. void updateThreadInfo(String parentId, List messages) { final newThreads = Map>.from(threads); @@ -1676,7 +1690,7 @@ class ChannelClientState { _threads = newThreads; } - /// Delete all channel messages + /// Delete all channel messages. void truncate() { _channelState = _channelState.copyWith( messages: [], @@ -1685,7 +1699,7 @@ class ChannelClientState { final List _updatedMessagesIds = []; - /// Update channelState with updated information + /// Update channelState with updated information. void updateChannelState(ChannelState updatedState) { final newMessages = [ ...updatedState.messages, @@ -1737,13 +1751,13 @@ class ChannelClientState { int _sortByCreatedAt(Message a, Message b) => a.createdAt.compareTo(b.createdAt); - /// The channel state related to this client + /// The channel state related to this client. ChannelState get _channelState => _channelStateController.value; - /// The channel state related to this client as a stream + /// The channel state related to this client as a stream. Stream get channelStateStream => _channelStateController.stream; - /// The channel state related to this client + /// The channel state related to this client. ChannelState get channelState => _channelStateController.value; late BehaviorSubject _channelStateController; @@ -1754,11 +1768,11 @@ class ChannelClientState { _debouncedUpdatePersistenceChannelState.call([v]); } - /// The channel threads related to this channel + /// The channel threads related to this channel. Map> get threads => _threadsController.value.map((key, value) => MapEntry(key, value)); - /// The channel threads related to this channel as a stream + /// The channel threads related to this channel as a stream. Stream>> get threadsStream => _threadsController.stream; final BehaviorSubject>> _threadsController = @@ -1772,10 +1786,10 @@ class ChannelClientState { _threadsController.add(v); } - /// Channel related typing users last value + /// Channel related typing users last value. Map get typingEvents => _typingEventsController.value; - /// Channel related typing users stream + /// Channel related typing users stream. Stream> get typingEventsStream => _typingEventsController.stream; @@ -1903,7 +1917,7 @@ class ChannelClientState { }); } - /// Call this method to dispose this object + /// Call this method to dispose this object. void dispose() { _debouncedUpdatePersistenceChannelState.cancel(); _unreadCountController.close(); From eae7cb10206264bfd1570dcf96f3d998910d41f6 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 10 Aug 2021 09:59:08 +0200 Subject: [PATCH 02/11] feat: add image get, set and update to channel --- .../stream_chat/lib/src/client/channel.dart | 110 ++++++++++++++++-- .../test/src/api/channel_test.dart | 54 +++++++++ 2 files changed, 155 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 4d4a3858..7ea1a50b 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -15,20 +15,49 @@ import 'package:stream_chat/src/core/util/utils.dart'; import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/stream_chat.dart'; -/// This a the class that manages a specific channel. +/// Class that manages a specific channel. +/// +/// {@template image} +/// If an optional [image] argument is provided in the constructor then it +/// will be set on [extraData] with a key of 'image'. +/// +/// ```dart +/// final channel = Channel(client, type, id, image: 'https://getstream.io/image.png'); +/// print(channel.image == channel.extraData['image']); // true +/// ``` +/// +/// Before the channel is initialized the image can be set directly: +/// ```dart +/// channel.image = 'https://getstream.io/new-image'; +/// ``` +/// +/// To update the image after the channel has been initialized call: +/// ```dart +/// channel.updateImage('https://getstream.io/new-image'); +/// ``` +/// +/// This will do a partial update to update the image. +/// {@endtemplate} class Channel { - /// Create a channel client instance. + /// Class that manages a specific channel. + /// + /// Optional [extraData] and [image] properties can be provided. The [image] + /// is exposed to easily set a key of 'image' on [extraData]. Channel( this._client, this._type, this._id, { Map? extraData, + String? image, }) : _cid = _id != null ? '$_type:$_id' : null, - _extraData = extraData ?? {} { - _client.logger.info('New Channel instance not initialized created'); + _extraData = { + ...?extraData, + if (image != null) 'image': image, + } { + _client.logger.info('New Channel instance created, not yet initialized'); } - /// Create a channel client instance from a [ChannelState] object + /// Create a channel client instance from a [ChannelState] object. Channel.fromState(this._client, ChannelState channelState) : assert( channelState.channel != null, @@ -40,7 +69,7 @@ class Channel { _extraData = channelState.channel!.extraData { state = ChannelClientState(this, channelState); _initializedCompleter.complete(true); - _client.logger.info('New Channel instance initialized created'); + _client.logger.info('New Channel instance initialized'); } /// This client state @@ -63,6 +92,19 @@ class Channel { _extraData.addAll(extraData); } + /// Shortcut to set channel image. + /// + /// {@macro image} + set image(String? image) { + if (_initializedCompleter.isCompleted) { + throw StateError( + 'Once the channel is initialized you should use channel.update ' + 'to update channel image', + ); + } + _extraData.addAll({'image': image}); + } + /// Returns true if the channel is muted. bool get isMuted => _client.state.currentUser?.channelMutes @@ -218,7 +260,26 @@ class Channel { ); } - /// The main Stream chat client + /// Shortcut to get channel image. + /// + /// {@macro image} + String? get image => extraData['image'] as String?; + + /// Channel [image] as a stream. + /// + /// The channel needs to be initialized. + /// + /// {@macro image} + Stream get imageStream { + _checkInitialized(); + return state!.channelStateStream.map( + (cs) => + (cs.channel?.extraData['image'] as String?) ?? + (_extraData['image'] as String?), + ); + } + + /// The main Stream chat client. StreamChatClient get client => _client; final StreamChatClient _client; @@ -799,7 +860,36 @@ class Channel { } } - /// Edit the channel custom data + /// Update the channel's [image]. + /// + /// This is equivelant to calling [updatePartial] and providing a map with an + /// 'image' key: + /// + /// ```dart + /// channel.updatePartial( + /// set: {'image': 'https://getstream.io/new-image'} + /// ); + /// ``` + /// + /// Instead do: + /// ```dart + /// channel.updateImage('https://getstream.io/new-image'); + /// ``` + Future updateImage( + String image, + ) { + _checkInitialized(); + + return _client.updateChannelPartial( + id!, + type, + set: { + 'image': image, + }, + ); + } + + /// Edit the channel custom data. Future update( Map channelData, [ Message? updateMessage, @@ -813,7 +903,9 @@ class Channel { ); } - /// Edit the channel custom data + /// Edit the channel custom data. + // TODO: This is the same description as [update]. Distinguish the two + // and provide a better description for set and unset. Future updatePartial({ Map? set, List? unset, diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 5be55491..381c208b 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -72,6 +72,23 @@ void main() { expect(channel.extraData.containsKey('name'), isTrue); expect(channel.extraData['name'], 'test-channel-name'); }); + + test('should be able to get and set `image`', () { + expect(channel.extraData.isEmpty, isTrue); + + const imageUrl = 'https://getstream.io/some-image'; + channel.image = imageUrl; + + expect(channel.image, imageUrl); + expect(channel.extraData['image'], imageUrl); + + const newImage = 'https://getstream.io/new-image'; + final newChannelInstance = + Channel(client, channelType, channelId, image: newImage); + + expect(newChannelInstance.image, newImage); + expect(newChannelInstance.extraData['image'], newImage); + }); }); // TODO : test all persistence related logic in this group @@ -192,6 +209,14 @@ void main() { } }); + test('should throw if trying to set `image`', () { + try { + channel.image = 'https://stream.io/some-image'; + } catch (e) { + expect(e, isA()); + } + }); + group('`.sendMessage`', () { test('should work fine', () async { final message = Message(id: 'test-message-id'); @@ -1192,6 +1217,35 @@ void main() { message: any(named: 'message'))).called(1); }); + test('`.updateImage`', () async { + const image = 'https://getstream.io/new-image'; + + final channelModel = ChannelModel( + cid: channelCid, + extraData: {'image': image}, + ); + + when(() => client.updateChannelPartial( + any(), + any(), + set: {'image': image}, + )).thenAnswer( + (_) async => PartialUpdateChannelResponse()..channel = channelModel, + ); + final res = await channel.updateImage(image); + + expect(res, isNotNull); + expect(res.channel.extraData['image'], image); + + verify( + () => client.updateChannelPartial( + any(), + any(), + set: {'image': image}, + ), + ).called(1); + }); + test('`.updatePartial`', () async { const set = { 'name': 'Stream Team', From ae8ebe2dba6e6a1c3a175bb506c18610bbf7c7ab Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 10 Aug 2021 10:00:02 +0200 Subject: [PATCH 03/11] chore: prefer image and name property on image --- .../test/src/core/models/reaction_test.dart | 28 ++++++++++--------- .../test/src/channel_image_test.dart | 26 +++++------------ 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/packages/stream_chat/test/src/core/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart index 0891b548..fbe493ef 100644 --- a/packages/stream_chat/test/src/core/models/reaction_test.dart +++ b/packages/stream_chat/test/src/core/models/reaction_test.dart @@ -13,10 +13,11 @@ void main() { expect(reaction.type, 'wow'); expect( reaction.user?.toJson(), - User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const { - 'image': 'https://randomuser.me/api/portraits/women/45.jpg', - 'name': 'Daisy Morgan' - }).toJson(), + User( + id: '2de0297c-f3f2-489d-b930-ef77342edccf', + image: 'https://randomuser.me/api/portraits/women/45.jpg', + name: 'Daisy Morgan', + ).toJson(), ); expect(reaction.score, 1); expect(reaction.userId, '2de0297c-f3f2-489d-b930-ef77342edccf'); @@ -28,11 +29,11 @@ void main() { messageId: '76cd8c82-b557-4e48-9d12-87995d3a0e04', createdAt: DateTime.parse('2020-01-28T22:17:31.108742Z'), type: 'wow', - user: - User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const { - 'image': 'https://randomuser.me/api/portraits/women/45.jpg', - 'name': 'Daisy Morgan' - }), + user: User( + id: '2de0297c-f3f2-489d-b930-ef77342edccf', + image: 'https://randomuser.me/api/portraits/women/45.jpg', + name: 'Daisy Morgan', + ), userId: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: {'bananas': 'yes'}, score: 1, @@ -58,10 +59,11 @@ void main() { expect(newReaction.type, 'wow'); expect( newReaction.user?.toJson(), - User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const { - 'image': 'https://randomuser.me/api/portraits/women/45.jpg', - 'name': 'Daisy Morgan', - }).toJson(), + User( + id: '2de0297c-f3f2-489d-b930-ef77342edccf', + image: 'https://randomuser.me/api/portraits/women/45.jpg', + name: 'Daisy Morgan', + ).toJson(), ); expect(newReaction.score, 1); expect(newReaction.userId, '2de0297c-f3f2-489d-b930-ef77342edccf'); diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart index c9293e79..f6d110dd 100644 --- a/packages/stream_chat_flutter/test/src/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -48,7 +48,7 @@ void main() { ); testWidgets( - 'it should show the the other member image', + 'it should show the other member image', (tester) async { final client = MockClient(); final clientState = MockClientState(); @@ -74,9 +74,7 @@ void main() { userId: 'user-id2', user: User( id: 'user-id2', - extraData: const { - 'image': 'testimage', - }, + image: 'testimage', ), ) ])); @@ -85,9 +83,7 @@ void main() { userId: 'user-id2', user: User( id: 'user-id2', - extraData: const { - 'image': 'testimage', - }, + image: 'testimage', ), ), Member( @@ -98,9 +94,7 @@ void main() { when(() => clientState.usersStream).thenAnswer((i) => Stream.value({ 'user-id2': User( id: 'user-id2', - extraData: const { - 'image': 'testimage', - }, + image: 'testimage', ), })); when(() => channel.extraData).thenReturn({ @@ -149,27 +143,21 @@ void main() { userId: 'user-id', user: User( id: 'user-id', - extraData: const { - 'image': 'testimage1', - }, + image: 'testimage1', ), ), Member( userId: 'user-id2', user: User( id: 'user-id2', - extraData: const { - 'image': 'testimage2', - }, + image: 'testimage2', ), ), Member( userId: 'user-id3', user: User( id: 'user-id3', - extraData: const { - 'image': 'testimage3', - }, + image: 'testimage3', ), ), ]; From 131019d77af5c5186a2005b1fbe13cbb92c6df0a Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 10 Aug 2021 10:09:37 +0200 Subject: [PATCH 04/11] chore: update CHANGELOG.md --- packages/stream_chat/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index e3e31db6..513c1cdb 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -8,7 +8,7 @@ - `User` and `OwnUser` classes now have an `image` property. Setting an image will also set the 'image' key on `extraData`, so `user.image` and `user.extraData['image']` is the same. - `User` and `OwnUser` classes now have a `name` property. Setting a name will also set the 'name' key on `extraData`, so `user.name` and `user.extraData['name']` is the same. - +- `Channel` class now has extra `image` getter and setter. As well as an `updateImage` to do a partial update after a channel has been initialized. ## 2.1.1 🐞 Fixed From adfc0adea97362909f7ed91f3b1652cc5812f4ab Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 10 Aug 2021 11:53:30 +0200 Subject: [PATCH 05/11] feat: add name get, set and update on Channel --- .../stream_chat/lib/src/client/channel.dart | 113 +++++++++++++++--- .../test/src/api/channel_test.dart | 59 +++++++++ .../example/lib/main.dart | 16 +-- 3 files changed, 160 insertions(+), 28 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 7ea1a50b..aeee3540 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -17,6 +17,32 @@ import 'package:stream_chat/stream_chat.dart'; /// Class that manages a specific channel. /// +/// #### Channel name +/// +/// {@template name} +/// If an optional [name] argument is provided in the constructor then it +/// will be set on [extraData] with a key of 'name'. +/// +/// ```dart +/// final channel = Channel(client, type, id, name: 'Channel name'); +/// print(channel.name == channel.extraData['name']); // true +/// ``` +/// +/// Before the channel is initialized the name can be set directly: +/// ```dart +/// channel.name = 'New channel name'; +/// ``` +/// +/// To update the name after the channel has been initialized, call: +/// ```dart +/// channel.updateName('Updated channel name'); +/// ``` +/// +/// This will do a partial update to update the name. +/// {@endtemplate} +/// +/// #### Channel image +/// /// {@template image} /// If an optional [image] argument is provided in the constructor then it /// will be set on [extraData] with a key of 'image'. @@ -31,7 +57,7 @@ import 'package:stream_chat/stream_chat.dart'; /// channel.image = 'https://getstream.io/new-image'; /// ``` /// -/// To update the image after the channel has been initialized call: +/// To update the image after the channel has been initialized, call: /// ```dart /// channel.updateImage('https://getstream.io/new-image'); /// ``` @@ -49,10 +75,12 @@ class Channel { this._id, { Map? extraData, String? image, + String? name, }) : _cid = _id != null ? '$_type:$_id' : null, _extraData = { ...?extraData, if (image != null) 'image': image, + if (name != null) 'name': name, } { _client.logger.info('New Channel instance created, not yet initialized'); } @@ -98,13 +126,26 @@ class Channel { set image(String? image) { if (_initializedCompleter.isCompleted) { throw StateError( - 'Once the channel is initialized you should use channel.update ' - 'to update channel image', + 'Once the channel is initialized you should use channel.updateImage ' + 'to update the channel image', ); } _extraData.addAll({'image': image}); } + /// Shortcut to set channel name. + /// + /// {@macro name} + set name(String? name) { + if (_initializedCompleter.isCompleted) { + throw StateError( + 'Once the channel is initialized you should use channel.updateName ' + 'to update the channel image', + ); + } + _extraData.addAll({'name': name}); + } + /// Returns true if the channel is muted. bool get isMuted => _client.state.currentUser?.channelMutes @@ -279,6 +320,39 @@ class Channel { ); } + /// Shortcut to get channel name. + /// + /// If no name is set this returns the channel cid, else null. + /// + /// {@macro name} + String? get name { + if (extraData.containsKey('name')) { + final name = extraData['name']! as String; + if (name.isNotEmpty) return name; + } + return cid; + } + + /// Channel [name] as a stream. + /// + /// If no name is set the stream returns the channel cid. + /// + /// The channel needs to be initialized. + /// + /// {@macro name} + Stream get nameStream { + _checkInitialized(); + return state!.channelStateStream.map( + (cs) { + if (cs.channel?.extraData.containsKey('name') ?? false) { + final name = cs.channel!.extraData['name']! as String; + if (name.isNotEmpty) return name; + } + return name!; + }, + ); + } + /// The main Stream chat client. StreamChatClient get client => _client; final StreamChatClient _client; @@ -862,7 +936,7 @@ class Channel { /// Update the channel's [image]. /// - /// This is equivelant to calling [updatePartial] and providing a map with an + /// This is the same as calling [updatePartial] and providing a map with an /// 'image' key: /// /// ```dart @@ -877,17 +951,28 @@ class Channel { /// ``` Future updateImage( String image, - ) { - _checkInitialized(); + ) => + updatePartial(set: {'image': image}); - return _client.updateChannelPartial( - id!, - type, - set: { - 'image': image, - }, - ); - } + /// Update the channel's [name]. + /// + /// This is the same as calling [updatePartial] and providing a map with a + /// 'name' key: + /// + /// ```dart + /// channel.updatePartial( + /// set: {'name': 'Updated channel name'} + /// ); + /// ``` + /// + /// Instead do: + /// ```dart + /// channel.updateName('Updated channel name'); + /// ``` + Future updateName( + String name, + ) => + updatePartial(set: {'name': name}); /// Edit the channel custom data. Future update( diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 381c208b..1166405a 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -89,6 +89,28 @@ void main() { expect(newChannelInstance.image, newImage); expect(newChannelInstance.extraData['image'], newImage); }); + + test('should be able to get and set `name`', () { + expect(channel.extraData.isEmpty, isTrue); + expect( + channel.name, + channelId, + reason: 'if name is not set then use channel id', + ); + + const name = 'Channel name'; + channel.name = name; + + expect(channel.name, name); + expect(channel.extraData['name'], name); + + const newName = 'New channel name'; + final newChannelInstance = + Channel(client, channelType, channelId, name: newName); + + expect(newChannelInstance.name, newName); + expect(newChannelInstance.extraData['name'], newName); + }); }); // TODO : test all persistence related logic in this group @@ -217,6 +239,14 @@ void main() { } }); + test('should throw if trying to set `name`', () { + try { + channel.name = 'New name'; + } catch (e) { + expect(e, isA()); + } + }); + group('`.sendMessage`', () { test('should work fine', () async { final message = Message(id: 'test-message-id'); @@ -1246,6 +1276,35 @@ void main() { ).called(1); }); + test('`.updateName`', () async { + const name = 'Name'; + + final channelModel = ChannelModel( + cid: channelCid, + extraData: {'name': name}, + ); + + when(() => client.updateChannelPartial( + any(), + any(), + set: {'name': name}, + )).thenAnswer( + (_) async => PartialUpdateChannelResponse()..channel = channelModel, + ); + final res = await channel.updateName(name); + + expect(res, isNotNull); + expect(res.channel.extraData['name'], name); + + verify( + () => client.updateChannelPartial( + any(), + any(), + set: {'name': name}, + ), + ).called(1); + }); + test('`.updatePartial`', () async { const set = { 'name': 'Stream Team', diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index 03e791de..31410e63 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -329,21 +329,9 @@ class _MessageScreenState extends State { } } -/// Extensions can be used to add functionality to the SDK. In the examples -/// below, we add two simple extensions to the [StreamChatClient] and [Channel]. +/// Extensions can be used to add functionality to the SDK. In the example +/// below, we add a simple extensions to the [StreamChatClient]. extension on StreamChatClient { /// Fetches the current user id. String get uid => state.currentUser!.id; } - -extension on Channel { - /// Fetches the name of the channel by accessing [extraData] or [cid]. - String? get name { - final _channelName = extraData['name']; - if (_channelName != null) { - return _channelName as String; - } else { - return cid; - } - } -} From 472b50397f4ae19266a50647217aa99fd56e69f9 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 10 Aug 2021 11:54:31 +0200 Subject: [PATCH 06/11] chore: update CHANGELOG.md --- packages/stream_chat/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 513c1cdb..52496a96 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -9,6 +9,7 @@ - `User` and `OwnUser` classes now have an `image` property. Setting an image will also set the 'image' key on `extraData`, so `user.image` and `user.extraData['image']` is the same. - `User` and `OwnUser` classes now have a `name` property. Setting a name will also set the 'name' key on `extraData`, so `user.name` and `user.extraData['name']` is the same. - `Channel` class now has extra `image` getter and setter. As well as an `updateImage` to do a partial update after a channel has been initialized. +- `Channel` class now has extra `name` getter and setter. As well as an `updateName` to do a partial update after a channel has been initialized. ## 2.1.1 🐞 Fixed From 250162b7bdf3de0c4ad6364d36f97cd7019eea9d Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 10 Aug 2021 11:56:32 +0200 Subject: [PATCH 07/11] use image getter --- packages/stream_chat/lib/src/client/channel.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index aeee3540..5181eaad 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -314,9 +314,7 @@ class Channel { Stream get imageStream { _checkInitialized(); return state!.channelStateStream.map( - (cs) => - (cs.channel?.extraData['image'] as String?) ?? - (_extraData['image'] as String?), + (cs) => (cs.channel?.extraData['image'] as String?) ?? image, ); } From 4dff4d4cbe66fed7140344f6b0fdb1ff26f229af Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 10 Aug 2021 12:17:19 +0200 Subject: [PATCH 08/11] chore: additional descriptions for channel update --- .../stream_chat/lib/src/client/channel.dart | 23 +++++++++++++++---- .../stream_chat/lib/src/client/client.dart | 9 ++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 5181eaad..7bf91caf 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -972,7 +972,13 @@ class Channel { ) => updatePartial(set: {'name': name}); - /// Edit the channel custom data. + /// Update the channel custom data. This replaces all of the channel data + /// with the given [channelData]. + /// + /// If you instead want to do a partial update, use [updatePartial]. + /// + /// See, https://getstream.io/chat/docs/other-rest/channel_update/?language=dart + /// for more information. Future update( Map channelData, [ Message? updateMessage, @@ -986,9 +992,18 @@ class Channel { ); } - /// Edit the channel custom data. - // TODO: This is the same description as [update]. Distinguish the two - // and provide a better description for set and unset. + /// A partial update can be used to set and unset specific custom data fields + /// when it is necessary to retain additional custom data fields on the + /// object. + /// + /// - [set] will add, or update existing attributes. + /// - [unset] will remove the attributes with the provided list of + /// values (keys). + /// + /// If you want to do a full update/replacement, use [update] instead. + /// + /// See, https://getstream.io/chat/docs/other-rest/channel_update/?language=dart + /// for more information. Future updatePartial({ Map? set, List? unset, diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 104b23fc..74abcb39 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -767,7 +767,9 @@ class StreamChatClient { cancelToken: cancelToken, ); - /// Replaces the [channelId] of type [ChannelType] data with [data] + /// Replaces the [channelId] of type [ChannelType] data with [data]. + /// + /// Use [updateChannelPartial] for a partial update. Future updateChannel( String channelId, String channelType, @@ -781,7 +783,10 @@ class StreamChatClient { message: message, ); - /// Updates the [channelId] of type [ChannelType] data with [data] + /// Partial update for the [channelId] of type [ChannelType]. Sets the + /// data provided in [set], and removes the attributes given in [unset]. + /// + /// Use [updateChannel] for a full update. Future updateChannelPartial( String channelId, String channelType, { From 5e2c6975e21642f871b9febd519882d4824a7cb7 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 10 Aug 2021 13:59:41 +0200 Subject: [PATCH 09/11] chore: update error description --- packages/stream_chat/lib/src/client/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 7bf91caf..656d7f38 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -140,7 +140,7 @@ class Channel { if (_initializedCompleter.isCompleted) { throw StateError( 'Once the channel is initialized you should use channel.updateName ' - 'to update the channel image', + 'to update the channel name', ); } _extraData.addAll({'name': name}); From b3a1d42f92d49a15f8746859269a2ca1e4096798 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 11 Aug 2021 12:47:20 +0530 Subject: [PATCH 10/11] chore(llc): fix tests, minor changes Signed-off-by: xsahil03x --- .../stream_chat/lib/src/client/channel.dart | 112 +++++++++--------- .../test/src/api/channel_test.dart | 37 +++--- 2 files changed, 73 insertions(+), 76 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 656d7f38..5eee8060 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -73,14 +73,14 @@ class Channel { this._client, this._type, this._id, { - Map? extraData, - String? image, String? name, + String? image, + Map? extraData, }) : _cid = _id != null ? '$_type:$_id' : null, _extraData = { ...?extraData, - if (image != null) 'image': image, if (name != null) 'name': name, + if (image != null) 'image': image, } { _client.logger.info('New Channel instance created, not yet initialized'); } @@ -110,14 +110,17 @@ class Channel { String? _cid; final Map _extraData; - set extraData(Map extraData) { + /// Shortcut to set channel name. + /// + /// {@macro name} + set name(String? name) { if (_initializedCompleter.isCompleted) { throw StateError( - 'Once the channel is initialized you should use channel.update ' - 'to update channel data', + 'Once the channel is initialized you should use `channel.updateName` ' + 'to update the channel name', ); } - _extraData.addAll(extraData); + _extraData.addAll({'name': name}); } /// Shortcut to set channel image. @@ -126,24 +129,21 @@ class Channel { set image(String? image) { if (_initializedCompleter.isCompleted) { throw StateError( - 'Once the channel is initialized you should use channel.updateImage ' + 'Once the channel is initialized you should use `channel.updateImage` ' 'to update the channel image', ); } _extraData.addAll({'image': image}); } - /// Shortcut to set channel name. - /// - /// {@macro name} - set name(String? name) { + set extraData(Map extraData) { if (_initializedCompleter.isCompleted) { throw StateError( - 'Once the channel is initialized you should use channel.updateName ' - 'to update the channel name', + 'Once the channel is initialized you should use `channel.update` ' + 'to update channel data', ); } - _extraData.addAll({'name': name}); + _extraData.addAll(extraData); } /// Returns true if the channel is muted. @@ -301,23 +301,6 @@ class Channel { ); } - /// Shortcut to get channel image. - /// - /// {@macro image} - String? get image => extraData['image'] as String?; - - /// Channel [image] as a stream. - /// - /// The channel needs to be initialized. - /// - /// {@macro image} - Stream get imageStream { - _checkInitialized(); - return state!.channelStateStream.map( - (cs) => (cs.channel?.extraData['image'] as String?) ?? image, - ); - } - /// Shortcut to get channel name. /// /// If no name is set this returns the channel cid, else null. @@ -342,15 +325,34 @@ class Channel { _checkInitialized(); return state!.channelStateStream.map( (cs) { - if (cs.channel?.extraData.containsKey('name') ?? false) { - final name = cs.channel!.extraData['name']! as String; + final extraData = cs.channel?.extraData; + if (extraData != null && extraData.containsKey('name')) { + final name = extraData['name']! as String; if (name.isNotEmpty) return name; } + // this can never be null once the channel is initialized return name!; }, ); } + /// Shortcut to get channel image. + /// + /// {@macro image} + String? get image => extraData['image'] as String?; + + /// Channel [image] as a stream. + /// + /// The channel needs to be initialized. + /// + /// {@macro image} + Stream get imageStream { + _checkInitialized(); + return state!.channelStateStream.map( + (cs) => (cs.channel?.extraData['image'] as String?) ?? image, + ); + } + /// The main Stream chat client. StreamChatClient get client => _client; final StreamChatClient _client; @@ -932,26 +934,6 @@ class Channel { } } - /// Update the channel's [image]. - /// - /// This is the same as calling [updatePartial] and providing a map with an - /// 'image' key: - /// - /// ```dart - /// channel.updatePartial( - /// set: {'image': 'https://getstream.io/new-image'} - /// ); - /// ``` - /// - /// Instead do: - /// ```dart - /// channel.updateImage('https://getstream.io/new-image'); - /// ``` - Future updateImage( - String image, - ) => - updatePartial(set: {'image': image}); - /// Update the channel's [name]. /// /// This is the same as calling [updatePartial] and providing a map with a @@ -967,11 +949,27 @@ class Channel { /// ```dart /// channel.updateName('Updated channel name'); /// ``` - Future updateName( - String name, - ) => + Future updateName(String name) => updatePartial(set: {'name': name}); + /// Update the channel's [image]. + /// + /// This is the same as calling [updatePartial] and providing a map with an + /// 'image' key: + /// + /// ```dart + /// channel.updatePartial( + /// set: {'image': 'https://getstream.io/new-image'} + /// ); + /// ``` + /// + /// Instead do: + /// ```dart + /// channel.updateImage('https://getstream.io/new-image'); + /// ``` + Future updateImage(String image) => + updatePartial(set: {'image': image}); + /// Update the channel custom data. This replaces all of the channel data /// with the given [channelData]. /// diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 1166405a..6b3dfda2 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -39,6 +39,7 @@ void main() { late final client = MockStreamChatClient(); const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; + const channelCid = '$channelType:$channelId'; late Channel channel; setUpAll(() { @@ -94,7 +95,7 @@ void main() { expect(channel.extraData.isEmpty, isTrue); expect( channel.name, - channelId, + channelCid, reason: 'if name is not set then use channel id', ); @@ -1256,24 +1257,23 @@ void main() { ); when(() => client.updateChannelPartial( - any(), - any(), + channelId, + channelType, set: {'image': image}, )).thenAnswer( (_) async => PartialUpdateChannelResponse()..channel = channelModel, ); + final res = await channel.updateImage(image); expect(res, isNotNull); expect(res.channel.extraData['image'], image); - verify( - () => client.updateChannelPartial( - any(), - any(), - set: {'image': image}, - ), - ).called(1); + verify(() => client.updateChannelPartial( + channelId, + channelType, + set: {'image': image}, + )).called(1); }); test('`.updateName`', () async { @@ -1285,24 +1285,23 @@ void main() { ); when(() => client.updateChannelPartial( - any(), - any(), + channelId, + channelType, set: {'name': name}, )).thenAnswer( (_) async => PartialUpdateChannelResponse()..channel = channelModel, ); + final res = await channel.updateName(name); expect(res, isNotNull); expect(res.channel.extraData['name'], name); - verify( - () => client.updateChannelPartial( - any(), - any(), - set: {'name': name}, - ), - ).called(1); + verify(() => client.updateChannelPartial( + channelId, + channelType, + set: {'name': name}, + )).called(1); }); test('`.updatePartial`', () async { From 702f7ee9379f20e6e9b5bb6069aac4ddec31b70d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 11 Aug 2021 15:00:05 +0530 Subject: [PATCH 11/11] fix(llc): don't return `cid` in case `name` is null, minor improvements Signed-off-by: xsahil03x --- .../stream_chat/lib/src/client/channel.dart | 30 +++---------------- .../test/src/api/channel_test.dart | 6 ---- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 5eee8060..b22a57cc 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -303,37 +303,17 @@ class Channel { /// Shortcut to get channel name. /// - /// If no name is set this returns the channel cid, else null. - /// /// {@macro name} - String? get name { - if (extraData.containsKey('name')) { - final name = extraData['name']! as String; - if (name.isNotEmpty) return name; - } - return cid; - } + String? get name => extraData['name'] as String?; /// Channel [name] as a stream. /// - /// If no name is set the stream returns the channel cid. - /// /// The channel needs to be initialized. /// /// {@macro name} - Stream get nameStream { + Stream get nameStream { _checkInitialized(); - return state!.channelStateStream.map( - (cs) { - final extraData = cs.channel?.extraData; - if (extraData != null && extraData.containsKey('name')) { - final name = extraData['name']! as String; - if (name.isNotEmpty) return name; - } - // this can never be null once the channel is initialized - return name!; - }, - ); + return extraDataStream.map((it) => it['name'] as String?); } /// Shortcut to get channel image. @@ -348,9 +328,7 @@ class Channel { /// {@macro image} Stream get imageStream { _checkInitialized(); - return state!.channelStateStream.map( - (cs) => (cs.channel?.extraData['image'] as String?) ?? image, - ); + return extraDataStream.map((it) => it['image'] as String?); } /// The main Stream chat client. diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 6b3dfda2..7c5386c6 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -39,7 +39,6 @@ void main() { late final client = MockStreamChatClient(); const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; - const channelCid = '$channelType:$channelId'; late Channel channel; setUpAll(() { @@ -93,11 +92,6 @@ void main() { test('should be able to get and set `name`', () { expect(channel.extraData.isEmpty, isTrue); - expect( - channel.name, - channelCid, - reason: 'if name is not set then use channel id', - ); const name = 'Channel name'; channel.name = name;