Merge branch 'master' into fix-attachment-upload-state
This commit is contained in:
@@ -325,7 +325,7 @@ class Channel {
|
||||
state?.addMessage(message);
|
||||
|
||||
try {
|
||||
if (message.attachments?.isNotEmpty == true) {
|
||||
if (message.attachments?.any((it) => !it.uploadState.isSuccess) == true) {
|
||||
final attachmentsUploadCompleter = Completer<Message>();
|
||||
_messageAttachmentsUploadCompleter[message.id] =
|
||||
attachmentsUploadCompleter;
|
||||
@@ -373,7 +373,7 @@ class Channel {
|
||||
state?.addMessage(message);
|
||||
|
||||
try {
|
||||
if (message.attachments?.isNotEmpty == true) {
|
||||
if (message.attachments?.any((it) => !it.uploadState.isSuccess) == true) {
|
||||
final attachmentsUploadCompleter = Completer<Message>();
|
||||
_messageAttachmentsUploadCompleter[message.id] =
|
||||
attachmentsUploadCompleter;
|
||||
@@ -439,6 +439,41 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pins provided message
|
||||
Future<UpdateMessageResponse> pinMessage(
|
||||
Message message,
|
||||
Object timeoutOrExpirationDate,
|
||||
) {
|
||||
assert(() {
|
||||
if (timeoutOrExpirationDate is! DateTime &&
|
||||
timeoutOrExpirationDate is! num &&
|
||||
timeoutOrExpirationDate != null) {
|
||||
throw ArgumentError('Invalid timeout or Expiration date');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
|
||||
DateTime pinExpires;
|
||||
if (timeoutOrExpirationDate is DateTime) {
|
||||
pinExpires = timeoutOrExpirationDate;
|
||||
} else if (timeoutOrExpirationDate is num) {
|
||||
pinExpires = DateTime.now().add(
|
||||
Duration(seconds: timeoutOrExpirationDate.toInt()),
|
||||
);
|
||||
}
|
||||
return updateMessage(
|
||||
message.copyWith(
|
||||
pinned: true,
|
||||
pinExpires: pinExpires,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Unpins provided message
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) {
|
||||
return updateMessage(message.copyWith(pinned: false));
|
||||
}
|
||||
|
||||
/// Send a file to this channel
|
||||
Future<SendFileResponse> sendFile(
|
||||
AttachmentFile file, {
|
||||
@@ -469,6 +504,26 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// A message search.
|
||||
Future<SearchMessagesResponse> search({
|
||||
String query,
|
||||
Map<String, dynamic> messageFilters,
|
||||
List<SortOption> sort,
|
||||
PaginationParams paginationParams,
|
||||
}) {
|
||||
return _client.search(
|
||||
{
|
||||
'cid': {
|
||||
r'$in': [cid],
|
||||
},
|
||||
},
|
||||
sort: sort,
|
||||
query: query,
|
||||
paginationParams: paginationParams,
|
||||
messageFilters: messageFilters,
|
||||
);
|
||||
}
|
||||
|
||||
/// Delete a file from this channel
|
||||
Future<EmptyResponse> deleteFile(
|
||||
String url, {
|
||||
|
||||
@@ -24,6 +24,7 @@ import 'exceptions.dart';
|
||||
import 'models/event.dart';
|
||||
import 'models/message.dart';
|
||||
import 'models/user.dart';
|
||||
import 'extensions/map_extension.dart';
|
||||
|
||||
/// Handler function used for logging records. Function requires a single [LogRecord]
|
||||
/// as the only parameter.
|
||||
@@ -1021,27 +1022,39 @@ class StreamChatClient {
|
||||
|
||||
/// A message search.
|
||||
Future<SearchMessagesResponse> search(
|
||||
Map<String, dynamic> filters,
|
||||
List<SortOption> sort,
|
||||
Map<String, dynamic> filters, {
|
||||
String query,
|
||||
PaginationParams paginationParams, {
|
||||
List<SortOption> sort,
|
||||
PaginationParams paginationParams,
|
||||
Map<String, dynamic> messageFilters,
|
||||
}) async {
|
||||
assert(() {
|
||||
if (filters == null || filters.isEmpty) {
|
||||
throw ArgumentError('`filters` cannot be set as null or empty');
|
||||
}
|
||||
if (query == null && messageFilters == null) {
|
||||
throw ArgumentError('Provide at least `query` or `messageFilters`');
|
||||
}
|
||||
if (query != null && messageFilters != null) {
|
||||
throw ArgumentError(
|
||||
"Can't provide both `query` and `messageFilters` at the same time",
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
|
||||
final payload = {
|
||||
'filter_conditions': filters,
|
||||
if (messageFilters != null) ...{
|
||||
'message_filter_conditions': messageFilters,
|
||||
},
|
||||
'message_filter_conditions': messageFilters,
|
||||
'query': query,
|
||||
'sort': sort,
|
||||
};
|
||||
if (paginationParams != null) ...paginationParams.toJson(),
|
||||
}.nullProtected;
|
||||
|
||||
if (paginationParams != null) {
|
||||
payload.addAll(paginationParams.toJson());
|
||||
}
|
||||
final response = await get('/search', queryParameters: {
|
||||
'payload': json.encode(payload),
|
||||
});
|
||||
|
||||
final response = await get('/search',
|
||||
queryParameters: {'payload': json.encode(payload)});
|
||||
return decode<SearchMessagesResponse>(
|
||||
response.data, SearchMessagesResponse.fromJson);
|
||||
}
|
||||
@@ -1295,7 +1308,7 @@ class StreamChatClient {
|
||||
Future<UpdateMessageResponse> updateMessage(Message message) async {
|
||||
final response = await post(
|
||||
'/messages/${message.id}',
|
||||
data: {'message': message},
|
||||
data: {'message': message.toJson()},
|
||||
);
|
||||
return decode(response.data, UpdateMessageResponse.fromJson);
|
||||
}
|
||||
@@ -1311,6 +1324,38 @@ class StreamChatClient {
|
||||
final response = await get('/messages/$messageId');
|
||||
return decode(response.data, GetMessageResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Pins provided message
|
||||
Future<UpdateMessageResponse> pinMessage(
|
||||
Message message,
|
||||
Object timeoutOrExpirationDate,
|
||||
) {
|
||||
assert(() {
|
||||
if (timeoutOrExpirationDate is! DateTime &&
|
||||
timeoutOrExpirationDate is! num &&
|
||||
timeoutOrExpirationDate != null) {
|
||||
throw ArgumentError('Invalid timeout or Expiration date');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
|
||||
DateTime pinExpires;
|
||||
if (timeoutOrExpirationDate is DateTime) {
|
||||
pinExpires = timeoutOrExpirationDate.toUtc();
|
||||
} else if (timeoutOrExpirationDate is num) {
|
||||
pinExpires = DateTime.now().add(
|
||||
Duration(seconds: timeoutOrExpirationDate.toInt()),
|
||||
);
|
||||
}
|
||||
return updateMessage(
|
||||
message.copyWith(pinned: true, pinExpires: pinExpires),
|
||||
);
|
||||
}
|
||||
|
||||
/// Unpins provided message
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) {
|
||||
return updateMessage(message.copyWith(pinned: false));
|
||||
}
|
||||
}
|
||||
|
||||
/// The class that handles the state of the channel listening to the events
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/// Useful extension functions for [Map]
|
||||
extension MapX on Map {
|
||||
/// Returns a new map with null keys or values removed
|
||||
Map<String, dynamic> get nullProtected {
|
||||
return {...this}..removeWhere((key, value) => key == null || value == null);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ class ChannelState {
|
||||
/// A paginated list of channel members
|
||||
final List<Member> members;
|
||||
|
||||
/// A paginated list of pinned messages
|
||||
final List<Message> pinnedMessages;
|
||||
|
||||
/// The count of users watching the channel
|
||||
final int watcherCount;
|
||||
|
||||
@@ -34,6 +37,7 @@ class ChannelState {
|
||||
this.channel,
|
||||
this.messages = const [],
|
||||
this.members = const [],
|
||||
this.pinnedMessages = const [],
|
||||
this.watcherCount,
|
||||
this.watchers = const [],
|
||||
this.read = const [],
|
||||
@@ -51,6 +55,7 @@ class ChannelState {
|
||||
ChannelModel channel,
|
||||
List<Message> messages,
|
||||
List<Member> members,
|
||||
List<Message> pinnedMessages,
|
||||
int watcherCount,
|
||||
List<User> watchers,
|
||||
List<Read> read,
|
||||
@@ -59,6 +64,7 @@ class ChannelState {
|
||||
channel: channel ?? this.channel,
|
||||
messages: messages ?? this.messages,
|
||||
members: members ?? this.members,
|
||||
pinnedMessages: pinnedMessages ?? this.pinnedMessages,
|
||||
watcherCount: watcherCount ?? this.watcherCount,
|
||||
watchers: watchers ?? this.watchers,
|
||||
read: read ?? this.read,
|
||||
|
||||
@@ -27,6 +27,13 @@ ChannelState _$ChannelStateFromJson(Map json) {
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList(),
|
||||
pinnedMessages: (json['pinned_messages'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Message.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList(),
|
||||
watcherCount: json['watcher_count'] as int,
|
||||
watchers: (json['watchers'] as List)
|
||||
?.map((e) => e == null
|
||||
@@ -50,6 +57,8 @@ Map<String, dynamic> _$ChannelStateToJson(ChannelState instance) =>
|
||||
'channel': instance.channel?.toJson(),
|
||||
'messages': instance.messages?.map((e) => e?.toJson())?.toList(),
|
||||
'members': instance.members?.map((e) => e?.toJson())?.toList(),
|
||||
'pinned_messages':
|
||||
instance.pinnedMessages?.map((e) => e?.toJson())?.toList(),
|
||||
'watcher_count': instance.watcherCount,
|
||||
'watchers': instance.watchers?.map((e) => e?.toJson())?.toList(),
|
||||
'read': instance.read?.map((e) => e?.toJson())?.toList(),
|
||||
|
||||
@@ -8,6 +8,12 @@ import 'user.dart';
|
||||
|
||||
part 'message.g.dart';
|
||||
|
||||
class _PinExpires {
|
||||
const _PinExpires();
|
||||
}
|
||||
|
||||
const _pinExpires = _PinExpires();
|
||||
|
||||
/// Enum defining the status of a sending message
|
||||
enum MessageSendingStatus {
|
||||
/// Message is being sent
|
||||
@@ -117,6 +123,22 @@ class Message {
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final User user;
|
||||
|
||||
/// If true the message is pinned
|
||||
final bool pinned;
|
||||
|
||||
/// Reserved field indicating when the message was pinned
|
||||
@JsonKey(toJson: Serialization.readOnly)
|
||||
final DateTime pinnedAt;
|
||||
|
||||
/// Reserved field indicating when the message will expire
|
||||
///
|
||||
/// if `null` message has no expiry
|
||||
final DateTime pinExpires;
|
||||
|
||||
/// Reserved field indicating who pinned the message
|
||||
@JsonKey(toJson: Serialization.readOnly)
|
||||
final User pinnedBy;
|
||||
|
||||
/// Message custom extraData
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, dynamic> extraData;
|
||||
@@ -160,6 +182,10 @@ class Message {
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
'user',
|
||||
'pinned',
|
||||
'pinned_at',
|
||||
'pin_expires',
|
||||
'pinned_by',
|
||||
];
|
||||
|
||||
/// Constructor used for json serialization
|
||||
@@ -185,10 +211,15 @@ class Message {
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.user,
|
||||
this.pinned = false,
|
||||
this.pinnedAt,
|
||||
DateTime pinExpires,
|
||||
this.pinnedBy,
|
||||
this.extraData,
|
||||
this.deletedAt,
|
||||
this.status = MessageSendingStatus.sent,
|
||||
}) : id = id ?? Uuid().v4();
|
||||
}) : id = id ?? Uuid().v4(),
|
||||
pinExpires = pinExpires?.toUtc();
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
|
||||
@@ -222,35 +253,52 @@ class Message {
|
||||
DateTime updatedAt,
|
||||
DateTime deletedAt,
|
||||
User user,
|
||||
bool pinned,
|
||||
DateTime pinnedAt,
|
||||
Object pinExpires = _pinExpires,
|
||||
User pinnedBy,
|
||||
Map<String, dynamic> extraData,
|
||||
MessageSendingStatus status,
|
||||
}) =>
|
||||
Message(
|
||||
id: id ?? this.id,
|
||||
text: text ?? this.text,
|
||||
type: type ?? this.type,
|
||||
attachments: attachments ?? this.attachments,
|
||||
mentionedUsers: mentionedUsers ?? this.mentionedUsers,
|
||||
reactionCounts: reactionCounts ?? this.reactionCounts,
|
||||
reactionScores: reactionScores ?? this.reactionScores,
|
||||
latestReactions: latestReactions ?? this.latestReactions,
|
||||
ownReactions: ownReactions ?? this.ownReactions,
|
||||
parentId: parentId ?? this.parentId,
|
||||
quotedMessage: quotedMessage ?? this.quotedMessage,
|
||||
quotedMessageId: quotedMessageId ?? this.quotedMessageId,
|
||||
replyCount: replyCount ?? this.replyCount,
|
||||
threadParticipants: threadParticipants ?? this.threadParticipants,
|
||||
showInChannel: showInChannel ?? this.showInChannel,
|
||||
command: command ?? this.command,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
silent: silent ?? this.silent,
|
||||
extraData: extraData ?? this.extraData,
|
||||
user: user ?? this.user,
|
||||
shadowed: shadowed ?? this.shadowed,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
deletedAt: deletedAt ?? this.deletedAt,
|
||||
status: status ?? this.status,
|
||||
);
|
||||
}) {
|
||||
assert(() {
|
||||
if (pinExpires is! DateTime &&
|
||||
pinExpires != null &&
|
||||
pinExpires is! _PinExpires) {
|
||||
throw ArgumentError('`pinExpires` can only be set as DateTime or null');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
return Message(
|
||||
id: id ?? this.id,
|
||||
text: text ?? this.text,
|
||||
type: type ?? this.type,
|
||||
attachments: attachments ?? this.attachments,
|
||||
mentionedUsers: mentionedUsers ?? this.mentionedUsers,
|
||||
reactionCounts: reactionCounts ?? this.reactionCounts,
|
||||
reactionScores: reactionScores ?? this.reactionScores,
|
||||
latestReactions: latestReactions ?? this.latestReactions,
|
||||
ownReactions: ownReactions ?? this.ownReactions,
|
||||
parentId: parentId ?? this.parentId,
|
||||
quotedMessage: quotedMessage ?? this.quotedMessage,
|
||||
quotedMessageId: quotedMessageId ?? this.quotedMessageId,
|
||||
replyCount: replyCount ?? this.replyCount,
|
||||
threadParticipants: threadParticipants ?? this.threadParticipants,
|
||||
showInChannel: showInChannel ?? this.showInChannel,
|
||||
command: command ?? this.command,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
silent: silent ?? this.silent,
|
||||
extraData: extraData ?? this.extraData,
|
||||
user: user ?? this.user,
|
||||
shadowed: shadowed ?? this.shadowed,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
deletedAt: deletedAt ?? this.deletedAt,
|
||||
status: status ?? this.status,
|
||||
pinned: pinned ?? this.pinned,
|
||||
pinnedAt: pinnedAt ?? this.pinnedAt,
|
||||
pinnedBy: pinnedBy ?? this.pinnedBy,
|
||||
pinExpires: pinExpires == _pinExpires ? this.pinExpires : pinExpires,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a new [Message] that is a combination of this message and the given
|
||||
/// [other] message.
|
||||
@@ -281,6 +329,10 @@ class Message {
|
||||
updatedAt: other.updatedAt,
|
||||
deletedAt: other.deletedAt,
|
||||
status: other.status,
|
||||
pinned: other.pinned,
|
||||
pinnedAt: other.pinnedAt,
|
||||
pinExpires: other.pinExpires,
|
||||
pinnedBy: other.pinnedBy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,18 @@ Message _$MessageFromJson(Map json) {
|
||||
: User.fromJson((json['user'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
pinned: json['pinned'] as bool,
|
||||
pinnedAt: json['pinned_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['pinned_at'] as String),
|
||||
pinExpires: json['pin_expires'] == null
|
||||
? null
|
||||
: DateTime.parse(json['pin_expires'] as String),
|
||||
pinnedBy: json['pinned_by'] == null
|
||||
? null
|
||||
: User.fromJson((json['pinned_by'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
extraData: (json['extra_data'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
),
|
||||
@@ -116,6 +128,10 @@ Map<String, dynamic> _$MessageToJson(Message instance) {
|
||||
writeNotNull('created_at', readonly(instance.createdAt));
|
||||
writeNotNull('updated_at', readonly(instance.updatedAt));
|
||||
writeNotNull('user', readonly(instance.user));
|
||||
val['pinned'] = instance.pinned;
|
||||
val['pinned_at'] = readonly(instance.pinnedAt);
|
||||
val['pin_expires'] = instance.pinExpires?.toIso8601String();
|
||||
val['pinned_by'] = readonly(instance.pinnedBy);
|
||||
writeNotNull('extra_data', instance.extraData);
|
||||
writeNotNull('deleted_at', readonly(instance.deletedAt));
|
||||
return val;
|
||||
|
||||
Reference in New Issue
Block a user