From eae7cb10206264bfd1570dcf96f3d998910d41f6 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 10 Aug 2021 09:59:08 +0200 Subject: [PATCH] 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',