Merge branch 'develop'
This commit is contained in:
@@ -1,3 +1,17 @@
|
||||
## 3.3.0
|
||||
|
||||
✅ Added
|
||||
|
||||
- Extra properties added to `PaginationParams` to aid in fetching messages.
|
||||
- Added hard delete functionality.
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- `closeConnection()` now uses `normalClosure` status when closing websocket.
|
||||
- Fixed local unread count indicator increasing for thread replies.
|
||||
- Fixed user presence indicator not updating correctly.
|
||||
- `ChannelEvent.membersCount` defaults to 0 avoiding parsing errors due to missing `members_count` field.
|
||||
|
||||
## 3.2.1
|
||||
|
||||
🐞 Fixed
|
||||
@@ -726,4 +740,4 @@
|
||||
|
||||
## 0.0.2
|
||||
|
||||
- first beta version
|
||||
- first beta version
|
||||
@@ -233,10 +233,10 @@ class _MessageViewState extends State<MessageView> {
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -648,7 +648,7 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Deletes the [message] from the channel.
|
||||
Future<EmptyResponse> deleteMessage(Message message) async {
|
||||
Future<EmptyResponse> deleteMessage(Message message, {bool? hard}) async {
|
||||
// Directly deleting the local messages which are not yet sent to server
|
||||
if (message.status == MessageSendingStatus.sending ||
|
||||
message.status == MessageSendingStatus.failed) {
|
||||
@@ -675,7 +675,7 @@ class Channel {
|
||||
|
||||
state?.addMessage(message);
|
||||
|
||||
final response = await _client.deleteMessage(message.id);
|
||||
final response = await _client.deleteMessage(message.id, hard: hard);
|
||||
|
||||
state?.addMessage(message.copyWith(status: MessageSendingStatus.sent));
|
||||
|
||||
@@ -1603,7 +1603,7 @@ class ChannelClientState {
|
||||
message.createdAt.isBefore(
|
||||
DateTime.now().subtract(
|
||||
const Duration(
|
||||
seconds: 1,
|
||||
seconds: 5,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1663,7 +1663,11 @@ class ChannelClientState {
|
||||
void _listenMessageDeleted() {
|
||||
_subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) {
|
||||
final message = event.message!;
|
||||
addMessage(message);
|
||||
if (event.hardDelete == true) {
|
||||
removeMessage(message, hardDelete: true);
|
||||
} else {
|
||||
addMessage(message);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1718,7 +1722,7 @@ class ChannelClientState {
|
||||
}
|
||||
|
||||
/// Remove a [message] from this [channelState].
|
||||
void removeMessage(Message message) {
|
||||
void removeMessage(Message message, {bool hardDelete = false}) {
|
||||
final parentId = message.parentId;
|
||||
// i.e. it's a thread message
|
||||
// 1. Remove the thread message
|
||||
@@ -1740,7 +1744,10 @@ class ChannelClientState {
|
||||
} else {
|
||||
// Remove regular message
|
||||
final allMessages = [...messages];
|
||||
if (allMessages.remove(message)) {
|
||||
if (hardDelete) {
|
||||
allMessages.removeWhere((e) => e.id == message.id);
|
||||
_channelState = _channelState.copyWith(messages: allMessages);
|
||||
} else if (allMessages.remove(message)) {
|
||||
_channelState = _channelState.copyWith(messages: allMessages);
|
||||
}
|
||||
}
|
||||
@@ -1860,10 +1867,13 @@ class ChannelClientState {
|
||||
(m) => m.user.id == message.user?.id,
|
||||
) !=
|
||||
null;
|
||||
final isThreadMessage = message.parentId != null;
|
||||
|
||||
return !message.silent &&
|
||||
!message.shadowed &&
|
||||
message.user?.id != userId &&
|
||||
!userIsMuted;
|
||||
!userIsMuted &&
|
||||
!isThreadMessage;
|
||||
}
|
||||
|
||||
/// Update threads with updated information about messages.
|
||||
|
||||
@@ -44,10 +44,6 @@ final _levelEmojiMapper = {
|
||||
Level.SEVERE: '🚨',
|
||||
};
|
||||
|
||||
final _userAgent = 'stream-chat-dart-client-'
|
||||
'${CurrentPlatform.name}-'
|
||||
'${PACKAGE_VERSION.split('+')[0]}';
|
||||
|
||||
/// The official Dart client for Stream Chat,
|
||||
/// a service for building chat applications.
|
||||
/// This library can be used on any Dart project and on both mobile and web apps
|
||||
@@ -86,7 +82,7 @@ class StreamChatClient {
|
||||
location: location,
|
||||
connectTimeout: connectTimeout,
|
||||
receiveTimeout: receiveTimeout,
|
||||
headers: {'X-Stream-Client': _userAgent},
|
||||
headers: {'X-Stream-Client': defaultUserAgent},
|
||||
);
|
||||
|
||||
_chatApi = chatApi ??
|
||||
@@ -106,7 +102,7 @@ class StreamChatClient {
|
||||
tokenManager: _tokenManager,
|
||||
handler: handleEvent,
|
||||
logger: detachedLogger('🔌'),
|
||||
queryParameters: {'X-Stream-Client': _userAgent},
|
||||
queryParameters: {'X-Stream-Client': defaultUserAgent},
|
||||
);
|
||||
|
||||
_retryPolicy = retryPolicy ??
|
||||
@@ -131,6 +127,14 @@ class StreamChatClient {
|
||||
_originalChatPersistenceClient = value;
|
||||
}
|
||||
|
||||
/// Default user agent for all requests
|
||||
static String defaultUserAgent = 'stream-chat-dart-client-'
|
||||
'${CurrentPlatform.name}-'
|
||||
'${PACKAGE_VERSION.split('+')[0]}';
|
||||
|
||||
/// Additionals headers for all requests
|
||||
static Map<String, Object?> additionalHeaders = {};
|
||||
|
||||
ChatPersistenceClient? _originalChatPersistenceClient;
|
||||
|
||||
/// Chat persistence client
|
||||
@@ -1209,8 +1213,14 @@ class StreamChatClient {
|
||||
);
|
||||
|
||||
/// Deletes the given message
|
||||
Future<EmptyResponse> deleteMessage(String messageId) =>
|
||||
_chatApi.message.deleteMessage(messageId);
|
||||
Future<EmptyResponse> deleteMessage(String messageId, {bool? hard}) async {
|
||||
final response =
|
||||
await _chatApi.message.deleteMessage(messageId, hard: hard);
|
||||
if (hard == true) {
|
||||
await _chatPersistenceClient?.deleteMessageById(messageId);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/// Get a message by [messageId]
|
||||
Future<GetMessageResponse> getMessage(String messageId) =>
|
||||
@@ -1428,6 +1438,7 @@ class ClientState {
|
||||
.listen((Event event) async {
|
||||
final eventChannel = event.channel!;
|
||||
await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]);
|
||||
channels[eventChannel.cid]?.dispose();
|
||||
channels = channels..remove(eventChannel.cid);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -80,10 +80,16 @@ class MessageApi {
|
||||
|
||||
/// Deletes the given [messageId]
|
||||
Future<EmptyResponse> deleteMessage(
|
||||
String messageId,
|
||||
) async {
|
||||
String messageId, {
|
||||
bool? hard,
|
||||
}) async {
|
||||
final response = await _client.delete(
|
||||
'/messages/$messageId',
|
||||
queryParameters: hard != null
|
||||
? {
|
||||
'hard': hard,
|
||||
}
|
||||
: null,
|
||||
);
|
||||
return EmptyResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
@@ -60,8 +60,11 @@ class PaginationParams extends Equatable {
|
||||
/// ```
|
||||
const PaginationParams({
|
||||
this.limit = 10,
|
||||
this.before = 10,
|
||||
this.after = 10,
|
||||
this.offset,
|
||||
this.next,
|
||||
this.idAround,
|
||||
this.greaterThan,
|
||||
this.greaterThanOrEqual,
|
||||
this.lessThan,
|
||||
@@ -78,12 +81,22 @@ class PaginationParams extends Equatable {
|
||||
/// The amount of items requested from the APIs.
|
||||
final int limit;
|
||||
|
||||
/// The amount of items requested before message ID from the APIs.
|
||||
final int before;
|
||||
|
||||
/// The amount of items requested after message ID from the APIs.
|
||||
final int after;
|
||||
|
||||
/// The offset of requesting items.
|
||||
final int? offset;
|
||||
|
||||
/// A key used to paginate.
|
||||
final String? next;
|
||||
|
||||
/// Message ID to fetch messages around
|
||||
@JsonKey(name: 'id_around')
|
||||
final String? idAround;
|
||||
|
||||
/// Filter on ids greater than the given value.
|
||||
@JsonKey(name: 'id_gt')
|
||||
final String? greaterThan;
|
||||
@@ -106,7 +119,10 @@ class PaginationParams extends Equatable {
|
||||
/// Creates a copy of [PaginationParams] with specified attributes overridden.
|
||||
PaginationParams copyWith({
|
||||
int? limit,
|
||||
int? before,
|
||||
int? after,
|
||||
int? offset,
|
||||
String? idAround,
|
||||
String? next,
|
||||
String? greaterThan,
|
||||
String? greaterThanOrEqual,
|
||||
@@ -115,7 +131,10 @@ class PaginationParams extends Equatable {
|
||||
}) =>
|
||||
PaginationParams(
|
||||
limit: limit ?? this.limit,
|
||||
before: before ?? this.before,
|
||||
after: limit ?? this.after,
|
||||
offset: offset ?? this.offset,
|
||||
idAround: idAround ?? this.idAround,
|
||||
next: next ?? this.next,
|
||||
greaterThan: greaterThan ?? this.greaterThan,
|
||||
greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual,
|
||||
@@ -126,8 +145,11 @@ class PaginationParams extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
limit,
|
||||
before,
|
||||
after,
|
||||
offset,
|
||||
next,
|
||||
idAround,
|
||||
greaterThan,
|
||||
greaterThanOrEqual,
|
||||
lessThan,
|
||||
|
||||
@@ -21,8 +21,11 @@ Map<String, dynamic> _$SortOptionToJson<T>(SortOption<T> instance) =>
|
||||
PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) =>
|
||||
PaginationParams(
|
||||
limit: json['limit'] as int? ?? 10,
|
||||
before: json['before'] as int? ?? 10,
|
||||
after: json['after'] as int? ?? 10,
|
||||
offset: json['offset'] as int?,
|
||||
next: json['next'] as String?,
|
||||
idAround: json['id_around'] as String?,
|
||||
greaterThan: json['id_gt'] as String?,
|
||||
greaterThanOrEqual: json['id_gte'] as String?,
|
||||
lessThan: json['id_lt'] as String?,
|
||||
@@ -32,6 +35,8 @@ PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) =>
|
||||
Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
||||
final val = <String, dynamic>{
|
||||
'limit': instance.limit,
|
||||
'before': instance.before,
|
||||
'after': instance.after,
|
||||
};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
@@ -42,6 +47,7 @@ Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
||||
|
||||
writeNotNull('offset', instance.offset);
|
||||
writeNotNull('next', instance.next);
|
||||
writeNotNull('id_around', instance.idAround);
|
||||
writeNotNull('id_gt', instance.greaterThan);
|
||||
writeNotNull('id_gte', instance.greaterThanOrEqual);
|
||||
writeNotNull('id_lt', instance.lessThan);
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
/// Interceptor that sets additional headers for all requests.
|
||||
class AdditionalHeadersInterceptor extends Interceptor {
|
||||
@override
|
||||
Future<void> onRequest(
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler handler,
|
||||
) async {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
...StreamChatClient.additionalHeaders,
|
||||
};
|
||||
return handler.next(options);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:stream_chat/src/core/error/error.dart';
|
||||
import 'package:stream_chat/src/core/http/connection_id_manager.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/additional_headers_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart';
|
||||
@@ -41,6 +42,7 @@ class StreamHttpClient {
|
||||
..._options.headers,
|
||||
}
|
||||
..interceptors.addAll([
|
||||
AdditionalHeadersInterceptor(),
|
||||
if (tokenManager != null) AuthInterceptor(this, tokenManager),
|
||||
if (connectionIdManager != null)
|
||||
ConnectionIdInterceptor(connectionIdManager),
|
||||
|
||||
@@ -14,7 +14,7 @@ final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
UploadState _$UploadStateFromJson(Map<String, dynamic> json) {
|
||||
switch (json['runtimeType'] as String?) {
|
||||
switch (json['runtimeType']) {
|
||||
case 'preparing':
|
||||
return Preparing.fromJson(json);
|
||||
case 'inProgress':
|
||||
@@ -55,7 +55,7 @@ class _$UploadStateTearOff {
|
||||
);
|
||||
}
|
||||
|
||||
UploadState fromJson(Map<String, Object> json) {
|
||||
UploadState fromJson(Map<String, Object?> json) {
|
||||
return UploadState.fromJson(json);
|
||||
}
|
||||
}
|
||||
@@ -153,11 +153,14 @@ class _$PreparingCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$Preparing implements Preparing {
|
||||
const _$Preparing();
|
||||
const _$Preparing({String? $type}) : $type = $type ?? 'preparing';
|
||||
|
||||
factory _$Preparing.fromJson(Map<String, dynamic> json) =>
|
||||
_$$PreparingFromJson(json);
|
||||
|
||||
@JsonKey(name: 'runtimeType')
|
||||
final String $type;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UploadState.preparing()';
|
||||
@@ -165,7 +168,8 @@ class _$Preparing implements Preparing {
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) || (other is Preparing);
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is Preparing);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -247,7 +251,7 @@ class _$Preparing implements Preparing {
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$PreparingToJson(this)..['runtimeType'] = 'preparing';
|
||||
return _$$PreparingToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +299,9 @@ class _$InProgressCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$InProgress implements InProgress {
|
||||
const _$InProgress({required this.uploaded, required this.total});
|
||||
const _$InProgress(
|
||||
{required this.uploaded, required this.total, String? $type})
|
||||
: $type = $type ?? 'inProgress';
|
||||
|
||||
factory _$InProgress.fromJson(Map<String, dynamic> json) =>
|
||||
_$$InProgressFromJson(json);
|
||||
@@ -305,6 +311,9 @@ class _$InProgress implements InProgress {
|
||||
@override
|
||||
final int total;
|
||||
|
||||
@JsonKey(name: 'runtimeType')
|
||||
final String $type;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UploadState.inProgress(uploaded: $uploaded, total: $total)';
|
||||
@@ -313,19 +322,15 @@ class _$InProgress implements InProgress {
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other is InProgress &&
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is InProgress &&
|
||||
(identical(other.uploaded, uploaded) ||
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.uploaded, uploaded)) &&
|
||||
(identical(other.total, total) ||
|
||||
const DeepCollectionEquality().equals(other.total, total)));
|
||||
other.uploaded == uploaded) &&
|
||||
(identical(other.total, total) || other.total == total));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
runtimeType.hashCode ^
|
||||
const DeepCollectionEquality().hash(uploaded) ^
|
||||
const DeepCollectionEquality().hash(total);
|
||||
int get hashCode => Object.hash(runtimeType, uploaded, total);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -408,7 +413,7 @@ class _$InProgress implements InProgress {
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$InProgressToJson(this)..['runtimeType'] = 'inProgress';
|
||||
return _$$InProgressToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,8 +424,8 @@ abstract class InProgress implements UploadState {
|
||||
factory InProgress.fromJson(Map<String, dynamic> json) =
|
||||
_$InProgress.fromJson;
|
||||
|
||||
int get uploaded => throw _privateConstructorUsedError;
|
||||
int get total => throw _privateConstructorUsedError;
|
||||
int get uploaded;
|
||||
int get total;
|
||||
@JsonKey(ignore: true)
|
||||
$InProgressCopyWith<InProgress> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
@@ -445,11 +450,14 @@ class _$SuccessCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$Success implements Success {
|
||||
const _$Success();
|
||||
const _$Success({String? $type}) : $type = $type ?? 'success';
|
||||
|
||||
factory _$Success.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SuccessFromJson(json);
|
||||
|
||||
@JsonKey(name: 'runtimeType')
|
||||
final String $type;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UploadState.success()';
|
||||
@@ -457,7 +465,8 @@ class _$Success implements Success {
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) || (other is Success);
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is Success);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -539,7 +548,7 @@ class _$Success implements Success {
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$SuccessToJson(this)..['runtimeType'] = 'success';
|
||||
return _$$SuccessToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,7 +590,8 @@ class _$FailedCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$Failed implements Failed {
|
||||
const _$Failed({required this.error});
|
||||
const _$Failed({required this.error, String? $type})
|
||||
: $type = $type ?? 'failed';
|
||||
|
||||
factory _$Failed.fromJson(Map<String, dynamic> json) =>
|
||||
_$$FailedFromJson(json);
|
||||
@@ -589,6 +599,9 @@ class _$Failed implements Failed {
|
||||
@override
|
||||
final String error;
|
||||
|
||||
@JsonKey(name: 'runtimeType')
|
||||
final String $type;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UploadState.failed(error: $error)';
|
||||
@@ -597,14 +610,13 @@ class _$Failed implements Failed {
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other is Failed &&
|
||||
(identical(other.error, error) ||
|
||||
const DeepCollectionEquality().equals(other.error, error)));
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is Failed &&
|
||||
(identical(other.error, error) || other.error == error));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
runtimeType.hashCode ^ const DeepCollectionEquality().hash(error);
|
||||
int get hashCode => Object.hash(runtimeType, error);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -687,7 +699,7 @@ class _$Failed implements Failed {
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$FailedToJson(this)..['runtimeType'] = 'failed';
|
||||
return _$$FailedToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,7 +708,7 @@ abstract class Failed implements UploadState {
|
||||
|
||||
factory Failed.fromJson(Map<String, dynamic> json) = _$Failed.fromJson;
|
||||
|
||||
String get error => throw _privateConstructorUsedError;
|
||||
String get error;
|
||||
@JsonKey(ignore: true)
|
||||
$FailedCopyWith<Failed> get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
@@ -22,31 +22,42 @@ Map<String, dynamic> _$AttachmentFileToJson(AttachmentFile instance) =>
|
||||
'size': instance.size,
|
||||
};
|
||||
|
||||
_$Preparing _$$PreparingFromJson(Map<String, dynamic> json) => _$Preparing();
|
||||
_$Preparing _$$PreparingFromJson(Map<String, dynamic> json) => _$Preparing(
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PreparingToJson(_$Preparing instance) =>
|
||||
<String, dynamic>{};
|
||||
<String, dynamic>{
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
_$InProgress _$$InProgressFromJson(Map<String, dynamic> json) => _$InProgress(
|
||||
uploaded: json['uploaded'] as int,
|
||||
total: json['total'] as int,
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$InProgressToJson(_$InProgress instance) =>
|
||||
<String, dynamic>{
|
||||
'uploaded': instance.uploaded,
|
||||
'total': instance.total,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
_$Success _$$SuccessFromJson(Map<String, dynamic> json) => _$Success();
|
||||
_$Success _$$SuccessFromJson(Map<String, dynamic> json) => _$Success(
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SuccessToJson(_$Success instance) =>
|
||||
<String, dynamic>{};
|
||||
Map<String, dynamic> _$$SuccessToJson(_$Success instance) => <String, dynamic>{
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
_$Failed _$$FailedFromJson(Map<String, dynamic> json) => _$Failed(
|
||||
error: json['error'] as String,
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$FailedToJson(_$Failed instance) => <String, dynamic>{
|
||||
'error': instance.error,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ class Event {
|
||||
this.channelId,
|
||||
this.channelType,
|
||||
this.parentId,
|
||||
this.hardDelete,
|
||||
this.extraData = const {},
|
||||
this.isLocal = true,
|
||||
}) : createdAt = createdAt?.toUtc() ?? DateTime.now().toUtc();
|
||||
@@ -91,6 +92,10 @@ class Event {
|
||||
@JsonKey(defaultValue: false)
|
||||
final bool isLocal;
|
||||
|
||||
/// This is true if the message has been hard deleted
|
||||
@JsonKey(includeIfNull: false)
|
||||
final bool? hardDelete;
|
||||
|
||||
/// Map of custom channel extraData
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
@@ -113,6 +118,7 @@ class Event {
|
||||
'channel_id',
|
||||
'channel_type',
|
||||
'parent_id',
|
||||
'hard_delete',
|
||||
'is_local',
|
||||
];
|
||||
|
||||
@@ -139,6 +145,7 @@ class Event {
|
||||
int? unreadChannels,
|
||||
bool? online,
|
||||
String? parentId,
|
||||
bool? hardDelete,
|
||||
Map<String, Object?>? extraData,
|
||||
}) =>
|
||||
Event(
|
||||
@@ -158,6 +165,7 @@ class Event {
|
||||
channelId: channelId ?? this.channelId,
|
||||
channelType: channelType ?? this.channelType,
|
||||
parentId: parentId ?? this.parentId,
|
||||
hardDelete: hardDelete ?? this.hardDelete,
|
||||
extraData: extraData ?? this.extraData,
|
||||
isLocal: isLocal,
|
||||
);
|
||||
@@ -181,7 +189,7 @@ class EventChannel extends ChannelModel {
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
required int memberCount,
|
||||
int memberCount = 0,
|
||||
Map<String, Object?>? extraData,
|
||||
int cooldown = 0,
|
||||
String? team,
|
||||
|
||||
@@ -37,30 +37,42 @@ Event _$EventFromJson(Map<String, dynamic> json) => Event(
|
||||
channelId: json['channel_id'] as String?,
|
||||
channelType: json['channel_type'] as String?,
|
||||
parentId: json['parent_id'] as String?,
|
||||
hardDelete: json['hard_delete'] as bool?,
|
||||
extraData: json['extra_data'] as Map<String, dynamic>? ?? const {},
|
||||
isLocal: json['is_local'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$EventToJson(Event instance) => <String, dynamic>{
|
||||
'type': instance.type,
|
||||
'cid': instance.cid,
|
||||
'channel_id': instance.channelId,
|
||||
'channel_type': instance.channelType,
|
||||
'connection_id': instance.connectionId,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'me': instance.me?.toJson(),
|
||||
'user': instance.user?.toJson(),
|
||||
'message': instance.message?.toJson(),
|
||||
'channel': instance.channel?.toJson(),
|
||||
'member': instance.member?.toJson(),
|
||||
'reaction': instance.reaction?.toJson(),
|
||||
'total_unread_count': instance.totalUnreadCount,
|
||||
'unread_channels': instance.unreadChannels,
|
||||
'online': instance.online,
|
||||
'parent_id': instance.parentId,
|
||||
'is_local': instance.isLocal,
|
||||
'extra_data': instance.extraData,
|
||||
};
|
||||
Map<String, dynamic> _$EventToJson(Event instance) {
|
||||
final val = <String, dynamic>{
|
||||
'type': instance.type,
|
||||
'cid': instance.cid,
|
||||
'channel_id': instance.channelId,
|
||||
'channel_type': instance.channelType,
|
||||
'connection_id': instance.connectionId,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'me': instance.me?.toJson(),
|
||||
'user': instance.user?.toJson(),
|
||||
'message': instance.message?.toJson(),
|
||||
'channel': instance.channel?.toJson(),
|
||||
'member': instance.member?.toJson(),
|
||||
'reaction': instance.reaction?.toJson(),
|
||||
'total_unread_count': instance.totalUnreadCount,
|
||||
'unread_channels': instance.unreadChannels,
|
||||
'online': instance.online,
|
||||
'parent_id': instance.parentId,
|
||||
'is_local': instance.isLocal,
|
||||
};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('hard_delete', instance.hardDelete);
|
||||
val['extra_data'] = instance.extraData;
|
||||
return val;
|
||||
}
|
||||
|
||||
EventChannel _$EventChannelFromJson(Map<String, dynamic> json) => EventChannel(
|
||||
members: (json['members'] as List<dynamic>?)
|
||||
@@ -82,7 +94,7 @@ EventChannel _$EventChannelFromJson(Map<String, dynamic> json) => EventChannel(
|
||||
deletedAt: json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
memberCount: json['member_count'] as int,
|
||||
memberCount: json['member_count'] as int? ?? 0,
|
||||
extraData: json['extra_data'] as Map<String, dynamic>?,
|
||||
cooldown: json['cooldown'] as int? ?? 0,
|
||||
team: json['team'] as String?,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/core/models/user.dart';
|
||||
|
||||
@@ -5,9 +6,9 @@ part 'read.g.dart';
|
||||
|
||||
/// The class that defines a read event
|
||||
@JsonSerializable()
|
||||
class Read {
|
||||
class Read extends Equatable {
|
||||
/// Constructor used for json serialization
|
||||
Read({
|
||||
const Read({
|
||||
required this.lastRead,
|
||||
required this.user,
|
||||
this.unreadMessages = 0,
|
||||
@@ -39,4 +40,11 @@ class Read {
|
||||
user: user ?? this.user,
|
||||
unreadMessages: unreadMessages ?? this.unreadMessages,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
lastRead,
|
||||
user,
|
||||
unreadMessages,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -179,5 +179,14 @@ class User extends Equatable {
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id, role];
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
role,
|
||||
lastActive,
|
||||
online,
|
||||
extraData,
|
||||
banned,
|
||||
teams,
|
||||
language,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ abstract class ChatPersistenceClient {
|
||||
members: data[0] as List<Member>,
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
read: data[1] as List<Read>,
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
channel: data[2] as ChannelModel?,
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
messages: data[3] as List<Message>,
|
||||
|
||||
@@ -121,7 +121,8 @@ class WebSocket with TimerHelper {
|
||||
_logger?.info('Closing connection with $baseUrl');
|
||||
if (_webSocketChannel != null) {
|
||||
_unsubscribeFromWebSocketChannel();
|
||||
_webSocketChannel?.sink.close(status.goingAway);
|
||||
_webSocketChannel?.sink
|
||||
.close(_manuallyClosed ? status.normalClosure : status.goingAway);
|
||||
_webSocketChannel = null;
|
||||
}
|
||||
}
|
||||
@@ -309,7 +310,10 @@ class WebSocket with TimerHelper {
|
||||
Event? event;
|
||||
try {
|
||||
event = Event.fromJson(jsonData);
|
||||
} catch (_) {}
|
||||
} catch (e, stk) {
|
||||
_logger?.warning('Error parsing an event: $e');
|
||||
_logger?.warning('Stack trace: $stk');
|
||||
}
|
||||
|
||||
if (event == null) return;
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
|
||||
/// Current package version
|
||||
/// Used in [StreamChatClient] to build the `x-stream-client` header
|
||||
// ignore: constant_identifier_names
|
||||
const PACKAGE_VERSION = '3.2.1';
|
||||
const PACKAGE_VERSION = '3.3.0';
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
name: stream_chat
|
||||
homepage: https://getstream.io/
|
||||
description: The official Dart client for Stream Chat, a service for building chat applications.
|
||||
<<<<<<< HEAD
|
||||
version: 3.2.1
|
||||
=======
|
||||
version: 3.3.0
|
||||
>>>>>>> develop
|
||||
repository: https://github.com/GetStream/stream-chat-flutter
|
||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||
|
||||
@@ -13,10 +17,10 @@ dependencies:
|
||||
collection: ^1.15.0
|
||||
dio: ^4.0.0
|
||||
equatable: ^2.0.0
|
||||
freezed_annotation: ^0.15.0
|
||||
freezed_annotation: ^1.0.0
|
||||
http_parser: ^4.0.0
|
||||
jose: ^0.3.2
|
||||
json_annotation: ^4.0.1
|
||||
json_annotation: ^4.3.0
|
||||
logging: ^1.0.1
|
||||
meta: ^1.3.0
|
||||
mime: ^1.0.0
|
||||
@@ -28,7 +32,7 @@ dependencies:
|
||||
dev_dependencies:
|
||||
build_runner: ^2.0.1
|
||||
dart_code_metrics: ^4.4.0
|
||||
freezed: ^0.15.0+1
|
||||
freezed: ^1.0.0
|
||||
json_serializable: ^6.0.1
|
||||
mocktail: ^0.2.0
|
||||
test: ^1.17.12
|
||||
@@ -118,9 +118,9 @@ void main() {
|
||||
|
||||
setUpAll(() {
|
||||
// Fallback values
|
||||
registerFallbackValue<Message>(FakeMessage());
|
||||
registerFallbackValue<List<Message>>(<Message>[]);
|
||||
registerFallbackValue<AttachmentFile>(FakeAttachmentFile());
|
||||
registerFallbackValue(FakeMessage());
|
||||
registerFallbackValue(<Message>[]);
|
||||
registerFallbackValue(FakeAttachmentFile());
|
||||
|
||||
// detached loggers
|
||||
when(() => client.detachedLogger(any())).thenAnswer((invocation) {
|
||||
@@ -176,9 +176,9 @@ void main() {
|
||||
|
||||
setUpAll(() {
|
||||
// Fallback values
|
||||
registerFallbackValue<Message>(FakeMessage());
|
||||
registerFallbackValue<AttachmentFile>(FakeAttachmentFile());
|
||||
registerFallbackValue<Event>(FakeEvent());
|
||||
registerFallbackValue(FakeMessage());
|
||||
registerFallbackValue(FakeAttachmentFile());
|
||||
registerFallbackValue(FakeEvent());
|
||||
|
||||
// detached loggers
|
||||
when(() => client.detachedLogger(any())).thenAnswer((invocation) {
|
||||
|
||||
@@ -31,7 +31,7 @@ void main() {
|
||||
|
||||
setUpAll(() {
|
||||
// fallback values
|
||||
registerFallbackValue<User>(FakeUser());
|
||||
registerFallbackValue(FakeUser());
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
@@ -230,7 +230,7 @@ void main() {
|
||||
|
||||
setUpAll(() {
|
||||
// fallback values
|
||||
registerFallbackValue<User>(FakeUser());
|
||||
registerFallbackValue(FakeUser());
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
@@ -311,7 +311,7 @@ void main() {
|
||||
|
||||
setUpAll(() {
|
||||
// fallback values
|
||||
registerFallbackValue<User>(FakeUser());
|
||||
registerFallbackValue(FakeUser());
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
@@ -399,7 +399,7 @@ void main() {
|
||||
|
||||
setUpAll(() {
|
||||
// fallback values
|
||||
registerFallbackValue<User>(FakeUser());
|
||||
registerFallbackValue(FakeUser());
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
@@ -523,9 +523,9 @@ void main() {
|
||||
|
||||
setUpAll(() {
|
||||
// fallback values
|
||||
registerFallbackValue<Event>(FakeEvent());
|
||||
registerFallbackValue<PaginationParams>(const PaginationParams());
|
||||
registerFallbackValue<ChannelState>(FakeChannelState());
|
||||
registerFallbackValue(FakeEvent());
|
||||
registerFallbackValue(const PaginationParams());
|
||||
registerFallbackValue(FakeChannelState());
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
@@ -827,9 +827,9 @@ void main() {
|
||||
|
||||
setUpAll(() {
|
||||
// fallback values
|
||||
registerFallbackValue<Event>(FakeEvent());
|
||||
registerFallbackValue<Message>(FakeMessage());
|
||||
registerFallbackValue<PaginationParams>(const PaginationParams());
|
||||
registerFallbackValue(FakeEvent());
|
||||
registerFallbackValue(FakeMessage());
|
||||
registerFallbackValue(const PaginationParams());
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
|
||||
@@ -15,7 +15,7 @@ void main() {
|
||||
|
||||
setUp(() {
|
||||
fileUploader = StreamAttachmentFileUploader(client);
|
||||
registerFallbackValue<MultipartFile>(FakeMultiPartFile());
|
||||
registerFallbackValue(FakeMultiPartFile());
|
||||
});
|
||||
|
||||
Response successResponse(String path, {Object? data}) => Response(
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/additional_headers_interceptor.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
late AdditionalHeadersInterceptor additionalHeadersInterceptor;
|
||||
|
||||
setUp(() {
|
||||
additionalHeadersInterceptor = AdditionalHeadersInterceptor();
|
||||
});
|
||||
|
||||
test(
|
||||
'`onRequest` should add additional headers in the request',
|
||||
() async {
|
||||
final options = RequestOptions(path: 'test-path');
|
||||
final handler = RequestInterceptorHandler();
|
||||
|
||||
StreamChatClient.additionalHeaders = {'test-header': 'test-value'};
|
||||
additionalHeadersInterceptor.onRequest(options, handler);
|
||||
|
||||
final updatedOptions = (await handler.future).data as RequestOptions;
|
||||
final updateHeaders = updatedOptions.headers;
|
||||
|
||||
expect(updateHeaders.containsKey('test-header'), isTrue);
|
||||
expect(updateHeaders['test-header'], 'test-value');
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'package:mocktail/mocktail.dart';
|
||||
import 'package:stream_chat/src/core/api/responses.dart';
|
||||
import 'package:stream_chat/src/core/error/error.dart';
|
||||
import 'package:stream_chat/src/core/http/connection_id_manager.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/additional_headers_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart';
|
||||
@@ -48,12 +49,23 @@ void main() {
|
||||
return dioError;
|
||||
}
|
||||
|
||||
test('UserAgentInterceptor should be added', () {
|
||||
const apiKey = 'api-key';
|
||||
final client = StreamHttpClient(apiKey);
|
||||
|
||||
expect(
|
||||
client.httpClient.interceptors
|
||||
.whereType<AdditionalHeadersInterceptor>()
|
||||
.length,
|
||||
1);
|
||||
});
|
||||
|
||||
test('AuthInterceptor should be added if tokenManager is provided', () {
|
||||
const apiKey = 'api-key';
|
||||
final client = StreamHttpClient(apiKey, tokenManager: TokenManager());
|
||||
|
||||
expect(client.httpClient.interceptors.length, 1);
|
||||
expect(client.httpClient.interceptors.first, isA<AuthInterceptor>());
|
||||
expect(
|
||||
client.httpClient.interceptors.whereType<AuthInterceptor>().length, 1);
|
||||
});
|
||||
|
||||
test(
|
||||
@@ -65,10 +77,11 @@ void main() {
|
||||
connectionIdManager: ConnectionIdManager(),
|
||||
);
|
||||
|
||||
expect(client.httpClient.interceptors.length, 1);
|
||||
expect(
|
||||
client.httpClient.interceptors.first,
|
||||
isA<ConnectionIdInterceptor>(),
|
||||
client.httpClient.interceptors
|
||||
.whereType<ConnectionIdInterceptor>()
|
||||
.length,
|
||||
1,
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -80,10 +93,9 @@ void main() {
|
||||
logger: Logger('test-logger'),
|
||||
);
|
||||
|
||||
expect(client.httpClient.interceptors.length, 1);
|
||||
expect(
|
||||
client.httpClient.interceptors.first,
|
||||
isA<LoggingInterceptor>(),
|
||||
client.httpClient.interceptors.whereType<LoggingInterceptor>().length,
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -111,27 +123,6 @@ void main() {
|
||||
verify(() => logger.severe(any())).called(greaterThan(0));
|
||||
});
|
||||
|
||||
test('`.lock` should lock the dio client', () async {
|
||||
final client = StreamHttpClient('api-key');
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isFalse);
|
||||
client.lock();
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isTrue);
|
||||
});
|
||||
|
||||
test('`.unlock` should unlock the dio client', () async {
|
||||
final client = StreamHttpClient('api-key');
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isFalse);
|
||||
client.lock();
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isTrue);
|
||||
client.unlock();
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isFalse);
|
||||
});
|
||||
|
||||
test('`.clear` should clear and unlock the dio client', () async {
|
||||
final client = StreamHttpClient('api-key')..clear();
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isFalse);
|
||||
});
|
||||
|
||||
test('`.close` should close the dio client', () async {
|
||||
final client = StreamHttpClient('api-key')..close(force: true);
|
||||
try {
|
||||
|
||||
@@ -70,13 +70,19 @@ void main() {
|
||||
expect(
|
||||
newReaction.extraData, {'updated_at': '2020-01-28T22:17:31.108742Z'});
|
||||
|
||||
final newUserCreateTime = DateTime.now();
|
||||
|
||||
newReaction = reaction.copyWith(
|
||||
type: 'lol',
|
||||
createdAt: DateTime.parse('2021-01-28T22:17:31.108742Z'),
|
||||
extraData: {},
|
||||
messageId: 'test',
|
||||
score: 2,
|
||||
user: User(id: 'test'),
|
||||
user: User(
|
||||
id: 'test',
|
||||
createdAt: newUserCreateTime,
|
||||
updatedAt: newUserCreateTime,
|
||||
),
|
||||
userId: 'test',
|
||||
);
|
||||
|
||||
@@ -88,12 +94,21 @@ void main() {
|
||||
expect(newReaction.extraData, {});
|
||||
expect(newReaction.messageId, 'test');
|
||||
expect(newReaction.score, 2);
|
||||
expect(newReaction.user, User(id: 'test'));
|
||||
expect(
|
||||
newReaction.user,
|
||||
User(
|
||||
id: 'test',
|
||||
createdAt: newUserCreateTime,
|
||||
updatedAt: newUserCreateTime,
|
||||
),
|
||||
);
|
||||
expect(newReaction.userId, 'test');
|
||||
});
|
||||
|
||||
test('merge', () {
|
||||
final reaction = Reaction.fromJson(jsonFixture('reaction.json'));
|
||||
final newUserCreateTime = DateTime.now();
|
||||
|
||||
final newReaction = reaction.merge(
|
||||
Reaction(
|
||||
type: 'lol',
|
||||
@@ -101,7 +116,11 @@ void main() {
|
||||
extraData: {},
|
||||
messageId: 'test',
|
||||
score: 2,
|
||||
user: User(id: 'test'),
|
||||
user: User(
|
||||
id: 'test',
|
||||
createdAt: newUserCreateTime,
|
||||
updatedAt: newUserCreateTime,
|
||||
),
|
||||
userId: 'test',
|
||||
),
|
||||
);
|
||||
@@ -114,7 +133,14 @@ void main() {
|
||||
expect(newReaction.extraData, {});
|
||||
expect(newReaction.messageId, 'test');
|
||||
expect(newReaction.score, 2);
|
||||
expect(newReaction.user, User(id: 'test'));
|
||||
expect(
|
||||
newReaction.user,
|
||||
User(
|
||||
id: 'test',
|
||||
createdAt: newUserCreateTime,
|
||||
updatedAt: newUserCreateTime,
|
||||
),
|
||||
);
|
||||
expect(newReaction.userId, 'test');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user