Merge branch 'v4' into feat/stream-channel-listview
This commit is contained in:
@@ -11,7 +11,8 @@ dependencies:
|
||||
cupertino_icons: ^1.0.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
stream_chat: ^2.2.1
|
||||
stream_chat:
|
||||
path: ../
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -294,6 +294,18 @@ class Channel {
|
||||
return data;
|
||||
}
|
||||
|
||||
/// List of user permissions on this channel
|
||||
List<String> get ownCapabilities =>
|
||||
state?._channelState.channel?.ownCapabilities ?? [];
|
||||
|
||||
/// List of user permissions on this channel
|
||||
Stream<List<String>> get ownCapabilitiesStream {
|
||||
_checkInitialized();
|
||||
return state!.channelStateStream
|
||||
.map((cs) => cs.channel?.ownCapabilities ?? [])
|
||||
.distinct();
|
||||
}
|
||||
|
||||
/// Channel extra data as a stream.
|
||||
Stream<Map<String, Object?>> get extraDataStream {
|
||||
_checkInitialized();
|
||||
@@ -487,6 +499,7 @@ class Channel {
|
||||
Future<SendMessageResponse> sendMessage(
|
||||
Message message, {
|
||||
bool skipPush = false,
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
_checkInitialized();
|
||||
// Cancelling previous completer in case it's called again in the process
|
||||
@@ -534,6 +547,7 @@ class Channel {
|
||||
id!,
|
||||
type,
|
||||
skipPush: skipPush,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
state!.addMessage(response.message);
|
||||
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
||||
@@ -550,7 +564,10 @@ class Channel {
|
||||
///
|
||||
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
||||
/// before actually updating the message.
|
||||
Future<UpdateMessageResponse> updateMessage(Message message) async {
|
||||
Future<UpdateMessageResponse> updateMessage(
|
||||
Message message, {
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
final originalMessage = message;
|
||||
|
||||
// Cancelling previous completer in case it's called again in the process
|
||||
@@ -588,7 +605,10 @@ class Channel {
|
||||
message = await attachmentsUploadCompleter.future;
|
||||
}
|
||||
|
||||
final response = await _client.updateMessage(message);
|
||||
final response = await _client.updateMessage(
|
||||
message,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
final m = response.message.copyWith(
|
||||
ownReactions: message.ownReactions,
|
||||
@@ -618,12 +638,14 @@ class Channel {
|
||||
Message message, {
|
||||
Map<String, Object?>? set,
|
||||
List<String>? unset,
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _client.partialUpdateMessage(
|
||||
message.id,
|
||||
set: set,
|
||||
unset: unset,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
final updatedMessage = response.message.copyWith(
|
||||
@@ -1572,7 +1594,9 @@ class ChannelClientState {
|
||||
_subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) {
|
||||
final channel = e.channel!;
|
||||
updateChannelState(channelState.copyWith(
|
||||
channel: channel,
|
||||
channel: channel.copyWith(
|
||||
ownCapabilities: channelState.channel?.ownCapabilities,
|
||||
),
|
||||
members: channel.members,
|
||||
));
|
||||
}));
|
||||
|
||||
@@ -1183,12 +1183,14 @@ class StreamChatClient {
|
||||
String channelId,
|
||||
String channelType, {
|
||||
bool skipPush = false,
|
||||
bool skipEnrichUrl = false,
|
||||
}) =>
|
||||
_chatApi.message.sendMessage(
|
||||
channelId,
|
||||
channelType,
|
||||
message,
|
||||
skipPush: skipPush,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
/// Lists all the message replies for the [parentId]
|
||||
@@ -1212,8 +1214,14 @@ class StreamChatClient {
|
||||
);
|
||||
|
||||
/// Update the given message
|
||||
Future<UpdateMessageResponse> updateMessage(Message message) =>
|
||||
_chatApi.message.updateMessage(message);
|
||||
Future<UpdateMessageResponse> updateMessage(
|
||||
Message message, {
|
||||
bool skipEnrichUrl = false,
|
||||
}) =>
|
||||
_chatApi.message.updateMessage(
|
||||
message,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
/// Partially update the given [messageId]
|
||||
/// Use [set] to define values to be set
|
||||
@@ -1222,11 +1230,13 @@ class StreamChatClient {
|
||||
String messageId, {
|
||||
Map<String, Object?>? set,
|
||||
List<String>? unset,
|
||||
bool skipEnrichUrl = false,
|
||||
}) =>
|
||||
_chatApi.message.partialUpdateMessage(
|
||||
messageId,
|
||||
set: set,
|
||||
unset: unset,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
/// Deletes the given message
|
||||
|
||||
@@ -5,9 +5,9 @@ import 'package:rxdart/rxdart.dart';
|
||||
import 'package:stream_chat/src/client/retry_policy.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
/// The retry queue associated to a channel
|
||||
/// The retry queue associated to a channel.
|
||||
class RetryQueue {
|
||||
/// Instantiate a new RetryQueue object
|
||||
/// Instantiate a new RetryQueue object.
|
||||
RetryQueue({
|
||||
required this.channel,
|
||||
this.logger,
|
||||
@@ -17,13 +17,13 @@ class RetryQueue {
|
||||
_listenFailedEvents();
|
||||
}
|
||||
|
||||
/// The channel of this queue
|
||||
/// The channel of this queue.
|
||||
final Channel channel;
|
||||
|
||||
/// The client associated with this [channel]
|
||||
/// The client associated with this [channel].
|
||||
final StreamChatClient client;
|
||||
|
||||
/// The logger associated to this queue
|
||||
/// The logger associated to this queue.
|
||||
final Logger? logger;
|
||||
|
||||
late final RetryPolicy _retryPolicy;
|
||||
@@ -63,7 +63,7 @@ class RetryQueue {
|
||||
}).addTo(_compositeSubscription);
|
||||
}
|
||||
|
||||
/// Add a list of messages
|
||||
/// Add a list of messages.
|
||||
void add(List<Message> messages) {
|
||||
if (messages.isEmpty) return;
|
||||
if (!_messageQueue.containsAllMessage(messages)) {
|
||||
@@ -113,6 +113,7 @@ class RetryQueue {
|
||||
} catch (e) {
|
||||
if (e is! StreamChatNetworkError || !e.isRetriable) {
|
||||
_messageQueue.removeMessage(message);
|
||||
_sendFailedEvent(message);
|
||||
return true;
|
||||
}
|
||||
// retry logic
|
||||
@@ -174,10 +175,10 @@ class RetryQueue {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether our [_messageQueue] has messages or not
|
||||
/// Whether our [_messageQueue] has messages or not.
|
||||
bool get hasMessages => _messageQueue.isNotEmpty;
|
||||
|
||||
/// Call this method to dispose this object
|
||||
/// Call this method to dispose this object.
|
||||
void dispose() {
|
||||
_messageQueue.clear();
|
||||
_compositeSubscription.dispose();
|
||||
|
||||
@@ -266,6 +266,7 @@ class ChannelApi {
|
||||
) async {
|
||||
final response = await _client.post(
|
||||
'${_getChannelUrl(channelId, channelType)}/truncate',
|
||||
data: {},
|
||||
);
|
||||
return EmptyResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ class MessageApi {
|
||||
String channelType,
|
||||
Message message, {
|
||||
bool skipPush = false,
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
final response = await _client.post(
|
||||
'/channels/$channelType/$channelId/message',
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_push': skipPush,
|
||||
'skip_enrich_url': skipEnrichUrl,
|
||||
},
|
||||
);
|
||||
return SendMessageResponse.fromJson(response.data);
|
||||
@@ -51,11 +53,15 @@ class MessageApi {
|
||||
|
||||
/// Updates the given [message]
|
||||
Future<UpdateMessageResponse> updateMessage(
|
||||
Message message,
|
||||
) async {
|
||||
Message message, {
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
final response = await _client.post(
|
||||
'/messages/${message.id}',
|
||||
data: {'message': message},
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_enrich_url': skipEnrichUrl,
|
||||
},
|
||||
);
|
||||
return UpdateMessageResponse.fromJson(response.data);
|
||||
}
|
||||
@@ -67,12 +73,14 @@ class MessageApi {
|
||||
String messageId, {
|
||||
Map<String, Object?>? set,
|
||||
List<String>? unset,
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
final response = await _client.put(
|
||||
'/messages/$messageId',
|
||||
data: {
|
||||
if (set != null) 'set': set,
|
||||
if (unset != null) 'unset': unset,
|
||||
'skip_enrich_url': skipEnrichUrl,
|
||||
},
|
||||
);
|
||||
return UpdateMessageResponse.fromJson(response.data);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/core/api/responses.dart';
|
||||
import 'package:stream_chat/src/core/models/action.dart';
|
||||
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
||||
import 'package:stream_chat/src/core/util/serializer.dart';
|
||||
@@ -66,6 +67,21 @@ class Attachment extends Equatable {
|
||||
topLevelFields + dbSpecificTopLevelFields,
|
||||
));
|
||||
|
||||
factory Attachment.fromOGAttachment(OGAttachmentResponse ogAttachment) =>
|
||||
Attachment(
|
||||
type: ogAttachment.type,
|
||||
title: ogAttachment.title,
|
||||
titleLink: ogAttachment.titleLink,
|
||||
text: ogAttachment.text,
|
||||
imageUrl: ogAttachment.imageUrl,
|
||||
thumbUrl: ogAttachment.thumbUrl,
|
||||
authorName: ogAttachment.authorName,
|
||||
authorLink: ogAttachment.authorLink,
|
||||
assetUrl: ogAttachment.assetUrl,
|
||||
ogScrapeUrl: ogAttachment.ogScrapeUrl,
|
||||
uploadState: const UploadState.success(),
|
||||
);
|
||||
|
||||
///The attachment type based on the URL resource. This can be: audio,
|
||||
///image or video
|
||||
final String? type;
|
||||
@@ -229,6 +245,33 @@ class Attachment extends Equatable {
|
||||
extraData: extraData ?? this.extraData,
|
||||
);
|
||||
|
||||
Attachment merge(Attachment? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
type: other.type,
|
||||
titleLink: other.titleLink,
|
||||
title: other.title,
|
||||
thumbUrl: other.thumbUrl,
|
||||
text: other.text,
|
||||
pretext: other.pretext,
|
||||
ogScrapeUrl: other.ogScrapeUrl,
|
||||
imageUrl: other.imageUrl,
|
||||
footerIcon: other.footerIcon,
|
||||
footer: other.footer,
|
||||
fields: other.fields,
|
||||
fallback: other.fallback,
|
||||
color: other.color,
|
||||
authorName: other.authorName,
|
||||
authorLink: other.authorLink,
|
||||
authorIcon: other.authorIcon,
|
||||
assetUrl: other.assetUrl,
|
||||
actions: other.actions,
|
||||
file: other.file,
|
||||
uploadState: other.uploadState,
|
||||
extraData: other.extraData,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
|
||||
@@ -13,6 +13,7 @@ class ChannelModel {
|
||||
String? id,
|
||||
String? type,
|
||||
String? cid,
|
||||
this.ownCapabilities = const [],
|
||||
ChannelConfig? config,
|
||||
this.createdBy,
|
||||
this.frozen = false,
|
||||
@@ -51,6 +52,10 @@ class ChannelModel {
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final String cid;
|
||||
|
||||
/// List of user permissions on this channel
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final List<String> ownCapabilities;
|
||||
|
||||
/// The channel configuration data
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final ChannelConfig config;
|
||||
@@ -101,6 +106,7 @@ class ChannelModel {
|
||||
'id',
|
||||
'type',
|
||||
'cid',
|
||||
'own_capabilities',
|
||||
'config',
|
||||
'created_by',
|
||||
'frozen',
|
||||
@@ -127,6 +133,7 @@ class ChannelModel {
|
||||
String? id,
|
||||
String? type,
|
||||
String? cid,
|
||||
List<String>? ownCapabilities,
|
||||
ChannelConfig? config,
|
||||
User? createdBy,
|
||||
bool? frozen,
|
||||
@@ -143,6 +150,7 @@ class ChannelModel {
|
||||
id: id ?? this.id,
|
||||
type: type ?? this.type,
|
||||
cid: cid ?? this.cid,
|
||||
ownCapabilities: ownCapabilities ?? this.ownCapabilities,
|
||||
config: config ?? this.config,
|
||||
createdBy: createdBy ?? this.createdBy,
|
||||
frozen: frozen ?? this.frozen,
|
||||
@@ -164,6 +172,7 @@ class ChannelModel {
|
||||
id: other.id,
|
||||
type: other.type,
|
||||
cid: other.cid,
|
||||
ownCapabilities: other.ownCapabilities,
|
||||
config: other.config,
|
||||
createdBy: other.createdBy,
|
||||
frozen: other.frozen,
|
||||
|
||||
@@ -10,6 +10,10 @@ ChannelModel _$ChannelModelFromJson(Map<String, dynamic> json) => ChannelModel(
|
||||
id: json['id'] as String?,
|
||||
type: json['type'] as String?,
|
||||
cid: json['cid'] as String?,
|
||||
ownCapabilities: (json['own_capabilities'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
config: json['config'] == null
|
||||
? null
|
||||
: ChannelConfig.fromJson(json['config'] as Map<String, dynamic>),
|
||||
@@ -48,6 +52,7 @@ Map<String, dynamic> _$ChannelModelToJson(ChannelModel instance) {
|
||||
}
|
||||
|
||||
writeNotNull('cid', readonly(instance.cid));
|
||||
writeNotNull('own_capabilities', readonly(instance.ownCapabilities));
|
||||
writeNotNull('config', readonly(instance.config));
|
||||
writeNotNull('created_by', readonly(instance.createdBy));
|
||||
val['frozen'] = instance.frozen;
|
||||
|
||||
@@ -8,13 +8,13 @@ import 'package:uuid/uuid.dart';
|
||||
|
||||
part 'message.g.dart';
|
||||
|
||||
class _PinExpires {
|
||||
const _PinExpires();
|
||||
class _NullConst {
|
||||
const _NullConst();
|
||||
}
|
||||
|
||||
const _pinExpires = _PinExpires();
|
||||
const _nullConst = _NullConst();
|
||||
|
||||
/// Enum defining the status of a sending message
|
||||
/// Enum defining the status of a sending message.
|
||||
enum MessageSendingStatus {
|
||||
/// Message is being sent
|
||||
sending,
|
||||
@@ -40,10 +40,10 @@ enum MessageSendingStatus {
|
||||
sent,
|
||||
}
|
||||
|
||||
/// The class that contains the information about a message
|
||||
/// The class that contains the information about a message.
|
||||
@JsonSerializable()
|
||||
class Message extends Equatable {
|
||||
/// Constructor used for json serialization
|
||||
/// Constructor used for json serialization.
|
||||
Message({
|
||||
String? id,
|
||||
this.text,
|
||||
@@ -58,44 +58,47 @@ class Message extends Equatable {
|
||||
this.ownReactions,
|
||||
this.parentId,
|
||||
this.quotedMessage,
|
||||
this.quotedMessageId,
|
||||
String? quotedMessageId,
|
||||
this.replyCount = 0,
|
||||
this.threadParticipants,
|
||||
this.showInChannel,
|
||||
this.command,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
this.deletedAt,
|
||||
this.user,
|
||||
this.pinned = false,
|
||||
this.pinnedAt,
|
||||
DateTime? pinExpires,
|
||||
this.pinnedBy,
|
||||
this.extraData = const {},
|
||||
this.deletedAt,
|
||||
this.status = MessageSendingStatus.sent,
|
||||
this.status = MessageSendingStatus.sending,
|
||||
this.i18n,
|
||||
}) : id = id ?? const Uuid().v4(),
|
||||
pinExpires = pinExpires?.toUtc(),
|
||||
createdAt = createdAt ?? DateTime.now(),
|
||||
updatedAt = updatedAt ?? DateTime.now();
|
||||
_createdAt = createdAt,
|
||||
_updatedAt = updatedAt,
|
||||
_quotedMessageId = quotedMessageId;
|
||||
|
||||
/// Create a new instance from a json
|
||||
/// Create a new instance from JSON.
|
||||
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
|
||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
||||
).copyWith(
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
/// The message ID. This is either created by Stream or set client side when
|
||||
/// the message is added.
|
||||
final String id;
|
||||
|
||||
/// The text of this message
|
||||
/// The text of this message.
|
||||
final String? text;
|
||||
|
||||
/// The status of a sending message
|
||||
/// The status of a sending message.
|
||||
@JsonKey(ignore: true)
|
||||
final MessageSendingStatus status;
|
||||
|
||||
/// The message type
|
||||
/// The message type.
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
toJson: Serializer.readOnly,
|
||||
@@ -107,15 +110,15 @@ class Message extends Equatable {
|
||||
@JsonKey(includeIfNull: false)
|
||||
final List<Attachment> attachments;
|
||||
|
||||
/// The list of user mentioned in the message
|
||||
/// The list of user mentioned in the message.
|
||||
@JsonKey(toJson: User.toIds)
|
||||
final List<User> mentionedUsers;
|
||||
|
||||
/// A map describing the count of number of every reaction
|
||||
/// A map describing the count of number of every reaction.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final Map<String, int>? reactionCounts;
|
||||
|
||||
/// A map describing the count of score of every reaction
|
||||
/// A map describing the count of score of every reaction.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final Map<String, int>? reactionScores;
|
||||
|
||||
@@ -130,12 +133,14 @@ class Message extends Equatable {
|
||||
/// The ID of the parent message, if the message is a thread reply.
|
||||
final String? parentId;
|
||||
|
||||
/// A quoted reply message
|
||||
/// A quoted reply message.
|
||||
@JsonKey(toJson: Serializer.readOnly)
|
||||
final Message? quotedMessage;
|
||||
|
||||
final String? _quotedMessageId;
|
||||
|
||||
/// The ID of the quoted message, if the message is a quoted reply.
|
||||
final String? quotedMessageId;
|
||||
String? get quotedMessageId => _quotedMessageId ?? quotedMessage?.id;
|
||||
|
||||
/// Reserved field indicating the number of replies for this message.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
@@ -148,10 +153,10 @@ class Message extends Equatable {
|
||||
/// Check if this message needs to show in the channel.
|
||||
final bool? showInChannel;
|
||||
|
||||
/// If true the message is silent
|
||||
/// If true the message is silent.
|
||||
final bool silent;
|
||||
|
||||
/// If true the message is shadowed
|
||||
/// If true the message is shadowed.
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
toJson: Serializer.readOnly,
|
||||
@@ -162,56 +167,61 @@ class Message extends Equatable {
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final String? command;
|
||||
|
||||
/// Reserved field indicating when the message was created.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime createdAt;
|
||||
|
||||
/// Reserved field indicating when the message was updated last time.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// User who sent the message
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final User? user;
|
||||
|
||||
/// If true the message is pinned
|
||||
final bool pinned;
|
||||
|
||||
/// Reserved field indicating when the message was pinned
|
||||
@JsonKey(toJson: Serializer.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: Serializer.readOnly)
|
||||
final User? pinnedBy;
|
||||
|
||||
/// Message custom extraData
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
/// True if the message is a system info
|
||||
bool get isSystem => type == 'system';
|
||||
|
||||
/// True if the message has been deleted
|
||||
bool get isDeleted => type == 'deleted';
|
||||
|
||||
/// True if the message is ephemeral
|
||||
bool get isEphemeral => type == 'ephemeral';
|
||||
final DateTime? _createdAt;
|
||||
|
||||
/// Reserved field indicating when the message was deleted.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime? deletedAt;
|
||||
|
||||
/// Reserved field indicating when the message was created.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
DateTime get createdAt => _createdAt ?? DateTime.now();
|
||||
|
||||
final DateTime? _updatedAt;
|
||||
|
||||
/// Reserved field indicating when the message was updated last time.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
DateTime get updatedAt => _updatedAt ?? DateTime.now();
|
||||
|
||||
/// User who sent the message.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final User? user;
|
||||
|
||||
/// If true the message is pinned.
|
||||
final bool pinned;
|
||||
|
||||
/// Reserved field indicating when the message was pinned.
|
||||
@JsonKey(toJson: Serializer.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: Serializer.readOnly)
|
||||
final User? pinnedBy;
|
||||
|
||||
/// Message custom extraData.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
/// True if the message is a system info.
|
||||
bool get isSystem => type == 'system';
|
||||
|
||||
/// True if the message has been deleted.
|
||||
bool get isDeleted => type == 'deleted';
|
||||
|
||||
/// True if the message is ephemeral.
|
||||
bool get isEphemeral => type == 'ephemeral';
|
||||
|
||||
/// A Map of translations.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, String>? i18n;
|
||||
|
||||
/// Known top level fields.
|
||||
///
|
||||
/// Useful for [Serializer] methods.
|
||||
static const topLevelFields = [
|
||||
'id',
|
||||
@@ -244,7 +254,7 @@ class Message extends Equatable {
|
||||
'i18n',
|
||||
];
|
||||
|
||||
/// Serialize to json
|
||||
/// Serialize to json.
|
||||
Map<String, dynamic> toJson() => Serializer.moveFromExtraDataToRoot(
|
||||
_$MessageToJson(this),
|
||||
);
|
||||
@@ -256,18 +266,18 @@ class Message extends Equatable {
|
||||
String? type,
|
||||
List<Attachment>? attachments,
|
||||
List<User>? mentionedUsers,
|
||||
bool? silent,
|
||||
bool? shadowed,
|
||||
Map<String, int>? reactionCounts,
|
||||
Map<String, int>? reactionScores,
|
||||
List<Reaction>? latestReactions,
|
||||
List<Reaction>? ownReactions,
|
||||
String? parentId,
|
||||
Message? quotedMessage,
|
||||
String? quotedMessageId,
|
||||
Object? quotedMessage = _nullConst,
|
||||
Object? quotedMessageId = _nullConst,
|
||||
int? replyCount,
|
||||
List<User>? threadParticipants,
|
||||
bool? showInChannel,
|
||||
bool? shadowed,
|
||||
bool? silent,
|
||||
String? command,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
@@ -275,7 +285,7 @@ class Message extends Equatable {
|
||||
User? user,
|
||||
bool? pinned,
|
||||
DateTime? pinnedAt,
|
||||
Object? pinExpires = _pinExpires,
|
||||
Object? pinExpires = _nullConst,
|
||||
User? pinnedBy,
|
||||
Map<String, Object?>? extraData,
|
||||
MessageSendingStatus? status,
|
||||
@@ -284,41 +294,68 @@ class Message extends Equatable {
|
||||
assert(() {
|
||||
if (pinExpires is! DateTime &&
|
||||
pinExpires != null &&
|
||||
pinExpires is! _PinExpires) {
|
||||
pinExpires is! _NullConst) {
|
||||
throw ArgumentError('`pinExpires` can only be set as DateTime or null');
|
||||
}
|
||||
return true;
|
||||
}(), 'Validate type for pinExpires');
|
||||
|
||||
assert(() {
|
||||
if (quotedMessage is! Message &&
|
||||
quotedMessage != null &&
|
||||
quotedMessage is! _NullConst) {
|
||||
throw ArgumentError(
|
||||
'`quotedMessage` can only be set as Message or null',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}(), 'Validate type for quotedMessage');
|
||||
|
||||
assert(() {
|
||||
if (quotedMessageId is! String &&
|
||||
quotedMessageId != null &&
|
||||
quotedMessageId is! _NullConst) {
|
||||
throw ArgumentError(
|
||||
'`quotedMessage` can only be set as String or null',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}(), 'Validate type for quotedMessage');
|
||||
|
||||
return Message(
|
||||
id: id ?? this.id,
|
||||
text: text ?? this.text,
|
||||
type: type ?? this.type,
|
||||
attachments: attachments ?? this.attachments,
|
||||
mentionedUsers: mentionedUsers ?? this.mentionedUsers,
|
||||
silent: silent ?? this.silent,
|
||||
shadowed: shadowed ?? this.shadowed,
|
||||
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,
|
||||
quotedMessage: quotedMessage == _nullConst
|
||||
? this.quotedMessage
|
||||
: quotedMessage as Message?,
|
||||
quotedMessageId: quotedMessageId == _nullConst
|
||||
? _quotedMessageId
|
||||
: quotedMessageId as String?,
|
||||
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,
|
||||
createdAt: createdAt ?? _createdAt,
|
||||
updatedAt: updatedAt ?? _updatedAt,
|
||||
deletedAt: deletedAt ?? this.deletedAt,
|
||||
status: status ?? this.status,
|
||||
user: user ?? this.user,
|
||||
pinned: pinned ?? this.pinned,
|
||||
pinnedAt: pinnedAt ?? this.pinnedAt,
|
||||
pinnedBy: pinnedBy ?? this.pinnedBy,
|
||||
pinExpires:
|
||||
pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?,
|
||||
pinExpires == _nullConst ? this.pinExpires : pinExpires as DateTime?,
|
||||
pinnedBy: pinnedBy ?? this.pinnedBy,
|
||||
extraData: extraData ?? this.extraData,
|
||||
status: status ?? this.status,
|
||||
i18n: i18n ?? this.i18n,
|
||||
);
|
||||
}
|
||||
@@ -331,6 +368,8 @@ class Message extends Equatable {
|
||||
type: other.type,
|
||||
attachments: other.attachments,
|
||||
mentionedUsers: other.mentionedUsers,
|
||||
silent: other.silent,
|
||||
shadowed: other.shadowed,
|
||||
reactionCounts: other.reactionCounts,
|
||||
reactionScores: other.reactionScores,
|
||||
latestReactions: other.latestReactions,
|
||||
@@ -343,17 +382,15 @@ class Message extends Equatable {
|
||||
showInChannel: other.showInChannel,
|
||||
command: other.command,
|
||||
createdAt: other.createdAt,
|
||||
silent: other.silent,
|
||||
extraData: other.extraData,
|
||||
user: other.user,
|
||||
shadowed: other.shadowed,
|
||||
updatedAt: other.updatedAt,
|
||||
deletedAt: other.deletedAt,
|
||||
status: other.status,
|
||||
user: other.user,
|
||||
pinned: other.pinned,
|
||||
pinnedAt: other.pinnedAt,
|
||||
pinExpires: other.pinExpires,
|
||||
pinnedBy: other.pinnedBy,
|
||||
extraData: other.extraData,
|
||||
status: other.status,
|
||||
i18n: other.i18n,
|
||||
);
|
||||
|
||||
@@ -377,8 +414,8 @@ class Message extends Equatable {
|
||||
shadowed,
|
||||
silent,
|
||||
command,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
_createdAt,
|
||||
_updatedAt,
|
||||
deletedAt,
|
||||
user,
|
||||
pinned,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/// Describes capabilities of a user vis-a-vis a channel
|
||||
class PermissionType {
|
||||
/// Capability required to send a message in the channel
|
||||
/// Channel is not frozen (or user has UseFrozenChannel permission)
|
||||
/// and user has CreateMessage permission.
|
||||
static const String sendMessage = 'send-message';
|
||||
|
||||
/// Capability required to receive connect events in the channel
|
||||
static const String connectEvents = 'connect-events';
|
||||
|
||||
/// Capability required to send a message
|
||||
/// Reactions are enabled for the channel, channel is not frozen
|
||||
/// (or user has UseFrozenChannel permission) and user has
|
||||
/// CreateReaction permission
|
||||
static const String sendReaction = 'send-reaction';
|
||||
|
||||
/// Capability required to send links in a channel
|
||||
/// send-message + user has AddLinks permission
|
||||
static const String sendLinks = 'send-links';
|
||||
|
||||
/// Capability required to send thread reply
|
||||
/// send-message + channel has replies enabled
|
||||
static const String sendReply = 'send-reply';
|
||||
|
||||
/// Capability to freeze a channel
|
||||
/// User has UpdateChannelFrozen permission.
|
||||
/// The name implies freezing,
|
||||
/// but unfreezing is also allowed when this capability is present
|
||||
static const String freezeChannel = 'freeze-channel';
|
||||
|
||||
/// User has UpdateChannelCooldown permission.
|
||||
/// Allows to enable/disable slow mode in the channel
|
||||
static const String setChannelCooldown = 'set-channel-cooldown';
|
||||
|
||||
/// User has RemoveOwnChannelMembership or UpdateChannelMembers permission
|
||||
static const String leaveChannel = 'leave-channel';
|
||||
|
||||
/// User can mute channel
|
||||
static const String muteChannel = 'mute-channel';
|
||||
|
||||
/// Ability to receive read events
|
||||
static const String readEvents = 'read-events';
|
||||
|
||||
/// Capability required to pin a message in a channel
|
||||
/// Corresponds to PinMessage permission
|
||||
static const String pinMessage = 'pin-message';
|
||||
|
||||
/// Capability required to quote a message in a channel
|
||||
static const String quoteMessage = 'quote-message';
|
||||
|
||||
/// Capability required to flag a message in a channel
|
||||
static const String flagMessage = 'flag-message';
|
||||
|
||||
/// User has ability to delete any message in the channel
|
||||
/// User has DeleteMessage permission
|
||||
/// which applies to any message in the channel
|
||||
static const String deleteAnyMessage = 'delete-any-message';
|
||||
|
||||
/// User has ability to delete their own message in the channel
|
||||
/// User has DeleteMessage permission which applies only to owned messages
|
||||
static const String deleteOwnMessage = 'delete-own-message';
|
||||
|
||||
/// User has ability to update/edit any message in the channel
|
||||
/// User has UpdateMessage permission which
|
||||
/// applies to any message in the channel
|
||||
static const String updateAnyMessage = 'update-any-message';
|
||||
|
||||
/// User has ability to update/edit their own message in the channel
|
||||
/// User has UpdateMessage permission which applies only to owned messages
|
||||
static const String updateOwnMessage = 'update-own-message';
|
||||
|
||||
/// User can search for message in a channel
|
||||
/// Search feature is enabled (it will also have
|
||||
/// permission check in the future)
|
||||
static const String searchMessages = 'search-messages';
|
||||
|
||||
/// Capability required to send typing events in a channel
|
||||
/// (Typing events are enabled)
|
||||
static const String sendTypingEvents = 'send-typing-events';
|
||||
|
||||
/// Capability required to upload a file in a channel
|
||||
/// Uploads are enabled and user has UploadAttachment
|
||||
static const String uploadFile = 'upload-file';
|
||||
|
||||
/// Capability required to delete channel
|
||||
/// User has DeleteChannel permission
|
||||
static const String deleteChannel = 'delete-channel';
|
||||
|
||||
/// Capability required update/edit channel info
|
||||
/// User has UpdateChannel permission
|
||||
static const String updateChannel = 'update-channel';
|
||||
|
||||
/// Capability required to update/edit channel members
|
||||
/// Channel is not distinct and user has UpdateChannelMembers permission
|
||||
static const String updateChannelMembers = 'update-channel-members';
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export 'package:dio/src/options.dart';
|
||||
export 'package:dio/src/options.dart' show ProgressCallback;
|
||||
export 'package:logging/logging.dart' show Logger, Level;
|
||||
export 'package:rate_limiter/rate_limiter.dart';
|
||||
export 'package:uuid/uuid.dart';
|
||||
|
||||
export './src/core/api/attachment_file_uploader.dart'
|
||||
show AttachmentFileUploader;
|
||||
@@ -36,6 +37,7 @@ export './src/core/util/extension.dart';
|
||||
export './src/db/chat_persistence_client.dart';
|
||||
export './src/event_type.dart';
|
||||
export './src/location.dart';
|
||||
export './src/permission_type.dart';
|
||||
export './src/ws/connection_status.dart';
|
||||
export 'src/client/channel.dart';
|
||||
export 'src/client/client.dart';
|
||||
|
||||
@@ -244,9 +244,13 @@ void main() {
|
||||
|
||||
group('`.sendMessage`', () {
|
||||
test('should work fine', () async {
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
user: client.state.currentUser,
|
||||
);
|
||||
|
||||
final sendMessageResponse = SendMessageResponse()..message = message;
|
||||
final sendMessageResponse = SendMessageResponse()
|
||||
..message = message.copyWith(status: MessageSendingStatus.sent);
|
||||
|
||||
when(() => client.sendMessage(
|
||||
any(that: isSameMessageAs(message)),
|
||||
@@ -329,6 +333,7 @@ void main() {
|
||||
.map((it) =>
|
||||
it.copyWith(uploadState: const UploadState.success()))
|
||||
.toList(growable: false),
|
||||
status: MessageSendingStatus.sent,
|
||||
));
|
||||
|
||||
expectLater(
|
||||
@@ -455,7 +460,10 @@ void main() {
|
||||
|
||||
group('`.updateMessage`', () {
|
||||
test('should work fine', () async {
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
final updateMessageResponse = UpdateMessageResponse()
|
||||
..message = message;
|
||||
@@ -530,6 +538,7 @@ void main() {
|
||||
any(that: isSameMessageAs(message)),
|
||||
)).thenAnswer((_) async => UpdateMessageResponse()
|
||||
..message = message.copyWith(
|
||||
status: MessageSendingStatus.sent,
|
||||
attachments: attachments
|
||||
.map((it) =>
|
||||
it.copyWith(uploadState: const UploadState.success()))
|
||||
@@ -678,7 +687,7 @@ void main() {
|
||||
[
|
||||
isSameMessageAs(
|
||||
updateMessageResponse.message.copyWith(
|
||||
status: MessageSendingStatus.sent,
|
||||
status: MessageSendingStatus.sending,
|
||||
),
|
||||
matchText: true,
|
||||
matchSendingStatus: true,
|
||||
@@ -707,7 +716,10 @@ void main() {
|
||||
group('`.deleteMessage`', () {
|
||||
test('should work fine', () async {
|
||||
const messageId = 'test-message-id';
|
||||
final message = Message(id: messageId);
|
||||
final message = Message(
|
||||
id: messageId,
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
when(() => client.deleteMessage(messageId))
|
||||
.thenAnswer((_) async => EmptyResponse());
|
||||
@@ -744,7 +756,6 @@ void main() {
|
||||
const messageId = 'test-message-id';
|
||||
final message = Message(
|
||||
id: messageId,
|
||||
status: MessageSendingStatus.sending,
|
||||
);
|
||||
|
||||
expectLater(
|
||||
@@ -1077,7 +1088,10 @@ void main() {
|
||||
group('`.sendReaction`', () {
|
||||
test('should work fine', () async {
|
||||
const type = 'test-reaction-type';
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
final reaction = Reaction(type: type, messageId: message.id);
|
||||
|
||||
@@ -1120,7 +1134,10 @@ void main() {
|
||||
'should restore previous message if `client.sendReaction` throws',
|
||||
() async {
|
||||
const type = 'test-reaction-type';
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
final reaction = Reaction(type: type, messageId: message.id);
|
||||
|
||||
@@ -1181,6 +1198,7 @@ void main() {
|
||||
latestReactions: [prevReaction],
|
||||
reactionScores: const {prevType: 1},
|
||||
reactionCounts: const {prevType: 1},
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
const type = 'test-reaction-type-2';
|
||||
@@ -1212,7 +1230,7 @@ void main() {
|
||||
emitsInOrder([
|
||||
[
|
||||
isSameMessageAs(
|
||||
newMessage.copyWith(status: MessageSendingStatus.sent),
|
||||
newMessage,
|
||||
matchReactions: true,
|
||||
matchSendingStatus: true,
|
||||
),
|
||||
@@ -1255,6 +1273,7 @@ void main() {
|
||||
latestReactions: [reaction],
|
||||
reactionScores: const {type: 1},
|
||||
reactionCounts: const {type: 1},
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
when(() => client.deleteReaction(messageId, type))
|
||||
@@ -1302,6 +1321,7 @@ void main() {
|
||||
latestReactions: [reaction],
|
||||
reactionScores: const {type: 1},
|
||||
reactionCounts: const {type: 1},
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
when(() => client.deleteReaction(messageId, type))
|
||||
|
||||
@@ -481,14 +481,21 @@ void main() {
|
||||
|
||||
final path = '${_getChannelUrl(channelId, channelType)}/truncate';
|
||||
|
||||
when(() => client.post(path)).thenAnswer(
|
||||
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||
when(() => client.post(
|
||||
path,
|
||||
data: {},
|
||||
))
|
||||
.thenAnswer(
|
||||
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||
|
||||
final res = await channelApi.truncateChannel(channelId, channelType);
|
||||
|
||||
expect(res, isNotNull);
|
||||
|
||||
verify(() => client.post(path)).called(1);
|
||||
verify(() => client.post(
|
||||
path,
|
||||
data: {},
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(client);
|
||||
});
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ void main() {
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_push': false,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).thenAnswer((_) async => successResponse(path, data: {
|
||||
'message': message.toJson(),
|
||||
@@ -58,6 +59,7 @@ void main() {
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_push': true,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).thenAnswer((_) async => successResponse(path, data: {
|
||||
'message': message.toJson(),
|
||||
@@ -137,7 +139,10 @@ void main() {
|
||||
|
||||
when(() => client.post(
|
||||
path,
|
||||
data: {'message': message},
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).thenAnswer(
|
||||
(_) async => successResponse(path, data: {'message': message.toJson()}),
|
||||
);
|
||||
@@ -162,7 +167,11 @@ void main() {
|
||||
|
||||
when(() => client.put(
|
||||
path,
|
||||
data: {'set': set, 'unset': unset},
|
||||
data: {
|
||||
'set': set,
|
||||
'unset': unset,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).thenAnswer(
|
||||
(_) async => successResponse(path, data: {'message': message.toJson()}),
|
||||
);
|
||||
@@ -180,7 +189,11 @@ void main() {
|
||||
|
||||
verify(() => client.put(
|
||||
path,
|
||||
data: {'set': set, 'unset': unset},
|
||||
data: {
|
||||
'set': set,
|
||||
'unset': unset,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(client);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user