feat: add image get, set and update to channel

This commit is contained in:
Gordon Hayes
2021-08-10 09:59:08 +02:00
parent aa223ce258
commit eae7cb1020
2 changed files with 155 additions and 9 deletions
@@ -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/src/event_type.dart';
import 'package:stream_chat/stream_chat.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 { 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( Channel(
this._client, this._client,
this._type, this._type,
this._id, { this._id, {
Map<String, Object?>? extraData, Map<String, Object?>? extraData,
String? image,
}) : _cid = _id != null ? '$_type:$_id' : null, }) : _cid = _id != null ? '$_type:$_id' : null,
_extraData = extraData ?? {} { _extraData = {
_client.logger.info('New Channel instance not initialized created'); ...?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) Channel.fromState(this._client, ChannelState channelState)
: assert( : assert(
channelState.channel != null, channelState.channel != null,
@@ -40,7 +69,7 @@ class Channel {
_extraData = channelState.channel!.extraData { _extraData = channelState.channel!.extraData {
state = ChannelClientState(this, channelState); state = ChannelClientState(this, channelState);
_initializedCompleter.complete(true); _initializedCompleter.complete(true);
_client.logger.info('New Channel instance initialized created'); _client.logger.info('New Channel instance initialized');
} }
/// This client state /// This client state
@@ -63,6 +92,19 @@ class Channel {
_extraData.addAll(extraData); _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. /// Returns true if the channel is muted.
bool get isMuted => bool get isMuted =>
_client.state.currentUser?.channelMutes _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<String?> 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; StreamChatClient get client => _client;
final StreamChatClient _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<PartialUpdateChannelResponse> updateImage(
String image,
) {
_checkInitialized();
return _client.updateChannelPartial(
id!,
type,
set: {
'image': image,
},
);
}
/// Edit the channel custom data.
Future<UpdateChannelResponse> update( Future<UpdateChannelResponse> update(
Map<String, Object?> channelData, [ Map<String, Object?> channelData, [
Message? updateMessage, 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<PartialUpdateChannelResponse> updatePartial({ Future<PartialUpdateChannelResponse> updatePartial({
Map<String, Object?>? set, Map<String, Object?>? set,
List<String>? unset, List<String>? unset,
@@ -72,6 +72,23 @@ void main() {
expect(channel.extraData.containsKey('name'), isTrue); expect(channel.extraData.containsKey('name'), isTrue);
expect(channel.extraData['name'], 'test-channel-name'); 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 // 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<StateError>());
}
});
group('`.sendMessage`', () { group('`.sendMessage`', () {
test('should work fine', () async { test('should work fine', () async {
final message = Message(id: 'test-message-id'); final message = Message(id: 'test-message-id');
@@ -1192,6 +1217,35 @@ void main() {
message: any(named: 'message'))).called(1); 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 { test('`.updatePartial`', () async {
const set = { const set = {
'name': 'Stream Team', 'name': 'Stream Team',