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; - } - } -}