Merge branch 'develop' of https://github.com/GetStream/stream-chat-flutter into cds-189
Conflicts: packages/stream_chat/lib/src/client/channel.dart packages/stream_chat_flutter/example/lib/main.dart packages/stream_chat_flutter/lib/src/message_list_view.dart
This commit is contained in:
@@ -1,3 +1,15 @@
|
||||
## Upcoming
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- Fixed `channel.markAllRead` throwing failed host lookup.
|
||||
|
||||
✅ Added
|
||||
|
||||
- `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
|
||||
|
||||
@@ -13,10 +13,9 @@ Future<void> main() async {
|
||||
await client.connectUser(
|
||||
User(
|
||||
id: 'cool-shadow-7',
|
||||
extraData: const {
|
||||
'image':
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||
},
|
||||
name: 'Cool Shadow',
|
||||
image:
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||
),
|
||||
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''',
|
||||
);
|
||||
|
||||
@@ -15,20 +15,77 @@ 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.
|
||||
///
|
||||
/// #### 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'.
|
||||
///
|
||||
/// ```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, {
|
||||
String? name,
|
||||
String? image,
|
||||
Map<String, Object?>? extraData,
|
||||
}) : _cid = _id != null ? '$_type:$_id' : null,
|
||||
_extraData = extraData ?? {} {
|
||||
_client.logger.info('New Channel instance not initialized created');
|
||||
_extraData = {
|
||||
...?extraData,
|
||||
if (name != null) 'name': name,
|
||||
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 +97,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
|
||||
@@ -53,66 +110,92 @@ class Channel {
|
||||
String? _cid;
|
||||
final Map<String, Object?> _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.updateName` '
|
||||
'to update the channel name',
|
||||
);
|
||||
}
|
||||
_extraData.addAll({'name': name});
|
||||
}
|
||||
|
||||
/// 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.updateImage` '
|
||||
'to update the channel image',
|
||||
);
|
||||
}
|
||||
_extraData.addAll({'image': image});
|
||||
}
|
||||
|
||||
set extraData(Map<String, Object?> extraData) {
|
||||
if (_initializedCompleter.isCompleted) {
|
||||
throw StateError(
|
||||
'Once the channel is initialized you should use channel.update '
|
||||
'Once the channel is initialized you should use `channel.update` '
|
||||
'to update channel data',
|
||||
);
|
||||
}
|
||||
_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<bool>? 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<ChannelConfig?>? 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<User?>? 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<bool?>? get frozenStream {
|
||||
_checkInitialized();
|
||||
return state?.channelStateStream.map((cs) => cs.channel?.frozen);
|
||||
@@ -133,90 +216,90 @@ class Channel {
|
||||
///
|
||||
DateTime? cooldownStartedAt;
|
||||
|
||||
/// 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<DateTime?>? 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<DateTime?>? 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<DateTime?>? 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<DateTime?>? 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<int?>? 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<String, Object?> get extraData {
|
||||
var data = state?._channelState.channel?.extraData;
|
||||
if (data == null || data.isEmpty) {
|
||||
@@ -225,7 +308,7 @@ class Channel {
|
||||
return data;
|
||||
}
|
||||
|
||||
/// Channel extra data as a stream
|
||||
/// Channel extra data as a stream.
|
||||
Stream<Map<String, dynamic>> get extraDataStream {
|
||||
_checkInitialized();
|
||||
return state!.channelStateStream.map(
|
||||
@@ -233,15 +316,46 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// The main Stream chat client
|
||||
/// Shortcut to get channel name.
|
||||
///
|
||||
/// {@macro name}
|
||||
String? get name => extraData['name'] as String?;
|
||||
|
||||
/// Channel [name] as a stream.
|
||||
///
|
||||
/// The channel needs to be initialized.
|
||||
///
|
||||
/// {@macro name}
|
||||
Stream<String?> get nameStream {
|
||||
_checkInitialized();
|
||||
return extraDataStream.map((it) => it['name'] as String?);
|
||||
}
|
||||
|
||||
/// 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 extraDataStream.map((it) => it['image'] as String?);
|
||||
}
|
||||
|
||||
/// The main Stream chat client.
|
||||
StreamChatClient get client => _client;
|
||||
final StreamChatClient _client;
|
||||
|
||||
final Completer<bool> _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<bool> get initialized => _initializedCompleter.future;
|
||||
|
||||
final _cancelableAttachmentUploadRequest = <String, CancelToken>{};
|
||||
@@ -377,7 +491,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<SendMessageResponse> sendMessage(
|
||||
@@ -445,6 +561,7 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Updates the [message] in this channel.
|
||||
///
|
||||
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
||||
/// before actually updating the message.
|
||||
Future<UpdateMessageResponse> updateMessage(Message message) async {
|
||||
@@ -507,8 +624,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<UpdateMessageResponse> partialUpdateMessage(
|
||||
Message message, {
|
||||
Map<String, Object?>? set,
|
||||
@@ -608,7 +727,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Unpins provided message
|
||||
/// Unpins provided message.
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) =>
|
||||
partialUpdateMessage(
|
||||
message,
|
||||
@@ -617,7 +736,7 @@ class Channel {
|
||||
},
|
||||
);
|
||||
|
||||
/// Send a file to this channel
|
||||
/// Send a file to this channel.
|
||||
Future<SendFileResponse> sendFile(
|
||||
AttachmentFile file, {
|
||||
ProgressCallback? onSendProgress,
|
||||
@@ -633,7 +752,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Send an image to this channel
|
||||
/// Send an image to this channel.
|
||||
Future<SendImageResponse> sendImage(
|
||||
AttachmentFile file, {
|
||||
ProgressCallback? onSendProgress,
|
||||
@@ -649,7 +768,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// A message search.
|
||||
/// Search for a message with the given options.
|
||||
Future<SearchMessagesResponse> search({
|
||||
String? query,
|
||||
Filter? messageFilters,
|
||||
@@ -666,7 +785,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Delete a file from this channel
|
||||
/// Delete a file from this channel.
|
||||
Future<EmptyResponse> deleteFile(
|
||||
String url, {
|
||||
CancelToken? cancelToken,
|
||||
@@ -680,7 +799,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Delete an image from this channel
|
||||
/// Delete an image from this channel.
|
||||
Future<EmptyResponse> deleteImage(
|
||||
String url, {
|
||||
CancelToken? cancelToken,
|
||||
@@ -694,14 +813,15 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Send an event on this channel
|
||||
/// Send an event on this channel.
|
||||
Future<EmptyResponse> 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<SendReactionResponse> sendReaction(
|
||||
Message message,
|
||||
String type, {
|
||||
@@ -764,7 +884,7 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a reaction from this channel
|
||||
/// Delete a reaction from this channel.
|
||||
Future<EmptyResponse> deleteReaction(
|
||||
Message message, Reaction reaction) async {
|
||||
final type = reaction.type;
|
||||
@@ -810,7 +930,49 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Edit the channel custom data
|
||||
/// 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<PartialUpdateChannelResponse> 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<PartialUpdateChannelResponse> updateImage(String image) =>
|
||||
updatePartial(set: {'image': image});
|
||||
|
||||
/// 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<UpdateChannelResponse> update(
|
||||
Map<String, Object?> channelData, [
|
||||
Message? updateMessage,
|
||||
@@ -824,7 +986,18 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Edit the channel custom data
|
||||
/// 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<PartialUpdateChannelResponse> updatePartial({
|
||||
Map<String, Object?>? set,
|
||||
List<String>? unset,
|
||||
@@ -853,25 +1026,25 @@ class Channel {
|
||||
return _client.deleteChannel(id!, type);
|
||||
}
|
||||
|
||||
/// Removes all messages from the channel
|
||||
/// Removes all messages from the channel.
|
||||
Future<EmptyResponse> truncate() async {
|
||||
_checkInitialized();
|
||||
return _client.truncateChannel(id!, type);
|
||||
}
|
||||
|
||||
/// Accept invitation to the channel
|
||||
/// Accept invitation to the channel.
|
||||
Future<AcceptInviteResponse> acceptInvite([Message? message]) async {
|
||||
_checkInitialized();
|
||||
return _client.acceptChannelInvite(id!, type, message: message);
|
||||
}
|
||||
|
||||
/// Reject invitation to the channel
|
||||
/// Reject invitation to the channel.
|
||||
Future<RejectInviteResponse> rejectInvite([Message? message]) async {
|
||||
_checkInitialized();
|
||||
return _client.rejectChannelInvite(id!, type, message: message);
|
||||
}
|
||||
|
||||
/// Add members to the channel
|
||||
/// Add members to the channel.
|
||||
Future<AddMembersResponse> addMembers(
|
||||
List<String> memberIds, [
|
||||
Message? message,
|
||||
@@ -880,7 +1053,7 @@ class Channel {
|
||||
return _client.addChannelMembers(id!, type, memberIds, message: message);
|
||||
}
|
||||
|
||||
/// Invite members to the channel
|
||||
/// Invite members to the channel.
|
||||
Future<InviteMembersResponse> inviteMembers(
|
||||
List<String> memberIds, [
|
||||
Message? message,
|
||||
@@ -889,7 +1062,7 @@ class Channel {
|
||||
return _client.inviteChannelMembers(id!, type, memberIds, message: message);
|
||||
}
|
||||
|
||||
/// Remove members from the channel
|
||||
/// Remove members from the channel.
|
||||
Future<RemoveMembersResponse> removeMembers(
|
||||
List<String> memberIds, [
|
||||
Message? message,
|
||||
@@ -898,7 +1071,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<SendActionResponse> sendAction(
|
||||
Message message,
|
||||
Map<String, dynamic> formData,
|
||||
@@ -945,9 +1118,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<EmptyResponse> markRead({String? messageId}) async {
|
||||
_checkInitialized();
|
||||
client.state.totalUnreadCount =
|
||||
@@ -956,7 +1130,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<ChannelState> watch() async {
|
||||
ChannelState response;
|
||||
|
||||
@@ -987,15 +1161,16 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop watching the channel
|
||||
/// Stop watching the channel.
|
||||
Future<EmptyResponse> 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<QueryRepliesResponse> getReplies(
|
||||
String parentId, {
|
||||
PaginationParams? options,
|
||||
@@ -1019,7 +1194,7 @@ class Channel {
|
||||
return repliesResponse;
|
||||
}
|
||||
|
||||
/// List the reactions for a message in the channel
|
||||
/// List the reactions for a message in the channel.
|
||||
Future<QueryReactionsResponse> getReactions(
|
||||
String messageId, {
|
||||
PaginationParams? pagination,
|
||||
@@ -1029,7 +1204,7 @@ class Channel {
|
||||
pagination: pagination,
|
||||
);
|
||||
|
||||
/// Retrieves a list of messages by ID
|
||||
/// Retrieves a list of messages by given [messageIDs].
|
||||
Future<GetMessagesByIdResponse> getMessagesById(
|
||||
List<String> messageIDs,
|
||||
) async {
|
||||
@@ -1040,7 +1215,7 @@ class Channel {
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Retrieves a list of messages by ID
|
||||
/// Translate a message by given [messageId] and [language].
|
||||
Future<TranslateMessageResponse> translateMessage(
|
||||
String messageId,
|
||||
String language,
|
||||
@@ -1050,12 +1225,13 @@ class Channel {
|
||||
language,
|
||||
);
|
||||
|
||||
/// Creates a new channel
|
||||
/// Creates a new channel.
|
||||
Future<ChannelState> 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<ChannelState> query({
|
||||
bool state = true,
|
||||
bool watch = false,
|
||||
@@ -1109,7 +1285,7 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Query channel members
|
||||
/// Query channel members.
|
||||
Future<QueryMembersResponse> queryMembers({
|
||||
Filter? filter,
|
||||
List<SortOption>? sort,
|
||||
@@ -1124,19 +1300,19 @@ class Channel {
|
||||
pagination: pagination,
|
||||
);
|
||||
|
||||
/// Mutes the channel
|
||||
/// Mutes the channel.
|
||||
Future<EmptyResponse> mute({Duration? expiration}) {
|
||||
_checkInitialized();
|
||||
return _client.muteChannel(cid!, expiration: expiration);
|
||||
}
|
||||
|
||||
/// Unmutes the channel
|
||||
/// Unmute the channel.
|
||||
Future<EmptyResponse> unmute() {
|
||||
_checkInitialized();
|
||||
return _client.unmuteChannel(cid!);
|
||||
}
|
||||
|
||||
/// Bans a user from the channel
|
||||
/// Bans the user with given [userID] from the channel.
|
||||
Future<EmptyResponse> banUser(
|
||||
String userID,
|
||||
Map<String, dynamic> options,
|
||||
@@ -1150,7 +1326,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<EmptyResponse> unbanUser(String userID) async {
|
||||
_checkInitialized();
|
||||
return _client.unbanUser(userID, {
|
||||
@@ -1159,7 +1335,7 @@ class Channel {
|
||||
});
|
||||
}
|
||||
|
||||
/// Shadow bans a user from the channel
|
||||
/// Shadow bans the user with the given [userID] from the channel.
|
||||
Future<EmptyResponse> shadowBan(
|
||||
String userID,
|
||||
Map<String, dynamic> options,
|
||||
@@ -1173,7 +1349,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<EmptyResponse> removeShadowBan(String userID) async {
|
||||
_checkInitialized();
|
||||
return _client.removeShadowBan(userID, {
|
||||
@@ -1183,8 +1359,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<EmptyResponse> hide({bool clearHistory = false}) async {
|
||||
_checkInitialized();
|
||||
final response = await _client.hideChannel(
|
||||
@@ -1202,7 +1380,7 @@ class Channel {
|
||||
return response;
|
||||
}
|
||||
|
||||
/// Removes the hidden status for the channel
|
||||
/// Removes the hidden status for the channel.
|
||||
Future<EmptyResponse> show() async {
|
||||
_checkInitialized();
|
||||
return _client.showChannel(id!, type);
|
||||
@@ -1210,7 +1388,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<Event> on([
|
||||
String? eventType,
|
||||
String? eventType2,
|
||||
@@ -1248,7 +1426,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<void> stopTyping([String? parentId]) async {
|
||||
if (config?.typingEvents == false) {
|
||||
return;
|
||||
@@ -1262,7 +1440,7 @@ class Channel {
|
||||
));
|
||||
}
|
||||
|
||||
/// Call this method to dispose the channel client
|
||||
/// Call this method to dispose the channel client.
|
||||
void dispose() {
|
||||
state?.dispose();
|
||||
}
|
||||
@@ -1276,9 +1454,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,
|
||||
@@ -1425,23 +1603,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<bool> get isUpToDateStream => _isUpToDateController.stream;
|
||||
|
||||
final BehaviorSubject<bool> _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<void> retryFailedMessages() async {
|
||||
final failedMessages =
|
||||
<Message>[...messages, ...threads.values.expand((v) => v)]
|
||||
@@ -1534,7 +1714,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<Message>.from(_channelState.messages);
|
||||
@@ -1599,36 +1779,36 @@ class ChannelClientState {
|
||||
);
|
||||
}
|
||||
|
||||
/// Channel message list
|
||||
/// Channel message list.
|
||||
List<Message> get messages => _channelState.messages;
|
||||
|
||||
/// Channel message list as a stream
|
||||
/// Channel message list as a stream.
|
||||
Stream<List<Message>?> get messagesStream => channelStateStream
|
||||
.map((cs) => cs.messages)
|
||||
.distinct(const ListEquality().equals);
|
||||
|
||||
/// Channel pinned message list
|
||||
/// Channel pinned message list.
|
||||
List<Message>? get pinnedMessages => _channelState.pinnedMessages.toList();
|
||||
|
||||
/// Channel pinned message list as a stream
|
||||
/// Channel pinned message list as a stream.
|
||||
Stream<List<Message>?> 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<Message?> get lastMessageStream => messagesStream
|
||||
.map((event) => event?.isNotEmpty == true ? event!.last : null);
|
||||
|
||||
/// Channel members list
|
||||
/// Channel members list.
|
||||
List<Member> 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<List<Member>> get membersStream => CombineLatestStream.combine2<
|
||||
List<Member?>?, Map<String?, User?>, List<Member>>(
|
||||
channelStateStream.map((cs) => cs.members),
|
||||
@@ -1637,19 +1817,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<int?> get watcherCountStream =>
|
||||
channelStateStream.map((cs) => cs.watcherCount);
|
||||
|
||||
/// Channel watchers list
|
||||
/// Channel watchers list.
|
||||
List<User> 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<List<User>> get watchersStream => CombineLatestStream.combine2<
|
||||
List<User>?, Map<String?, User?>, List<User>>(
|
||||
channelStateStream.map((cs) => cs.watchers),
|
||||
@@ -1657,20 +1837,20 @@ class ChannelClientState {
|
||||
(watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(),
|
||||
);
|
||||
|
||||
/// Channel read list
|
||||
/// Channel read list.
|
||||
List<Read>? get read => _channelState.read;
|
||||
|
||||
/// Channel read list as a stream
|
||||
/// Channel read list as a stream.
|
||||
Stream<List<Read>?> get readStream => channelStateStream.map((cs) => cs.read);
|
||||
|
||||
final BehaviorSubject<int> _unreadCountController = BehaviorSubject.seeded(0);
|
||||
|
||||
set unreadCount(int value) => _unreadCountController.add(value);
|
||||
|
||||
/// Unread count getter as a stream
|
||||
/// Unread count getter as a stream.
|
||||
Stream<int> get unreadCountStream => _unreadCountController.stream.distinct();
|
||||
|
||||
/// Unread count getter
|
||||
/// Unread count getter.
|
||||
int get unreadCount => _unreadCountController.value;
|
||||
|
||||
bool _countMessageAsUnread(Message message) {
|
||||
@@ -1686,7 +1866,7 @@ class ChannelClientState {
|
||||
!userIsMuted;
|
||||
}
|
||||
|
||||
/// Update threads with updated information about messages
|
||||
/// Update threads with updated information about messages.
|
||||
void updateThreadInfo(String parentId, List<Message> messages) {
|
||||
final newThreads = Map<String, List<Message>>.from(threads);
|
||||
|
||||
@@ -1708,7 +1888,7 @@ class ChannelClientState {
|
||||
_threads = newThreads;
|
||||
}
|
||||
|
||||
/// Delete all channel messages
|
||||
/// Delete all channel messages.
|
||||
void truncate() {
|
||||
_channelState = _channelState.copyWith(
|
||||
messages: [],
|
||||
@@ -1717,7 +1897,7 @@ class ChannelClientState {
|
||||
|
||||
final List<String> _updatedMessagesIds = [];
|
||||
|
||||
/// Update channelState with updated information
|
||||
/// Update channelState with updated information.
|
||||
void updateChannelState(ChannelState updatedState) {
|
||||
final newMessages = <Message>[
|
||||
...updatedState.messages,
|
||||
@@ -1769,13 +1949,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<ChannelState> 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<ChannelState> _channelStateController;
|
||||
|
||||
@@ -1786,11 +1966,11 @@ class ChannelClientState {
|
||||
_debouncedUpdatePersistenceChannelState.call([v]);
|
||||
}
|
||||
|
||||
/// The channel threads related to this channel
|
||||
/// The channel threads related to this channel.
|
||||
Map<String, List<Message>> 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<Map<String, List<Message>>> get threadsStream =>
|
||||
_threadsController.stream;
|
||||
final BehaviorSubject<Map<String, List<Message>>> _threadsController =
|
||||
@@ -1804,10 +1984,10 @@ class ChannelClientState {
|
||||
_threadsController.add(v);
|
||||
}
|
||||
|
||||
/// Channel related typing users last value
|
||||
/// Channel related typing users last value.
|
||||
Map<User, Event> get typingEvents => _typingEventsController.value;
|
||||
|
||||
/// Channel related typing users stream
|
||||
/// Channel related typing users stream.
|
||||
Stream<Map<User, Event>> get typingEventsStream =>
|
||||
_typingEventsController.stream;
|
||||
|
||||
@@ -1935,7 +2115,7 @@ class ChannelClientState {
|
||||
});
|
||||
}
|
||||
|
||||
/// Call this method to dispose this object
|
||||
/// Call this method to dispose this object.
|
||||
void dispose() {
|
||||
_debouncedUpdatePersistenceChannelState.cancel();
|
||||
_unreadCountController.close();
|
||||
|
||||
@@ -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<UpdateChannelResponse> 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<PartialUpdateChannelResponse> updateChannelPartial(
|
||||
String channelId,
|
||||
String channelType, {
|
||||
|
||||
@@ -84,7 +84,7 @@ class ChannelApi {
|
||||
|
||||
/// Mark all channels for this user as read
|
||||
Future<EmptyResponse> markAllRead() async {
|
||||
final response = await _client.post('channels/read');
|
||||
final response = await _client.post('/channels/read');
|
||||
return EmptyResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
part 'own_user.g.dart';
|
||||
|
||||
/// The class that defines the own user model
|
||||
/// This object can be found in [Event]
|
||||
/// The class that defines the own user model.
|
||||
///
|
||||
/// This object can be found in [Event].
|
||||
@JsonSerializable(createToJson: false)
|
||||
class OwnUser extends User {
|
||||
/// Constructor used for json serialization
|
||||
/// Constructor used for json serialization.
|
||||
OwnUser({
|
||||
this.devices = const [],
|
||||
this.mutes = const [],
|
||||
@@ -20,6 +21,8 @@ class OwnUser extends User {
|
||||
this.channelMutes = const [],
|
||||
required String id,
|
||||
String? role,
|
||||
String? name,
|
||||
String? image,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? lastActive,
|
||||
@@ -31,6 +34,8 @@ class OwnUser extends User {
|
||||
}) : super(
|
||||
id: id,
|
||||
role: role,
|
||||
name: name,
|
||||
image: image,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
lastActive: lastActive,
|
||||
@@ -41,14 +46,16 @@ class OwnUser extends User {
|
||||
language: language,
|
||||
);
|
||||
|
||||
/// Create a new instance from a json
|
||||
/// Create a new instance from json.
|
||||
factory OwnUser.fromJson(Map<String, dynamic> json) => _$OwnUserFromJson(
|
||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
|
||||
|
||||
/// Create a new instance from [User] object
|
||||
/// Create a new instance from [User] object.
|
||||
factory OwnUser.fromUser(User user) => OwnUser(
|
||||
id: user.id,
|
||||
role: user.role,
|
||||
name: user.name,
|
||||
image: user.image,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt,
|
||||
lastActive: user.lastActive,
|
||||
@@ -64,6 +71,8 @@ class OwnUser extends User {
|
||||
OwnUser copyWith({
|
||||
String? id,
|
||||
String? role,
|
||||
String? name,
|
||||
String? image,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? lastActive,
|
||||
@@ -80,8 +89,12 @@ class OwnUser extends User {
|
||||
}) =>
|
||||
OwnUser(
|
||||
id: id ?? this.id,
|
||||
banned: banned ?? this.banned,
|
||||
role: role ?? this.role,
|
||||
/* if null, it will be retrieved from extraData['name']*/
|
||||
name: name,
|
||||
/* if null, it will be retrieved from extraData['image']*/
|
||||
image: image,
|
||||
banned: banned ?? this.banned,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
lastActive: lastActive ?? this.lastActive,
|
||||
@@ -101,16 +114,18 @@ class OwnUser extends User {
|
||||
OwnUser merge(OwnUser? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
id: other.id,
|
||||
role: other.role,
|
||||
name: other.name,
|
||||
image: other.image,
|
||||
banned: other.banned,
|
||||
channelMutes: other.channelMutes,
|
||||
createdAt: other.createdAt,
|
||||
devices: other.devices,
|
||||
extraData: other.extraData,
|
||||
id: other.id,
|
||||
lastActive: other.lastActive,
|
||||
mutes: other.mutes,
|
||||
online: other.online,
|
||||
role: other.role,
|
||||
teams: other.teams,
|
||||
totalUnreadCount: other.totalUnreadCount,
|
||||
unreadChannels: other.unreadChannels,
|
||||
@@ -119,27 +134,28 @@ class OwnUser extends User {
|
||||
);
|
||||
}
|
||||
|
||||
/// List of user devices
|
||||
/// List of user devices.
|
||||
@JsonKey(includeIfNull: false, defaultValue: <Device>[])
|
||||
final List<Device> devices;
|
||||
|
||||
/// List of users muted by the user
|
||||
/// List of users muted by the user.
|
||||
@JsonKey(includeIfNull: false, defaultValue: <Mute>[])
|
||||
final List<Mute> mutes;
|
||||
|
||||
/// List of users muted by the user
|
||||
/// List of users muted by the user.
|
||||
@JsonKey(includeIfNull: false, defaultValue: <Mute>[])
|
||||
final List<Mute> channelMutes;
|
||||
|
||||
/// Total unread messages by the user
|
||||
/// Total unread messages by the user.
|
||||
@JsonKey(includeIfNull: false, defaultValue: 0)
|
||||
final int totalUnreadCount;
|
||||
|
||||
/// Total unread channels by the user
|
||||
/// Total unread channels by the user.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final int? unreadChannels;
|
||||
|
||||
/// Known top level fields.
|
||||
///
|
||||
/// Useful for [Serializer] methods.
|
||||
static final topLevelFields = [
|
||||
'devices',
|
||||
|
||||
@@ -4,29 +4,60 @@ import 'package:stream_chat/src/core/util/serializer.dart';
|
||||
|
||||
part 'user.g.dart';
|
||||
|
||||
/// The class that defines the user model
|
||||
/// Class that defines a Stream Chat User.
|
||||
@JsonSerializable()
|
||||
class User extends Equatable {
|
||||
/// Constructor used for json serialization
|
||||
/// Creates a new user.
|
||||
///
|
||||
/// {@template name}
|
||||
/// If an [name] is provided it will be set on [extraData] with a `key`
|
||||
/// of 'name'.
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// final user = User(id: 'id', name: 'Sahil Kumar');
|
||||
/// print(user.name == user.extraData['name']); // true
|
||||
/// ```
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// {@template image}
|
||||
/// If an [image] is provided it will be set on [extraData] with a `key`
|
||||
/// of 'image'.
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// final user = User(id: 'id', image: 'https://getstream.io/image.png');
|
||||
/// print(user.image == user.extraData['image']); // true
|
||||
/// ```
|
||||
/// {@endtemplate}
|
||||
User({
|
||||
required this.id,
|
||||
this.role,
|
||||
String? name,
|
||||
String? image,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
this.lastActive,
|
||||
Map<String, Object?> extraData = const {},
|
||||
this.online = false,
|
||||
this.extraData = const {},
|
||||
this.banned = false,
|
||||
this.teams = const [],
|
||||
this.language,
|
||||
}) : createdAt = createdAt ?? DateTime.now(),
|
||||
updatedAt = updatedAt ?? DateTime.now();
|
||||
updatedAt = updatedAt ?? DateTime.now(),
|
||||
/*For backwards compatibility, set 'name', 'image' in [extraData].*/
|
||||
extraData = {
|
||||
...extraData,
|
||||
if (name != null) 'name': name,
|
||||
if (image != null) 'image': image,
|
||||
};
|
||||
|
||||
/// Create a new instance from a json
|
||||
/// Create a new instance from json.
|
||||
factory User.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserFromJson(Serializer.moveToExtraDataFromRoot(json, topLevelFields));
|
||||
|
||||
/// Known top level fields.
|
||||
///
|
||||
/// Useful for [Serializer] methods.
|
||||
static const topLevelFields = [
|
||||
'id',
|
||||
@@ -40,57 +71,13 @@ class User extends Equatable {
|
||||
'language',
|
||||
];
|
||||
|
||||
/// User id
|
||||
/// User id.
|
||||
final String id;
|
||||
|
||||
/// User role
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final String? role;
|
||||
|
||||
/// User role
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
toJson: Serializer.readOnly,
|
||||
defaultValue: <String>[],
|
||||
)
|
||||
final List<String> teams;
|
||||
|
||||
/// Date of user creation
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime createdAt;
|
||||
|
||||
/// Date of last user update
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// Date of last user connection
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime? lastActive;
|
||||
|
||||
/// True if user is online
|
||||
@JsonKey(
|
||||
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
|
||||
final bool online;
|
||||
|
||||
/// True if user is banned from the chat
|
||||
@JsonKey(
|
||||
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
|
||||
final bool banned;
|
||||
|
||||
/// Map of custom user extraData
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
defaultValue: {},
|
||||
)
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
/// The language this user prefers.
|
||||
/// Shortcut for user name.
|
||||
///
|
||||
/// Defaults to 'en'.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final String? language;
|
||||
|
||||
/// Shortcut for user name
|
||||
/// {@macro name}
|
||||
@JsonKey(ignore: true)
|
||||
String get name {
|
||||
if (extraData.containsKey('name')) {
|
||||
final name = extraData['name']! as String;
|
||||
@@ -99,11 +86,62 @@ class User extends Equatable {
|
||||
return id;
|
||||
}
|
||||
|
||||
/// List of users to list of userIds
|
||||
/// Shortcut for user image.
|
||||
///
|
||||
/// {@macro image}
|
||||
@JsonKey(ignore: true)
|
||||
String? get image => extraData['image'] as String?;
|
||||
|
||||
/// User role.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final String? role;
|
||||
|
||||
/// User teams
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
toJson: Serializer.readOnly,
|
||||
defaultValue: <String>[],
|
||||
)
|
||||
final List<String> teams;
|
||||
|
||||
/// Date of user creation.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime createdAt;
|
||||
|
||||
/// Date of last user update.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// Date of last user connection.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime? lastActive;
|
||||
|
||||
/// True if user is online.
|
||||
@JsonKey(
|
||||
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
|
||||
final bool online;
|
||||
|
||||
/// True if user is banned from the chat.
|
||||
@JsonKey(
|
||||
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
|
||||
final bool banned;
|
||||
|
||||
/// Map of custom user extraData.
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
defaultValue: {},
|
||||
)
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
/// The language this user prefers.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final String? language;
|
||||
|
||||
/// List of users to list of userIds.
|
||||
static List<String>? toIds(List<User>? users) =>
|
||||
users?.map((u) => u.id).toList();
|
||||
|
||||
/// Serialize to json
|
||||
/// Serialize to json.
|
||||
Map<String, dynamic> toJson() => Serializer.moveFromExtraDataToRoot(
|
||||
_$UserToJson(this),
|
||||
);
|
||||
@@ -112,6 +150,8 @@ class User extends Equatable {
|
||||
User copyWith({
|
||||
String? id,
|
||||
String? role,
|
||||
String? name,
|
||||
String? image,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? lastActive,
|
||||
@@ -124,6 +164,10 @@ class User extends Equatable {
|
||||
User(
|
||||
id: id ?? this.id,
|
||||
role: role ?? this.role,
|
||||
/* if null, it will be retrieved from extraData['name']*/
|
||||
name: name,
|
||||
/* if null, it will be retrieved from extraData['image']*/
|
||||
image: image,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
lastActive: lastActive ?? this.lastActive,
|
||||
@@ -135,5 +179,5 @@ class User extends Equatable {
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
List<Object?> get props => [id, role];
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ User _$UserFromJson(Map<String, dynamic> json) {
|
||||
lastActive: json['last_active'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_active'] as String),
|
||||
online: json['online'] as bool? ?? false,
|
||||
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
|
||||
online: json['online'] as bool? ?? false,
|
||||
banned: json['banned'] as bool? ?? false,
|
||||
teams:
|
||||
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||
|
||||
+11
@@ -2,5 +2,16 @@
|
||||
"id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e",
|
||||
"role": "test-role",
|
||||
"name": "John",
|
||||
"image": "https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow",
|
||||
"extraDataStringTest": "Extra data test",
|
||||
"extraDataIntTest": 1,
|
||||
"extraDataDoubleTest": 1.1,
|
||||
"extraDataBoolTest": true,
|
||||
"banned": true,
|
||||
"online": true,
|
||||
"teams": ["team-1", "team-2"],
|
||||
"created_at": "2021-08-03 12:39:21.817646",
|
||||
"updated_at": "2021-08-04 12:39:21.817646",
|
||||
"last_active" : "2021-08-05 12:39:21.817646",
|
||||
"language": "en"
|
||||
}
|
||||
@@ -72,6 +72,40 @@ 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);
|
||||
});
|
||||
|
||||
test('should be able to get and set `name`', () {
|
||||
expect(channel.extraData.isEmpty, isTrue);
|
||||
|
||||
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
|
||||
@@ -192,6 +226,22 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
test('should throw if trying to set `image`', () {
|
||||
try {
|
||||
channel.image = 'https://stream.io/some-image';
|
||||
} catch (e) {
|
||||
expect(e, isA<StateError>());
|
||||
}
|
||||
});
|
||||
|
||||
test('should throw if trying to set `name`', () {
|
||||
try {
|
||||
channel.name = 'New name';
|
||||
} catch (e) {
|
||||
expect(e, isA<StateError>());
|
||||
}
|
||||
});
|
||||
|
||||
group('`.sendMessage`', () {
|
||||
test('should work fine', () async {
|
||||
final message = Message(id: 'test-message-id');
|
||||
@@ -1192,6 +1242,62 @@ 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(
|
||||
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(
|
||||
channelId,
|
||||
channelType,
|
||||
set: {'image': image},
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('`.updateName`', () async {
|
||||
const name = 'Name';
|
||||
|
||||
final channelModel = ChannelModel(
|
||||
cid: channelCid,
|
||||
extraData: {'name': name},
|
||||
);
|
||||
|
||||
when(() => client.updateChannelPartial(
|
||||
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(
|
||||
channelId,
|
||||
channelType,
|
||||
set: {'name': name},
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('`.updatePartial`', () async {
|
||||
const set = {
|
||||
'name': 'Stream Team',
|
||||
|
||||
@@ -176,7 +176,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('markAllRead', () async {
|
||||
const path = 'channels/read';
|
||||
const path = '/channels/read';
|
||||
when(() => client.post(path)).thenAnswer(
|
||||
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:stream_chat/src/core/models/own_user.dart';
|
||||
import 'package:stream_chat/src/core/models/user.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../utils.dart';
|
||||
|
||||
class MockMute extends Mock implements Mute {}
|
||||
|
||||
class MockDevice extends Mock implements Device {}
|
||||
|
||||
void main() {
|
||||
final devices = [MockDevice(), MockDevice()];
|
||||
final mutes = [MockMute(), MockMute()];
|
||||
final channelMutes = [MockMute()];
|
||||
final createdAt = DateTime.parse('2021-05-03 12:39:21.817646');
|
||||
final updatedAt = DateTime.parse('2021-04-03 12:39:21.817646');
|
||||
final lastActive = DateTime.parse('2021-03-03 12:39:21.817646');
|
||||
|
||||
group('src/models/own_user', () {
|
||||
test('should parse json correctly', () {
|
||||
final ownUser = OwnUser.fromJson(jsonFixture('own_user.json'));
|
||||
@@ -22,6 +35,7 @@ void main() {
|
||||
expect(ownUser.channelMutes.length, 1);
|
||||
expect(ownUser.totalUnreadCount, 0);
|
||||
expect(ownUser.unreadChannels, 0);
|
||||
expect(ownUser.image, 'https://placehold.jp/150x150.png');
|
||||
expect(ownUser.extraData['image'], 'https://placehold.jp/150x150.png');
|
||||
expect(ownUser.extraData['name'], 'Proud darkness');
|
||||
expect(ownUser.extraData['username'], 'Rioland');
|
||||
@@ -40,6 +54,7 @@ void main() {
|
||||
expect(ownUser.online, user.online);
|
||||
expect(ownUser.banned, user.banned);
|
||||
expect(ownUser.extraData, user.extraData);
|
||||
expect(ownUser.image, user.image);
|
||||
});
|
||||
|
||||
test('copyWith', () {
|
||||
@@ -49,35 +64,116 @@ void main() {
|
||||
expect(newUser.id, user.id);
|
||||
expect(newUser.role, user.role);
|
||||
expect(newUser.name, user.name);
|
||||
expect(newUser.devices, user.devices);
|
||||
expect(newUser.mutes, user.mutes);
|
||||
expect(newUser.totalUnreadCount, user.totalUnreadCount);
|
||||
expect(newUser.channelMutes, user.channelMutes);
|
||||
expect(newUser.createdAt, user.createdAt);
|
||||
expect(newUser.updatedAt, user.updatedAt);
|
||||
expect(newUser.lastActive, user.lastActive);
|
||||
expect(newUser.online, user.online);
|
||||
expect(newUser.extraData, user.extraData);
|
||||
expect(newUser.banned, user.banned);
|
||||
expect(newUser.teams, user.teams);
|
||||
expect(newUser.language, user.language);
|
||||
expect(newUser.image, user.image);
|
||||
|
||||
newUser = user.copyWith(
|
||||
id: 'test',
|
||||
role: 'test',
|
||||
image: 'https://getstream.io/image-new',
|
||||
extraData: {
|
||||
'name': 'test',
|
||||
},
|
||||
devices: devices,
|
||||
mutes: mutes,
|
||||
totalUnreadCount: 10,
|
||||
unreadChannels: 5,
|
||||
channelMutes: channelMutes,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
lastActive: lastActive,
|
||||
online: true,
|
||||
banned: true,
|
||||
teams: ['team1', 'team2'],
|
||||
language: 'fr',
|
||||
);
|
||||
|
||||
expect(newUser.id, 'test');
|
||||
expect(newUser.role, 'test');
|
||||
expect(newUser.name, 'test');
|
||||
expect(newUser.image, 'https://getstream.io/image-new');
|
||||
expect(
|
||||
newUser.extraData,
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'https://getstream.io/image-new',
|
||||
},
|
||||
reason: 'Should get image from user.image',
|
||||
);
|
||||
expect(newUser.devices, devices);
|
||||
expect(newUser.mutes, mutes);
|
||||
expect(newUser.totalUnreadCount, 10);
|
||||
expect(newUser.unreadChannels, 5);
|
||||
expect(newUser.channelMutes, channelMutes);
|
||||
expect(newUser.createdAt, createdAt);
|
||||
expect(newUser.updatedAt, updatedAt);
|
||||
expect(newUser.createdAt, createdAt);
|
||||
expect(newUser.online, true);
|
||||
expect(newUser.banned, true);
|
||||
expect(newUser.teams, ['team1', 'team2']);
|
||||
expect(newUser.language, 'fr');
|
||||
});
|
||||
|
||||
test('merge', () {
|
||||
final user = OwnUser.fromJson(jsonFixture('own_user.json'));
|
||||
final newUser = user.merge(OwnUser(
|
||||
id: 'test',
|
||||
role: 'test',
|
||||
extraData: const {
|
||||
'name': 'test',
|
||||
},
|
||||
banned: true,
|
||||
));
|
||||
final newUser = user.merge(
|
||||
OwnUser(
|
||||
id: 'test',
|
||||
role: 'test',
|
||||
extraData: const {
|
||||
'name': 'test',
|
||||
},
|
||||
image: 'https://getstream.io/image-new',
|
||||
devices: devices,
|
||||
mutes: mutes,
|
||||
totalUnreadCount: 10,
|
||||
unreadChannels: 5,
|
||||
channelMutes: channelMutes,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
lastActive: lastActive,
|
||||
online: true,
|
||||
banned: true,
|
||||
teams: const ['team1', 'team2'],
|
||||
language: 'fr',
|
||||
),
|
||||
);
|
||||
|
||||
expect(newUser.id, 'test');
|
||||
expect(newUser.role, 'test');
|
||||
expect(newUser.name, 'test');
|
||||
expect(newUser.image, 'https://getstream.io/image-new');
|
||||
expect(
|
||||
newUser.extraData,
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'https://getstream.io/image-new',
|
||||
},
|
||||
reason: 'Should get image from user.image',
|
||||
);
|
||||
expect(newUser.devices, devices);
|
||||
expect(newUser.mutes, mutes);
|
||||
expect(newUser.totalUnreadCount, 10);
|
||||
expect(newUser.unreadChannels, 5);
|
||||
expect(newUser.channelMutes, channelMutes);
|
||||
expect(newUser.createdAt, createdAt);
|
||||
expect(newUser.updatedAt, updatedAt);
|
||||
expect(newUser.createdAt, createdAt);
|
||||
expect(newUser.online, true);
|
||||
expect(newUser.banned, true);
|
||||
expect(newUser.teams, ['team1', 'team2']);
|
||||
expect(newUser.language, 'fr');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -4,21 +4,73 @@ import 'package:test/test.dart';
|
||||
import '../../utils.dart';
|
||||
|
||||
void main() {
|
||||
const id = 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e';
|
||||
const role = 'test-role';
|
||||
const name = 'John';
|
||||
const image =
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow';
|
||||
const extraDataStringTest = 'Extra data test';
|
||||
const extraDataIntTest = 1;
|
||||
const extraDataDoubleTest = 1.1;
|
||||
const extraDataBoolTest = true;
|
||||
const online = true;
|
||||
const banned = true;
|
||||
const teams = ['team-1', 'team-2'];
|
||||
const createdAtString = '2021-08-03 12:39:21.817646';
|
||||
const updatedAtString = '2021-08-04 12:39:21.817646';
|
||||
const lastActiveString = '2021-08-05 12:39:21.817646';
|
||||
|
||||
group('src/models/user', () {
|
||||
test('should parse json correctly', () {
|
||||
final user = User.fromJson(jsonFixture('user.json'));
|
||||
expect(user.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e');
|
||||
expect(user.name, 'John');
|
||||
expect(user.id, id);
|
||||
expect(user.role, role);
|
||||
expect(user.name, name);
|
||||
expect(user.image, image);
|
||||
expect(user.extraData['image'], image);
|
||||
expect(user.extraData['extraDataStringTest'], extraDataStringTest);
|
||||
expect(user.extraData['extraDataIntTest'], extraDataIntTest);
|
||||
expect(user.extraData['extraDataDoubleTest'], extraDataDoubleTest);
|
||||
expect(user.extraData['extraDataBoolTest'], extraDataBoolTest);
|
||||
expect(user.online, online);
|
||||
expect(user.banned, banned);
|
||||
expect(user.teams, teams);
|
||||
expect(user.createdAt, DateTime.parse(createdAtString));
|
||||
expect(user.updatedAt, DateTime.parse(updatedAtString));
|
||||
expect(user.lastActive, DateTime.parse(lastActiveString));
|
||||
expect(user.language, 'en');
|
||||
});
|
||||
|
||||
test('should serialize to json correctly', () {
|
||||
final user = User(
|
||||
id: 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e',
|
||||
role: 'abc',
|
||||
id: id,
|
||||
role: role,
|
||||
name: name,
|
||||
image: image,
|
||||
extraData: const {
|
||||
'extraDataStringTest': extraDataStringTest,
|
||||
'extraDataIntTest': extraDataIntTest,
|
||||
'extraDataDoubleTest': extraDataDoubleTest,
|
||||
'extraDataBoolTest': extraDataBoolTest,
|
||||
},
|
||||
createdAt: DateTime.parse(createdAtString),
|
||||
updatedAt: DateTime.parse(updatedAtString),
|
||||
lastActive: DateTime.parse(lastActiveString),
|
||||
banned: online,
|
||||
online: banned,
|
||||
teams: const ['team-1', 'team-2'],
|
||||
language: 'fr',
|
||||
);
|
||||
|
||||
expect(user.toJson(), {
|
||||
'id': 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e',
|
||||
'id': id,
|
||||
'name': name,
|
||||
'image': image,
|
||||
'extraDataStringTest': extraDataStringTest,
|
||||
'extraDataIntTest': extraDataIntTest,
|
||||
'extraDataDoubleTest': extraDataDoubleTest,
|
||||
'extraDataBoolTest': extraDataBoolTest,
|
||||
'language': 'fr',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,18 +81,141 @@ void main() {
|
||||
expect(newUser.id, user.id);
|
||||
expect(newUser.role, user.role);
|
||||
expect(newUser.name, user.name);
|
||||
expect(newUser.image, user.image);
|
||||
expect(newUser.online, user.online);
|
||||
expect(newUser.banned, user.banned);
|
||||
expect(newUser.teams, user.teams);
|
||||
expect(newUser.createdAt, user.createdAt);
|
||||
expect(newUser.updatedAt, user.updatedAt);
|
||||
expect(newUser.lastActive, user.lastActive);
|
||||
expect(newUser.language, user.language);
|
||||
|
||||
newUser = user.copyWith(
|
||||
id: 'test',
|
||||
role: 'test',
|
||||
extraData: {
|
||||
'name': 'test',
|
||||
},
|
||||
name: 'test',
|
||||
image: 'https://stream.io/new-image',
|
||||
online: false,
|
||||
banned: false,
|
||||
teams: ['new-team1', 'new-team2'],
|
||||
createdAt: DateTime.parse('2021-05-03 12:39:21.817646'),
|
||||
updatedAt: DateTime.parse('2021-05-04 12:39:21.817646'),
|
||||
lastActive: DateTime.parse('2021-05-06 12:39:21.817646'),
|
||||
language: 'it',
|
||||
);
|
||||
|
||||
expect(newUser.id, 'test');
|
||||
expect(newUser.role, 'test');
|
||||
expect(newUser.name, 'test');
|
||||
expect(newUser.image, 'https://stream.io/new-image');
|
||||
expect(newUser.extraData['image'], 'https://stream.io/new-image');
|
||||
expect(newUser.online, false);
|
||||
expect(newUser.banned, false);
|
||||
expect(newUser.teams, ['new-team1', 'new-team2']);
|
||||
expect(newUser.createdAt, DateTime.parse('2021-05-03 12:39:21.817646'));
|
||||
expect(newUser.updatedAt, DateTime.parse('2021-05-04 12:39:21.817646'));
|
||||
expect(newUser.lastActive, DateTime.parse('2021-05-06 12:39:21.817646'));
|
||||
expect(newUser.language, 'it');
|
||||
});
|
||||
|
||||
test('name property and extraData manipulation', () {
|
||||
final user = User(id: id, name: name);
|
||||
|
||||
expect(user.name, name);
|
||||
expect(user.extraData['name'], name);
|
||||
expect(user.toJson(), {'id': id, 'name': name});
|
||||
expect(User.fromJson(user.toJson()).toJson(), {'id': id, 'name': name});
|
||||
|
||||
const nameOne = 'Name One';
|
||||
var newUser = user.copyWith(
|
||||
extraData: {'name': nameOne},
|
||||
);
|
||||
|
||||
expect(newUser.extraData['name'], nameOne);
|
||||
expect(newUser.name, nameOne);
|
||||
|
||||
const nameTwo = 'Name Two';
|
||||
newUser = user.copyWith(
|
||||
name: nameTwo,
|
||||
);
|
||||
|
||||
expect(newUser.extraData['name'], nameTwo);
|
||||
expect(newUser.name, nameTwo);
|
||||
|
||||
const nameThree = 'Name Three';
|
||||
newUser = user.copyWith(
|
||||
name: nameThree,
|
||||
extraData: {'name': nameThree},
|
||||
);
|
||||
|
||||
expect(newUser.extraData['name'], nameThree);
|
||||
expect(newUser.name, nameThree);
|
||||
});
|
||||
|
||||
test('image property and extraData manipulation', () {
|
||||
final user = User(id: id, image: image);
|
||||
|
||||
expect(user.image, image);
|
||||
expect(user.extraData['image'], image);
|
||||
expect(user.toJson(), {'id': id, 'image': image});
|
||||
expect(User.fromJson(user.toJson()).toJson(), {'id': id, 'image': image});
|
||||
|
||||
const imageURLOne = 'https://stream.io/image-one';
|
||||
var newUser = user.copyWith(
|
||||
extraData: {'image': imageURLOne},
|
||||
);
|
||||
|
||||
expect(newUser.extraData['image'], imageURLOne);
|
||||
expect(newUser.image, imageURLOne);
|
||||
|
||||
const imageURLTwo = 'https://stream.io/image-two';
|
||||
newUser = user.copyWith(
|
||||
image: imageURLTwo,
|
||||
);
|
||||
|
||||
expect(newUser.extraData['image'], imageURLTwo);
|
||||
expect(newUser.image, imageURLTwo);
|
||||
|
||||
const imageURLThree = 'https://stream.io/image-three';
|
||||
newUser = user.copyWith(
|
||||
image: imageURLThree,
|
||||
extraData: {'image': imageURLThree},
|
||||
);
|
||||
|
||||
expect(newUser.extraData['image'], imageURLThree);
|
||||
expect(newUser.image, imageURLThree);
|
||||
});
|
||||
|
||||
test('default values, constructor', () {
|
||||
final user = User(id: id);
|
||||
|
||||
expect(user.id, id);
|
||||
expect(user.role, null);
|
||||
expect(user.name, id, reason: 'if a name is not supplied, default to id');
|
||||
expect(user.image, null);
|
||||
expect(user.extraData, const {});
|
||||
expect(user.online, false);
|
||||
expect(user.banned, false);
|
||||
expect(user.teams, []);
|
||||
expect(user.lastActive, null);
|
||||
expect(user.createdAt, isNotNull);
|
||||
expect(user.updatedAt, isNotNull);
|
||||
});
|
||||
|
||||
test('default values, parse json', () {
|
||||
final user = User.fromJson(const {'id': id});
|
||||
|
||||
expect(user.id, id);
|
||||
expect(user.role, null);
|
||||
expect(user.name, id, reason: 'if a name is not supplied, default to id');
|
||||
expect(user.image, null);
|
||||
expect(user.extraData, const {});
|
||||
expect(user.online, false);
|
||||
expect(user.banned, false);
|
||||
expect(user.teams, []);
|
||||
expect(user.lastActive, null);
|
||||
expect(user.createdAt, isNotNull);
|
||||
expect(user.updatedAt, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user