@@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:collection/collection.dart'
|
import 'package:collection/collection.dart'
|
||||||
@@ -10,9 +9,9 @@ import 'package:rxdart/rxdart.dart';
|
|||||||
import 'package:stream_chat/src/api/retry_queue.dart';
|
import 'package:stream_chat/src/api/retry_queue.dart';
|
||||||
import 'package:stream_chat/src/event_type.dart';
|
import 'package:stream_chat/src/event_type.dart';
|
||||||
import 'package:stream_chat/src/extensions/rate_limit.dart';
|
import 'package:stream_chat/src/extensions/rate_limit.dart';
|
||||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
||||||
import 'package:stream_chat/src/models/channel_state.dart';
|
import 'package:stream_chat/src/core/models/channel_state.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
/// This a the class that manages a specific channel.
|
/// This a the class that manages a specific channel.
|
||||||
@@ -214,8 +213,6 @@ class Channel {
|
|||||||
StreamChatClient get client => _client;
|
StreamChatClient get client => _client;
|
||||||
final StreamChatClient _client;
|
final StreamChatClient _client;
|
||||||
|
|
||||||
String get _channelURL => '/channels/$type/$id';
|
|
||||||
|
|
||||||
final Completer<bool> _initializedCompleter = Completer();
|
final Completer<bool> _initializedCompleter = Completer();
|
||||||
|
|
||||||
/// True if this is initialized
|
/// True if this is initialized
|
||||||
@@ -492,7 +489,7 @@ class Channel {
|
|||||||
|
|
||||||
state?.addMessage(message);
|
state?.addMessage(message);
|
||||||
|
|
||||||
final response = await _client.deleteMessage(message);
|
final response = await _client.deleteMessage(message.id);
|
||||||
|
|
||||||
state?.addMessage(message.copyWith(status: MessageSendingStatus.sent));
|
state?.addMessage(message.copyWith(status: MessageSendingStatus.sent));
|
||||||
|
|
||||||
@@ -507,9 +504,9 @@ class Channel {
|
|||||||
|
|
||||||
/// Pins provided message
|
/// Pins provided message
|
||||||
Future<UpdateMessageResponse> pinMessage(
|
Future<UpdateMessageResponse> pinMessage(
|
||||||
Message message,
|
Message message, {
|
||||||
Object? timeoutOrExpirationDate,
|
Object? /*num|DateTime*/ timeoutOrExpirationDate,
|
||||||
) {
|
}) {
|
||||||
assert(() {
|
assert(() {
|
||||||
if (timeoutOrExpirationDate is! DateTime &&
|
if (timeoutOrExpirationDate is! DateTime &&
|
||||||
timeoutOrExpirationDate != null &&
|
timeoutOrExpirationDate != null &&
|
||||||
@@ -517,7 +514,7 @@ class Channel {
|
|||||||
throw ArgumentError('Invalid timeout or Expiration date');
|
throw ArgumentError('Invalid timeout or Expiration date');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}(), 'Check for invalid token or expiration date');
|
}(), 'Check for invalid timeout or expiration date');
|
||||||
|
|
||||||
DateTime? pinExpires;
|
DateTime? pinExpires;
|
||||||
if (timeoutOrExpirationDate is DateTime) {
|
if (timeoutOrExpirationDate is DateTime) {
|
||||||
@@ -619,10 +616,7 @@ class Channel {
|
|||||||
/// Send an event on this channel
|
/// Send an event on this channel
|
||||||
Future<EmptyResponse> sendEvent(Event event) {
|
Future<EmptyResponse> sendEvent(Event event) {
|
||||||
_checkInitialized();
|
_checkInitialized();
|
||||||
return _client.post(
|
return _client.sendEvent(id!, type, event);
|
||||||
'$_channelURL/event',
|
|
||||||
data: {'event': event.toJson()},
|
|
||||||
).then((res) => _client.decode(res.data, EmptyResponse.fromJson)!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a reaction to this channel
|
/// Send a reaction to this channel
|
||||||
@@ -674,21 +668,13 @@ class Channel {
|
|||||||
|
|
||||||
state?.addMessage(newMessage);
|
state?.addMessage(newMessage);
|
||||||
|
|
||||||
final data = Map<String, dynamic>.from(extraData)
|
|
||||||
..addAll({
|
|
||||||
'type': type,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final res = await _client.post(
|
final reactionResp = await _client.sendReaction(
|
||||||
'/messages/$messageId/reaction',
|
messageId,
|
||||||
data: {
|
type,
|
||||||
'reaction': data,
|
extraData: extraData,
|
||||||
'enforce_unique': enforceUnique,
|
enforceUnique: enforceUnique,
|
||||||
},
|
|
||||||
);
|
);
|
||||||
final reactionResp =
|
|
||||||
_client.decode(res.data, SendReactionResponse.fromJson);
|
|
||||||
return reactionResp;
|
return reactionResp;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Reset the message if the update fails
|
// Reset the message if the update fails
|
||||||
@@ -731,9 +717,11 @@ class Channel {
|
|||||||
state?.addMessage(newMessage);
|
state?.addMessage(newMessage);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final res = await client
|
final deleteResponse = await _client.deleteReaction(
|
||||||
.delete('/messages/${message.id}/reaction/${reaction.type}');
|
message.id,
|
||||||
return _client.decode(res.data, EmptyResponse.fromJson);
|
reaction.type,
|
||||||
|
);
|
||||||
|
return deleteResponse;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Reset the message if the update fails
|
// Reset the message if the update fails
|
||||||
state?.addMessage(message);
|
state?.addMessage(message);
|
||||||
@@ -746,45 +734,45 @@ class Channel {
|
|||||||
Map<String, dynamic> channelData, [
|
Map<String, dynamic> channelData, [
|
||||||
Message? updateMessage,
|
Message? updateMessage,
|
||||||
]) async {
|
]) async {
|
||||||
final response = await _client.post(_channelURL, data: {
|
_checkInitialized();
|
||||||
if (updateMessage != null)
|
return _client.updateChannel(
|
||||||
'message': updateMessage.copyWith(updatedAt: DateTime.now()).toJson(),
|
id!,
|
||||||
'data': channelData,
|
type,
|
||||||
});
|
channelData,
|
||||||
return _client.decode(response.data, UpdateChannelResponse.fromJson);
|
message: updateMessage,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Edit the channel custom data
|
/// Edit the channel custom data
|
||||||
Future<PartialUpdateChannelResponse> updatePartial(
|
Future<PartialUpdateChannelResponse> updatePartial(
|
||||||
Map<String, dynamic> channelData) async {
|
Map<String, dynamic> channelData,
|
||||||
final response = await _client.patch(_channelURL, data: channelData);
|
) async {
|
||||||
return _client.decode(response.data, PartialUpdateChannelResponse.fromJson);
|
_checkInitialized();
|
||||||
|
return _client.updateChannelPartial(id!, type, channelData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete this channel. Messages are permanently removed.
|
/// Delete this channel. Messages are permanently removed.
|
||||||
Future<EmptyResponse> delete() async {
|
Future<EmptyResponse> delete() async {
|
||||||
final response = await _client.delete(_channelURL);
|
_checkInitialized();
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
return _client.deleteChannel(id!, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes all messages from the channel
|
/// Removes all messages from the channel
|
||||||
Future<EmptyResponse> truncate() async {
|
Future<EmptyResponse> truncate() async {
|
||||||
final response = await _client.post('$_channelURL/truncate');
|
_checkInitialized();
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
return _client.truncateChannel(id!, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Accept invitation to the channel
|
/// Accept invitation to the channel
|
||||||
Future<AcceptInviteResponse> acceptInvite([Message? message]) async {
|
Future<AcceptInviteResponse> acceptInvite([Message? message]) async {
|
||||||
final res = await _client.post(_channelURL,
|
_checkInitialized();
|
||||||
data: {'accept_invite': true, 'message': message?.toJson()});
|
return _client.acceptChannelInvite(id!, type, message: message);
|
||||||
return _client.decode(res.data, AcceptInviteResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reject invitation to the channel
|
/// Reject invitation to the channel
|
||||||
Future<RejectInviteResponse> rejectInvite([Message? message]) async {
|
Future<RejectInviteResponse> rejectInvite([Message? message]) async {
|
||||||
final res = await _client.post(_channelURL,
|
_checkInitialized();
|
||||||
data: {'reject_invite': true, 'message': message?.toJson()});
|
return _client.rejectChannelInvite(id!, type, message: message);
|
||||||
return _client.decode(res.data, RejectInviteResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add members to the channel
|
/// Add members to the channel
|
||||||
@@ -792,11 +780,8 @@ class Channel {
|
|||||||
List<String> memberIds, [
|
List<String> memberIds, [
|
||||||
Message? message,
|
Message? message,
|
||||||
]) async {
|
]) async {
|
||||||
final res = await _client.post(_channelURL, data: {
|
_checkInitialized();
|
||||||
'add_members': memberIds,
|
return _client.addChannelMembers(id!, type, memberIds, message: message);
|
||||||
'message': message?.toJson(),
|
|
||||||
});
|
|
||||||
return _client.decode(res.data, AddMembersResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invite members to the channel
|
/// Invite members to the channel
|
||||||
@@ -804,11 +789,8 @@ class Channel {
|
|||||||
List<String> memberIds, [
|
List<String> memberIds, [
|
||||||
Message? message,
|
Message? message,
|
||||||
]) async {
|
]) async {
|
||||||
final res = await _client.post(_channelURL, data: {
|
_checkInitialized();
|
||||||
'invites': memberIds,
|
return _client.inviteChannelMembers(id!, type, memberIds, message: message);
|
||||||
'message': message?.toJson(),
|
|
||||||
});
|
|
||||||
return _client.decode(res.data, InviteMembersResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove members from the channel
|
/// Remove members from the channel
|
||||||
@@ -816,11 +798,8 @@ class Channel {
|
|||||||
List<String> memberIds, [
|
List<String> memberIds, [
|
||||||
Message? message,
|
Message? message,
|
||||||
]) async {
|
]) async {
|
||||||
final res = await _client.post(_channelURL, data: {
|
_checkInitialized();
|
||||||
'remove_members': memberIds,
|
return _client.removeChannelMembers(id!, type, memberIds, message: message);
|
||||||
'message': message?.toJson(),
|
|
||||||
});
|
|
||||||
return _client.decode(res.data, RemoveMembersResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send action for a specific message of this channel
|
/// Send action for a specific message of this channel
|
||||||
@@ -829,16 +808,8 @@ class Channel {
|
|||||||
Map<String, dynamic> formData,
|
Map<String, dynamic> formData,
|
||||||
) async {
|
) async {
|
||||||
_checkInitialized();
|
_checkInitialized();
|
||||||
|
|
||||||
final messageId = message.id;
|
final messageId = message.id;
|
||||||
final response = await _client.post('/messages/$messageId/action', data: {
|
final res = await _client.sendAction(id!, type, messageId, formData);
|
||||||
'id': id,
|
|
||||||
'type': type,
|
|
||||||
'form_data': formData,
|
|
||||||
'message_id': messageId,
|
|
||||||
});
|
|
||||||
|
|
||||||
final res = _client.decode(response.data, SendActionResponse.fromJson);
|
|
||||||
|
|
||||||
if (res.message != null) {
|
if (res.message != null) {
|
||||||
state!.addMessage(res.message!);
|
state!.addMessage(res.message!);
|
||||||
@@ -867,21 +838,20 @@ class Channel {
|
|||||||
state!.threads[oldMessage.parentId!]!..remove(oldMessage));
|
state!.threads[oldMessage.parentId!]!..remove(oldMessage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _client.chatPersistenceClient?.deleteMessageById(messageId);
|
await _client.chatPersistenceClient?.deleteMessageById(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark all channel messages as read
|
/// Mark all messages as read
|
||||||
Future<EmptyResponse> markRead() async {
|
/// Optionally provide a [messageId] if you want to mark a
|
||||||
|
/// particular message as read
|
||||||
|
Future<EmptyResponse> markRead({String? messageId}) async {
|
||||||
_checkInitialized();
|
_checkInitialized();
|
||||||
client.state.totalUnreadCount = max(
|
client.state.totalUnreadCount = max(
|
||||||
0, (client.state.totalUnreadCount ?? 0) - (state!.unreadCount ?? 0));
|
0, (client.state.totalUnreadCount ?? 0) - (state!.unreadCount ?? 0));
|
||||||
state!._unreadCountController.add(0);
|
state!._unreadCountController.add(0);
|
||||||
final response = await _client.post('$_channelURL/read', data: {});
|
return _client.markChannelRead(id!, type, messageId: messageId);
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the initial channel state and watches for changes
|
/// Loads the initial channel state and watches for changes
|
||||||
@@ -924,11 +894,8 @@ class Channel {
|
|||||||
|
|
||||||
/// Stop watching the channel
|
/// Stop watching the channel
|
||||||
Future<EmptyResponse> stopWatching() async {
|
Future<EmptyResponse> stopWatching() async {
|
||||||
final response = await _client.post(
|
_checkInitialized();
|
||||||
'$_channelURL/stop-watching',
|
return _client.stopChannelWatching(id!, type);
|
||||||
data: {},
|
|
||||||
);
|
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List the message replies for a parent message
|
/// List the message replies for a parent message
|
||||||
@@ -949,50 +916,29 @@ class Channel {
|
|||||||
return QueryRepliesResponse()..messages = cachedReplies;
|
return QueryRepliesResponse()..messages = cachedReplies;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
final repliesResponse = await _client.getReplies(parentId, options);
|
||||||
final response = await _client.get('/messages/$parentId/replies',
|
|
||||||
queryParameters: options.toJson());
|
|
||||||
|
|
||||||
final repliesResponse = _client.decode<QueryRepliesResponse>(
|
|
||||||
response.data,
|
|
||||||
QueryRepliesResponse.fromJson,
|
|
||||||
);
|
|
||||||
|
|
||||||
state?.updateThreadInfo(parentId, repliesResponse.messages);
|
state?.updateThreadInfo(parentId, repliesResponse.messages);
|
||||||
|
|
||||||
return repliesResponse;
|
return repliesResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List the reactions for a message in the channel
|
/// List the reactions for a message in the channel
|
||||||
Future<QueryReactionsResponse> getReactions(
|
Future<QueryReactionsResponse> getReactions(
|
||||||
String messageID,
|
String messageId,
|
||||||
PaginationParams options,
|
PaginationParams options,
|
||||||
) async {
|
) =>
|
||||||
final response = await _client.get(
|
_client.getReactions(
|
||||||
'/messages/$messageID/reactions',
|
messageId,
|
||||||
queryParameters: options.toJson(),
|
options,
|
||||||
);
|
);
|
||||||
return _client.decode<QueryReactionsResponse>(
|
|
||||||
response.data, QueryReactionsResponse.fromJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves a list of messages by ID
|
/// Retrieves a list of messages by ID
|
||||||
Future<GetMessagesByIdResponse> getMessagesById(
|
Future<GetMessagesByIdResponse> getMessagesById(
|
||||||
List<String> messageIDs) async {
|
List<String> messageIDs,
|
||||||
final response = await _client.get(
|
) async {
|
||||||
'$_channelURL/messages',
|
_checkInitialized();
|
||||||
queryParameters: {'ids': messageIDs.join(',')},
|
final res = await _client.getMessagesById(id!, type, messageIDs);
|
||||||
);
|
|
||||||
|
|
||||||
final res = _client.decode<GetMessagesByIdResponse>(
|
|
||||||
response.data,
|
|
||||||
GetMessagesByIdResponse.fromJson,
|
|
||||||
);
|
|
||||||
|
|
||||||
final messages = res.messages;
|
final messages = res.messages;
|
||||||
|
|
||||||
state?.updateChannelState(ChannelState(messages: messages));
|
state?.updateChannelState(ChannelState(messages: messages));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1000,18 +946,11 @@ class Channel {
|
|||||||
Future<TranslateMessageResponse> translateMessage(
|
Future<TranslateMessageResponse> translateMessage(
|
||||||
String messageId,
|
String messageId,
|
||||||
String language,
|
String language,
|
||||||
) async {
|
) =>
|
||||||
final response = await _client.post(
|
_client.translateMessage(
|
||||||
'/messages/$messageId/translate',
|
messageId,
|
||||||
data: {
|
language,
|
||||||
'language': language,
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
return _client.decode<TranslateMessageResponse>(
|
|
||||||
response.data,
|
|
||||||
TranslateMessageResponse.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new channel
|
/// Creates a new channel
|
||||||
Future<ChannelState> create() async => query(options: {
|
Future<ChannelState> create() async => query(options: {
|
||||||
@@ -1030,36 +969,10 @@ class Channel {
|
|||||||
PaginationParams? watchersPagination,
|
PaginationParams? watchersPagination,
|
||||||
bool preferOffline = false,
|
bool preferOffline = false,
|
||||||
}) async {
|
}) async {
|
||||||
var path = '/channels/$type';
|
|
||||||
if (id != null) path = '$path/$id';
|
|
||||||
path = '$path/query';
|
|
||||||
|
|
||||||
final payload = Map<String, dynamic>.from({
|
|
||||||
'state': true,
|
|
||||||
})
|
|
||||||
..addAll(options);
|
|
||||||
|
|
||||||
if (_extraData.isNotEmpty) {
|
|
||||||
payload['data'] = _extraData;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (messagesPagination != null) {
|
|
||||||
payload['messages'] = messagesPagination.toJson();
|
|
||||||
}
|
|
||||||
if (membersPagination != null) {
|
|
||||||
payload['members'] = membersPagination.toJson();
|
|
||||||
}
|
|
||||||
if (watchersPagination != null) {
|
|
||||||
payload['watchers'] = watchersPagination.toJson();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preferOffline && cid != null) {
|
if (preferOffline && cid != null) {
|
||||||
final updatedState =
|
final updatedState = await _client.chatPersistenceClient
|
||||||
(await _client.chatPersistenceClient?.getChannelStateByCid(
|
?.getChannelStateByCid(cid!, messagePagination: messagesPagination);
|
||||||
cid!,
|
if (updatedState != null && updatedState.messages.isNotEmpty) {
|
||||||
messagePagination: messagesPagination,
|
|
||||||
))!;
|
|
||||||
if (updatedState.messages.isNotEmpty) {
|
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
_initState(updatedState);
|
_initState(updatedState);
|
||||||
} else {
|
} else {
|
||||||
@@ -1070,8 +983,14 @@ class Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _client.post(path, data: payload);
|
final updatedState = await _client.queryChannel(
|
||||||
final updatedState = _client.decode(response.data, ChannelState.fromJson);
|
type,
|
||||||
|
channelId: id,
|
||||||
|
channelData: _extraData,
|
||||||
|
messagesPagination: messagesPagination,
|
||||||
|
membersPagination: membersPagination,
|
||||||
|
watchersPagination: watchersPagination,
|
||||||
|
);
|
||||||
|
|
||||||
if (_id == null) {
|
if (_id == null) {
|
||||||
_id = updatedState.channel!.id;
|
_id = updatedState.channel!.id;
|
||||||
@@ -1096,45 +1015,26 @@ class Channel {
|
|||||||
Filter? filter,
|
Filter? filter,
|
||||||
List<SortOption>? sort,
|
List<SortOption>? sort,
|
||||||
PaginationParams? pagination,
|
PaginationParams? pagination,
|
||||||
}) async {
|
}) =>
|
||||||
final payload = <String, dynamic>{
|
_client.queryMembers(
|
||||||
'sort': sort,
|
type,
|
||||||
'filter_conditions': filter ?? {},
|
channelId: id,
|
||||||
'type': type,
|
filter: filter,
|
||||||
};
|
members: state?.members,
|
||||||
|
sort: sort,
|
||||||
if (pagination != null) {
|
pagination: pagination,
|
||||||
payload.addAll(pagination.toJson());
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (id != null) {
|
|
||||||
payload['id'] = id;
|
|
||||||
} else if (state?.members.isNotEmpty == true) {
|
|
||||||
payload['members'] = state!.members;
|
|
||||||
}
|
|
||||||
|
|
||||||
final rawRes = await _client.get('/members', queryParameters: {
|
|
||||||
'payload': jsonEncode(payload),
|
|
||||||
});
|
|
||||||
final response = _client.decode(rawRes.data, QueryMembersResponse.fromJson);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutes the channel
|
/// Mutes the channel
|
||||||
Future<EmptyResponse> mute({Duration? expiration}) async {
|
Future<EmptyResponse> mute({Duration? expiration}) {
|
||||||
final response = await _client.post('/moderation/mute/channel', data: {
|
_checkInitialized();
|
||||||
'channel_cid': cid,
|
return _client.muteChannel(cid!, expiration: expiration);
|
||||||
if (expiration != null) 'expiration': expiration.inMilliseconds,
|
|
||||||
});
|
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unmutes the channel
|
/// Unmutes the channel
|
||||||
Future<EmptyResponse> unmute() async {
|
Future<EmptyResponse> unmute() {
|
||||||
final response = await _client.post('/moderation/unmute/channel', data: {
|
_checkInitialized();
|
||||||
'channel_cid': cid,
|
return _client.unmuteChannel(cid!);
|
||||||
});
|
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bans a user from the channel
|
/// Bans a user from the channel
|
||||||
@@ -1188,9 +1088,11 @@ class Channel {
|
|||||||
/// will be removed for the user
|
/// will be removed for the user
|
||||||
Future<EmptyResponse> hide({bool clearHistory = false}) async {
|
Future<EmptyResponse> hide({bool clearHistory = false}) async {
|
||||||
_checkInitialized();
|
_checkInitialized();
|
||||||
final response = await _client
|
final response = await _client.hideChannel(
|
||||||
.post('$_channelURL/hide', data: {'clear_history': clearHistory});
|
id!,
|
||||||
|
type,
|
||||||
|
clearHistory: clearHistory,
|
||||||
|
);
|
||||||
if (clearHistory == true) {
|
if (clearHistory == true) {
|
||||||
state!.truncate();
|
state!.truncate();
|
||||||
final cid = _cid;
|
final cid = _cid;
|
||||||
@@ -1198,15 +1100,13 @@ class Channel {
|
|||||||
await _client.chatPersistenceClient?.deleteMessageByCid(cid);
|
await _client.chatPersistenceClient?.deleteMessageByCid(cid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return response;
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the hidden status for the channel
|
/// Removes the hidden status for the channel
|
||||||
Future<EmptyResponse> show() async {
|
Future<EmptyResponse> show() async {
|
||||||
_checkInitialized();
|
_checkInitialized();
|
||||||
final response = await _client.post('$_channelURL/show');
|
return _client.showChannel(id!, type);
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stream of [Event] coming from websocket connection specific for the
|
/// Stream of [Event] coming from websocket connection specific for the
|
||||||
@@ -1588,7 +1488,7 @@ class ChannelClientState {
|
|||||||
}
|
}
|
||||||
readList.add(Read(
|
readList.add(Read(
|
||||||
user: event.user!,
|
user: event.user!,
|
||||||
lastRead: event.createdAt!,
|
lastRead: event.createdAt,
|
||||||
unreadMessages: event.totalUnreadCount ?? 0,
|
unreadMessages: event.totalUnreadCount ?? 0,
|
||||||
));
|
));
|
||||||
_channelState = _channelState.copyWith(read: readList);
|
_channelState = _channelState.copyWith(read: readList);
|
||||||
@@ -1793,7 +1693,7 @@ class ChannelClientState {
|
|||||||
BehaviorSubject.seeded({});
|
BehaviorSubject.seeded({});
|
||||||
|
|
||||||
set _threads(Map<String, List<Message>> v) {
|
set _threads(Map<String, List<Message>> v) {
|
||||||
_channel._client.chatPersistenceClient?.updateMessages(
|
_channel.client.chatPersistenceClient?.updateMessages(
|
||||||
_channel.cid!,
|
_channel.cid!,
|
||||||
v.values.expand((v) => v).toList(),
|
v.values.expand((v) => v).toList(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:stream_chat/src/api/channel.dart';
|
|||||||
import 'package:stream_chat/src/api/retry_policy.dart';
|
import 'package:stream_chat/src/api/retry_policy.dart';
|
||||||
import 'package:stream_chat/src/event_type.dart';
|
import 'package:stream_chat/src/event_type.dart';
|
||||||
import 'package:stream_chat/src/exceptions.dart';
|
import 'package:stream_chat/src/exceptions.dart';
|
||||||
import 'package:stream_chat/src/models/message.dart';
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
/// The retry queue associated to a channel
|
/// The retry queue associated to a channel
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import 'package:web_socket_channel/html.dart';
|
|
||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
|
||||||
|
|
||||||
/// Html version of websocket implementation
|
|
||||||
/// Used in Flutter web version
|
|
||||||
WebSocketChannel connectWebSocket(String url, {Iterable<String>? protocols}) =>
|
|
||||||
HtmlWebSocketChannel.connect(url, protocols: protocols);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import 'package:web_socket_channel/io.dart';
|
|
||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
|
||||||
|
|
||||||
/// IO version of websocket implementation
|
|
||||||
/// Used in Flutter mobile version
|
|
||||||
WebSocketChannel connectWebSocket(String url, {Iterable<String>? protocols}) =>
|
|
||||||
IOWebSocketChannel.connect(url, protocols: protocols);
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
|
||||||
|
|
||||||
/// Stub version of websocket implementation
|
|
||||||
/// Used just for conditional library import
|
|
||||||
WebSocketChannel connectWebSocket(String url,
|
|
||||||
{Iterable<String>? protocols,
|
|
||||||
Map<String, dynamic>? headers,
|
|
||||||
Duration? pingInterval}) =>
|
|
||||||
throw UnimplementedError();
|
|
||||||
@@ -1,321 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:stream_chat/src/api/connection_status.dart';
|
|
||||||
import 'package:stream_chat/src/models/event.dart';
|
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
|
||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
|
||||||
|
|
||||||
/// Typedef which exposes an [Event] as the only parameter.
|
|
||||||
typedef EventHandler = void Function(Event);
|
|
||||||
|
|
||||||
/// Typedef used for connecting to a websocket. Method returns a
|
|
||||||
/// [WebSocketChannel] and accepts a connection [url] and an optional
|
|
||||||
/// [Iterable] of `protocols`.
|
|
||||||
typedef ConnectWebSocket = WebSocketChannel Function(String? url,
|
|
||||||
{Iterable<String>? protocols});
|
|
||||||
|
|
||||||
// TODO: parse error even
|
|
||||||
// TODO: if parsing an error into an event fails we should not hide the
|
|
||||||
// TODO: original error
|
|
||||||
/// A WebSocket connection that reconnects upon failure.
|
|
||||||
class WebSocket {
|
|
||||||
/// Creates a new websocket
|
|
||||||
/// To connect the WS call [connect]
|
|
||||||
WebSocket({
|
|
||||||
required this.baseUrl,
|
|
||||||
required this.user,
|
|
||||||
required this.handler,
|
|
||||||
this.connectParams = const {},
|
|
||||||
this.connectPayload = const {},
|
|
||||||
this.logger,
|
|
||||||
this.connectFunc,
|
|
||||||
this.reconnectionMonitorInterval = 1,
|
|
||||||
this.healthCheckInterval = 20,
|
|
||||||
this.reconnectionMonitorTimeout = 40,
|
|
||||||
}) {
|
|
||||||
final qs = Map<String, String>.from(connectParams);
|
|
||||||
|
|
||||||
final data = Map<String, dynamic>.from(connectPayload);
|
|
||||||
|
|
||||||
data['user_details'] = user.toJson();
|
|
||||||
qs['json'] = json.encode(data);
|
|
||||||
|
|
||||||
if (baseUrl.startsWith('https')) {
|
|
||||||
_path = baseUrl.replaceFirst('https://', '');
|
|
||||||
_path = Uri.https(_path, 'connect', qs)
|
|
||||||
.toString()
|
|
||||||
.replaceFirst('https', 'wss');
|
|
||||||
} else if (baseUrl.startsWith('http')) {
|
|
||||||
_path = baseUrl.replaceFirst('http://', '');
|
|
||||||
_path =
|
|
||||||
Uri.http(_path, 'connect', qs).toString().replaceFirst('http', 'ws');
|
|
||||||
} else {
|
|
||||||
_path = Uri.https(baseUrl, 'connect', qs)
|
|
||||||
.toString()
|
|
||||||
.replaceFirst('https', 'wss');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// WS base url
|
|
||||||
final String baseUrl;
|
|
||||||
|
|
||||||
/// User performing the WS connection
|
|
||||||
final User user;
|
|
||||||
|
|
||||||
/// Querystring connection parameters
|
|
||||||
final Map<String, String> connectParams;
|
|
||||||
|
|
||||||
/// WS connection payload
|
|
||||||
final Map<String, dynamic> connectPayload;
|
|
||||||
|
|
||||||
/// Functions that will be called every time a new event is received from the
|
|
||||||
/// connection
|
|
||||||
final EventHandler handler;
|
|
||||||
|
|
||||||
/// A WS specific logger instance
|
|
||||||
final Logger? logger;
|
|
||||||
|
|
||||||
/// Connection function
|
|
||||||
/// Used only for testing purpose
|
|
||||||
@visibleForTesting
|
|
||||||
final ConnectWebSocket? connectFunc;
|
|
||||||
|
|
||||||
/// Interval of the reconnection monitor timer
|
|
||||||
/// This checks that it received a new event in the last
|
|
||||||
/// [reconnectionMonitorTimeout] seconds, otherwise it considers the
|
|
||||||
/// connection unhealthy and reconnects the WS
|
|
||||||
final int reconnectionMonitorInterval;
|
|
||||||
|
|
||||||
/// Interval of the health event sending timer
|
|
||||||
/// This sends a health event every [healthCheckInterval] seconds in order to
|
|
||||||
/// make the server aware that the client is still listening
|
|
||||||
final int healthCheckInterval;
|
|
||||||
|
|
||||||
/// The timeout that uses the reconnection monitor timer to consider the
|
|
||||||
/// connection unhealthy
|
|
||||||
final int reconnectionMonitorTimeout;
|
|
||||||
|
|
||||||
final _connectionStatusController =
|
|
||||||
BehaviorSubject.seeded(ConnectionStatus.disconnected);
|
|
||||||
|
|
||||||
set _connectionStatus(ConnectionStatus status) =>
|
|
||||||
_connectionStatusController.add(status);
|
|
||||||
|
|
||||||
/// The current connection status value
|
|
||||||
ConnectionStatus? get connectionStatus => _connectionStatusController.value;
|
|
||||||
|
|
||||||
/// This notifies of connection status changes
|
|
||||||
Stream<ConnectionStatus> get connectionStatusStream =>
|
|
||||||
_connectionStatusController.stream;
|
|
||||||
|
|
||||||
late String _path;
|
|
||||||
int _retryAttempt = 1;
|
|
||||||
late WebSocketChannel _channel;
|
|
||||||
Timer? _healthCheck, _reconnectionMonitor;
|
|
||||||
DateTime? _lastEventAt;
|
|
||||||
bool _manuallyDisconnected = false;
|
|
||||||
bool _connecting = false;
|
|
||||||
bool _reconnecting = false;
|
|
||||||
|
|
||||||
Event _decodeEvent(String source) => Event.fromJson(json.decode(source));
|
|
||||||
|
|
||||||
Completer<Event?> _connectionCompleter = Completer<Event?>();
|
|
||||||
|
|
||||||
/// Connect the WS using the parameters passed in the constructor
|
|
||||||
Future<Event?> connect() async {
|
|
||||||
_manuallyDisconnected = false;
|
|
||||||
|
|
||||||
if (_connecting) {
|
|
||||||
logger?.severe('already connecting');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_connecting = true;
|
|
||||||
_connectionStatus = ConnectionStatus.connecting;
|
|
||||||
|
|
||||||
logger?.info('connecting to $_path');
|
|
||||||
|
|
||||||
_channel =
|
|
||||||
connectFunc?.call(_path) ?? WebSocketChannel.connect(Uri.parse(_path));
|
|
||||||
_channel.stream.listen(
|
|
||||||
(data) async {
|
|
||||||
final jsonData = json.decode(data);
|
|
||||||
if (jsonData['error'] != null) {
|
|
||||||
return _onConnectionError(jsonData['error']);
|
|
||||||
}
|
|
||||||
_onData(data);
|
|
||||||
},
|
|
||||||
onError: (error, stacktrace) {
|
|
||||||
_onConnectionError(error, stacktrace);
|
|
||||||
},
|
|
||||||
onDone: _onDone,
|
|
||||||
);
|
|
||||||
return _connectionCompleter.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onDone() {
|
|
||||||
_connecting = false;
|
|
||||||
if (_manuallyDisconnected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger?.info('connection closed | closeCode: ${_channel.closeCode} | '
|
|
||||||
'closedReason: ${_channel.closeReason}');
|
|
||||||
|
|
||||||
if (!_reconnecting) {
|
|
||||||
_reconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onData(data) {
|
|
||||||
if (_manuallyDisconnected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final event = _decodeEvent(data);
|
|
||||||
logger?.info('received new event: $data');
|
|
||||||
|
|
||||||
if (_lastEventAt == null) {
|
|
||||||
logger?.info('connection estabilished');
|
|
||||||
_connecting = false;
|
|
||||||
_reconnecting = false;
|
|
||||||
_lastEventAt = DateTime.now();
|
|
||||||
|
|
||||||
_connectionStatus = ConnectionStatus.connected;
|
|
||||||
_retryAttempt = 1;
|
|
||||||
|
|
||||||
if (!_connectionCompleter.isCompleted) {
|
|
||||||
_connectionCompleter.complete(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
_startReconnectionMonitor();
|
|
||||||
_startHealthCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
handler(event);
|
|
||||||
_lastEventAt = DateTime.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onConnectionError(error, [stacktrace]) async {
|
|
||||||
logger?..severe('error connecting')..severe(error);
|
|
||||||
if (stacktrace != null) {
|
|
||||||
logger?.severe(stacktrace);
|
|
||||||
}
|
|
||||||
_connecting = false;
|
|
||||||
|
|
||||||
if (!_reconnecting) {
|
|
||||||
_connectionStatus = ConnectionStatus.disconnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_connectionCompleter.isCompleted) {
|
|
||||||
_cancelTimers();
|
|
||||||
_connectionCompleter.completeError(error, stacktrace);
|
|
||||||
} else if (!_reconnecting) {
|
|
||||||
return _reconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _reconnectionTimer(_) {
|
|
||||||
final now = DateTime.now();
|
|
||||||
if (_lastEventAt != null &&
|
|
||||||
now.difference(_lastEventAt!).inSeconds > reconnectionMonitorTimeout) {
|
|
||||||
_channel.sink.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _startReconnectionMonitor() {
|
|
||||||
_reconnectionMonitor = Timer.periodic(
|
|
||||||
Duration(seconds: reconnectionMonitorInterval),
|
|
||||||
_reconnectionTimer,
|
|
||||||
);
|
|
||||||
|
|
||||||
_reconnectionTimer(_reconnectionMonitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _reconnectTimer() async {
|
|
||||||
if (!_reconnecting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_connecting) {
|
|
||||||
logger?.info('already connecting');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger?.info('reconnecting..');
|
|
||||||
|
|
||||||
_cancelTimers();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await connect();
|
|
||||||
} catch (e) {
|
|
||||||
logger?.log(Level.SEVERE, e.toString());
|
|
||||||
}
|
|
||||||
await Future.delayed(
|
|
||||||
Duration(seconds: min(_retryAttempt * 5, 25)),
|
|
||||||
() {
|
|
||||||
_reconnectTimer();
|
|
||||||
_retryAttempt++;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _reconnect() async {
|
|
||||||
logger?.info('reconnect');
|
|
||||||
if (!_reconnecting) {
|
|
||||||
_reconnecting = true;
|
|
||||||
_connectionStatus = ConnectionStatus.connecting;
|
|
||||||
}
|
|
||||||
|
|
||||||
_reconnectTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _cancelTimers() {
|
|
||||||
_lastEventAt = null;
|
|
||||||
if (_healthCheck != null) {
|
|
||||||
_healthCheck!.cancel();
|
|
||||||
}
|
|
||||||
if (_reconnectionMonitor != null) {
|
|
||||||
_reconnectionMonitor!.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _healthCheckTimer(_) {
|
|
||||||
logger?.info('sending health.check');
|
|
||||||
_channel.sink.add("{'type': 'health.check'}");
|
|
||||||
}
|
|
||||||
|
|
||||||
void _startHealthCheck() {
|
|
||||||
logger?.info('start health check monitor');
|
|
||||||
|
|
||||||
_healthCheck = Timer.periodic(
|
|
||||||
Duration(seconds: healthCheckInterval),
|
|
||||||
_healthCheckTimer,
|
|
||||||
);
|
|
||||||
|
|
||||||
_healthCheckTimer(_healthCheck);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Disconnects the WS and releases eventual resources
|
|
||||||
Future<void> disconnect() async {
|
|
||||||
_connecting = false;
|
|
||||||
if (!_connectionCompleter.isCompleted) {
|
|
||||||
_connectionCompleter.complete();
|
|
||||||
}
|
|
||||||
if (_manuallyDisconnected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger?.info('disconnecting');
|
|
||||||
_connectionCompleter = Completer();
|
|
||||||
_cancelTimers();
|
|
||||||
_reconnecting = false;
|
|
||||||
_manuallyDisconnected = true;
|
|
||||||
_connectionStatus = ConnectionStatus.disconnected;
|
|
||||||
await _connectionStatusController.close();
|
|
||||||
await _channel.sink.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
+8
-8
@@ -1,8 +1,8 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:stream_chat/src/api/responses.dart';
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
import 'package:stream_chat/src/client.dart';
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
import 'package:stream_chat/src/extensions/string_extension.dart';
|
import 'package:stream_chat/src/extensions/string_extension.dart';
|
||||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
||||||
|
|
||||||
/// Class responsible for uploading images and files to a given channel
|
/// Class responsible for uploading images and files to a given channel
|
||||||
abstract class AttachmentFileUploader {
|
abstract class AttachmentFileUploader {
|
||||||
@@ -60,7 +60,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader {
|
|||||||
/// Creates a new [StreamAttachmentFileUploader] instance.
|
/// Creates a new [StreamAttachmentFileUploader] instance.
|
||||||
const StreamAttachmentFileUploader(this._client);
|
const StreamAttachmentFileUploader(this._client);
|
||||||
|
|
||||||
final StreamChatClient _client;
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SendImageResponse> sendImage(
|
Future<SendImageResponse> sendImage(
|
||||||
@@ -96,7 +96,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader {
|
|||||||
onSendProgress: onSendProgress,
|
onSendProgress: onSendProgress,
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
return _client.decode(response.data, SendImageResponse.fromJson);
|
return SendImageResponse.fromJson(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -133,7 +133,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader {
|
|||||||
onSendProgress: onSendProgress,
|
onSendProgress: onSendProgress,
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
return _client.decode(response.data, SendFileResponse.fromJson);
|
return SendFileResponse.fromJson(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -148,7 +148,7 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader {
|
|||||||
queryParameters: {'url': url},
|
queryParameters: {'url': url},
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
return EmptyResponse.fromJson(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -163,6 +163,6 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader {
|
|||||||
queryParameters: {'url': url},
|
queryParameters: {'url': url},
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
return EmptyResponse.fromJson(response.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:stream_chat/src/core/api/requests.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/channel_model.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/channel_state.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/event.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/filter.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class ChannelApi {
|
||||||
|
///
|
||||||
|
ChannelApi(this._client);
|
||||||
|
|
||||||
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
|
String _getChannelUrl(String channelId, String channelType) =>
|
||||||
|
'/channels/$channelType/$channelId';
|
||||||
|
|
||||||
|
/// Query the API, get messages, members or other channel fields
|
||||||
|
Future<ChannelState> queryChannel(
|
||||||
|
String channelType, {
|
||||||
|
bool state = true,
|
||||||
|
bool watch = false,
|
||||||
|
bool presence = false,
|
||||||
|
String? channelId,
|
||||||
|
Map<String, Object?>? channelData,
|
||||||
|
PaginationParams? messagesPagination,
|
||||||
|
PaginationParams? membersPagination,
|
||||||
|
PaginationParams? watchersPagination,
|
||||||
|
}) async {
|
||||||
|
var channelPath = '/channels/$channelType';
|
||||||
|
if (channelId != null) channelPath = '$channelPath/$channelId';
|
||||||
|
final response = await _client.post(
|
||||||
|
'$channelPath/query',
|
||||||
|
data: {
|
||||||
|
'state': state,
|
||||||
|
'watch': watch,
|
||||||
|
'presence': presence,
|
||||||
|
if (channelData != null) 'data': channelData,
|
||||||
|
if (messagesPagination != null) 'messages': messagesPagination,
|
||||||
|
if (membersPagination != null) 'members': membersPagination,
|
||||||
|
if (watchersPagination != null) 'watchers': watchersPagination,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return ChannelState.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Requests channels with a given query from the API.
|
||||||
|
Future<QueryChannelsResponse> queryChannels({
|
||||||
|
Filter? filter,
|
||||||
|
List<SortOption<ChannelModel>>? sort,
|
||||||
|
int? memberLimit,
|
||||||
|
int? messageLimit,
|
||||||
|
bool state = true,
|
||||||
|
bool watch = true,
|
||||||
|
bool presence = false,
|
||||||
|
PaginationParams paginationParams = const PaginationParams(),
|
||||||
|
}) async {
|
||||||
|
print('Query Channel Started 2 : ${DateTime.now()}');
|
||||||
|
final response = await _client.get(
|
||||||
|
'/channels',
|
||||||
|
queryParameters: {
|
||||||
|
'payload': jsonEncode({
|
||||||
|
// default options
|
||||||
|
'state': state,
|
||||||
|
'watch': watch,
|
||||||
|
'presence': presence,
|
||||||
|
|
||||||
|
// passed options
|
||||||
|
if (sort != null) 'sort': sort,
|
||||||
|
if (filter != null) 'filter_conditions': filter,
|
||||||
|
if (memberLimit != null) 'member_limit': memberLimit,
|
||||||
|
if (messageLimit != null) 'message_limit': messageLimit,
|
||||||
|
|
||||||
|
// pagination
|
||||||
|
...paginationParams.toJson()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
print('Query Channel Completed 2 : ${DateTime.now()}');
|
||||||
|
return QueryChannelsResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark all channels for this user as read
|
||||||
|
Future<EmptyResponse> markAllRead() async {
|
||||||
|
final response = await _client.post('channels/read');
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces the [channelId] of type [ChannelType] data with [data]
|
||||||
|
Future<UpdateChannelResponse> updateChannel(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
Map<String, dynamic> data, {
|
||||||
|
Message? message,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
_getChannelUrl(channelId, channelType),
|
||||||
|
data: {
|
||||||
|
'data': data,
|
||||||
|
if (message != null)
|
||||||
|
'message': message.copyWith(updatedAt: DateTime.now()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return UpdateChannelResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the [channelId] of type [ChannelType] data with [data]
|
||||||
|
Future<PartialUpdateChannelResponse> updateChannelPartial(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
Map<String, dynamic> data,
|
||||||
|
) async {
|
||||||
|
final response = await _client.patch(
|
||||||
|
_getChannelUrl(channelId, channelType),
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
|
return PartialUpdateChannelResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accept invitation to the channel
|
||||||
|
Future<AcceptInviteResponse> acceptChannelInvite(
|
||||||
|
String channelId,
|
||||||
|
String channelType, {
|
||||||
|
Message? message,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
_getChannelUrl(channelId, channelType),
|
||||||
|
data: {
|
||||||
|
'accept_invite': true,
|
||||||
|
'message': message,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return AcceptInviteResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reject invitation to the channel
|
||||||
|
Future<RejectInviteResponse> rejectChannelInvite(
|
||||||
|
String channelId,
|
||||||
|
String channelType, {
|
||||||
|
Message? message,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
_getChannelUrl(channelId, channelType),
|
||||||
|
data: {
|
||||||
|
'reject_invite': true,
|
||||||
|
'message': message,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return RejectInviteResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invite members to the channel
|
||||||
|
Future<InviteMembersResponse> inviteChannelMembers(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
List<String> memberIds, {
|
||||||
|
Message? message,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
_getChannelUrl(channelId, channelType),
|
||||||
|
data: {
|
||||||
|
'invites': memberIds,
|
||||||
|
'message': message,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return InviteMembersResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add members to the channel
|
||||||
|
Future<AddMembersResponse> addMembers(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
List<String> memberIds, {
|
||||||
|
Message? message,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
_getChannelUrl(channelId, channelType),
|
||||||
|
data: {
|
||||||
|
'add_members': memberIds,
|
||||||
|
'message': message,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return AddMembersResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove members from the channel
|
||||||
|
Future<RemoveMembersResponse> removeMembers(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
List<String> memberIds, {
|
||||||
|
Message? message,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
_getChannelUrl(channelId, channelType),
|
||||||
|
data: {
|
||||||
|
'remove_members': memberIds,
|
||||||
|
'message': message,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return RemoveMembersResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send an event on this channel
|
||||||
|
Future<EmptyResponse> sendEvent(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
Event event,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'${_getChannelUrl(channelId, channelType)}/event',
|
||||||
|
data: {'event': event},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete this channel. Messages are permanently removed.
|
||||||
|
Future<EmptyResponse> deleteChannel(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
) async {
|
||||||
|
final response = await _client.delete(
|
||||||
|
_getChannelUrl(channelId, channelType),
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all messages from the channel
|
||||||
|
Future<EmptyResponse> truncateChannel(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'${_getChannelUrl(channelId, channelType)}/truncate',
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hides the channel from [StreamChatClient.queryChannels] for the user
|
||||||
|
/// until a message is added If [clearHistory] is set to true - all messages
|
||||||
|
/// will be removed for the user
|
||||||
|
Future<EmptyResponse> hideChannel(
|
||||||
|
String channelId,
|
||||||
|
String channelType, {
|
||||||
|
bool clearHistory = false,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'${_getChannelUrl(channelId, channelType)}/hide',
|
||||||
|
data: {'clear_history': clearHistory},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the hidden status for the channel
|
||||||
|
Future<EmptyResponse> showChannel(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'${_getChannelUrl(channelId, channelType)}/show',
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark [channelId] of type [channelType] all messages as read
|
||||||
|
/// Optionally provide a [messageId] if you want to mark a
|
||||||
|
/// particular message as read
|
||||||
|
Future<EmptyResponse> markRead(
|
||||||
|
String channelId,
|
||||||
|
String channelType, {
|
||||||
|
String? messageId,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'${_getChannelUrl(channelId, channelType)}/read',
|
||||||
|
data: {if (messageId != null) 'message_id': messageId},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop watching the channel
|
||||||
|
Future<EmptyResponse> stopWatching(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'${_getChannelUrl(channelId, channelType)}/stop-watching',
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
|
|
||||||
|
/// Provider used to send push notifications.
|
||||||
|
enum PushProvider {
|
||||||
|
/// Send notifications using Google's Firebase Cloud Messaging
|
||||||
|
firebase,
|
||||||
|
|
||||||
|
/// Send notifications using Apple's Push Notification service
|
||||||
|
apn
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on PushProvider {
|
||||||
|
/// Returns the string notion for [PushProvider].
|
||||||
|
String get name => {
|
||||||
|
PushProvider.apn: 'apn',
|
||||||
|
PushProvider.firebase: 'firebase',
|
||||||
|
}[this]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
class DeviceApi {
|
||||||
|
///
|
||||||
|
DeviceApi(this._client);
|
||||||
|
|
||||||
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
|
/// Add a device for Push Notifications.
|
||||||
|
Future<EmptyResponse> addDevice(
|
||||||
|
String deviceId,
|
||||||
|
PushProvider pushProvider,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/devices',
|
||||||
|
data: {
|
||||||
|
'id': deviceId,
|
||||||
|
'push_provider': pushProvider.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a list of user devices.
|
||||||
|
Future<ListDevicesResponse> getDevices() async {
|
||||||
|
final response = await _client.get('/devices');
|
||||||
|
return ListDevicesResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a user's device.
|
||||||
|
Future<EmptyResponse> removeDevice(
|
||||||
|
String deviceId,
|
||||||
|
) async {
|
||||||
|
final response = await _client.delete(
|
||||||
|
'/devices',
|
||||||
|
queryParameters: {'id': deviceId},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:stream_chat/src/core/api/requests.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/filter.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/member.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class GeneralApi {
|
||||||
|
///
|
||||||
|
GeneralApi(this._client);
|
||||||
|
|
||||||
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
|
/// Get all the missed events
|
||||||
|
Future<SyncResponse> sync(
|
||||||
|
List<String> cids,
|
||||||
|
DateTime lastSyncAt,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/sync',
|
||||||
|
data: {
|
||||||
|
'channel_cids': cids,
|
||||||
|
'last_sync_at': lastSyncAt.toUtc().toIso8601String(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return SyncResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A message search.
|
||||||
|
Future<SearchMessagesResponse> searchMessages(
|
||||||
|
Filter filter, {
|
||||||
|
String? query,
|
||||||
|
List<SortOption>? sort,
|
||||||
|
PaginationParams? pagination,
|
||||||
|
Filter? messageFilters,
|
||||||
|
}) async {
|
||||||
|
assert(() {
|
||||||
|
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;
|
||||||
|
}(), 'Check incoming params.');
|
||||||
|
|
||||||
|
final response = await _client.get(
|
||||||
|
'/search',
|
||||||
|
queryParameters: {
|
||||||
|
'payload': jsonEncode({
|
||||||
|
'filter_conditions': filter,
|
||||||
|
if (sort != null) 'sort': sort,
|
||||||
|
if (query != null) 'query': query,
|
||||||
|
if (messageFilters != null)
|
||||||
|
'message_filter_conditions': messageFilters,
|
||||||
|
if (pagination != null) ...pagination.toJson(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return SearchMessagesResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query channel members
|
||||||
|
Future<QueryMembersResponse> queryMembers(
|
||||||
|
String channelType, {
|
||||||
|
Filter? filter,
|
||||||
|
String? channelId,
|
||||||
|
List<Member>? members,
|
||||||
|
List<SortOption>? sort,
|
||||||
|
PaginationParams? pagination,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.get(
|
||||||
|
'/members',
|
||||||
|
queryParameters: {
|
||||||
|
'payload': jsonEncode({
|
||||||
|
'type': channelType,
|
||||||
|
if (channelId != null)
|
||||||
|
'id': channelId
|
||||||
|
else if (members != null)
|
||||||
|
'members': members,
|
||||||
|
if (sort != null) 'sort': sort,
|
||||||
|
if (filter != null) 'filter': filter,
|
||||||
|
if (pagination != null) ...pagination.toJson(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return QueryMembersResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class GuestApi {
|
||||||
|
///
|
||||||
|
GuestApi(this._client);
|
||||||
|
|
||||||
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
|
///
|
||||||
|
Future<ConnectGuestUserResponse> getGuestUser(User user) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/guest',
|
||||||
|
data: {'user': user},
|
||||||
|
);
|
||||||
|
return ConnectGuestUserResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import 'package:stream_chat/src/core/api/requests.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class MessageApi {
|
||||||
|
///
|
||||||
|
MessageApi(this._client);
|
||||||
|
|
||||||
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
|
/// Sends the [message] to the given [channelId] of given [channelType]
|
||||||
|
Future<SendMessageResponse> sendMessage(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
Message message,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/channels/$channelType/$channelId/message',
|
||||||
|
data: {'message': message},
|
||||||
|
);
|
||||||
|
return SendMessageResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a list of messages by [messageIDs]
|
||||||
|
/// from the given [channelId] of type [channelType]
|
||||||
|
Future<GetMessagesByIdResponse> getMessagesById(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
List<String> messageIDs,
|
||||||
|
) async {
|
||||||
|
final response = await _client.get(
|
||||||
|
'/channels/$channelType/$channelId/messages',
|
||||||
|
queryParameters: {'ids': messageIDs.join(',')},
|
||||||
|
);
|
||||||
|
return GetMessagesByIdResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a message by [messageId]
|
||||||
|
Future<GetMessageResponse> getMessage(String messageId) async {
|
||||||
|
final response = await _client.get(
|
||||||
|
'/messages/$messageId',
|
||||||
|
);
|
||||||
|
return GetMessageResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the given [message]
|
||||||
|
Future<UpdateMessageResponse> updateMessage(
|
||||||
|
Message message,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/messages/${message.id}',
|
||||||
|
data: {'message': message},
|
||||||
|
);
|
||||||
|
return UpdateMessageResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes the given [messageId]
|
||||||
|
Future<EmptyResponse> deleteMessage(
|
||||||
|
String messageId,
|
||||||
|
) async {
|
||||||
|
final response = await _client.delete(
|
||||||
|
'/messages/$messageId',
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send action for a specific [messageId]
|
||||||
|
/// of the given [channelId] of given [channelType]
|
||||||
|
Future<SendActionResponse> sendAction(
|
||||||
|
String channelId,
|
||||||
|
String channelType,
|
||||||
|
String messageId,
|
||||||
|
Map<String, Object?> formData,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/messages/$messageId/action',
|
||||||
|
data: {
|
||||||
|
'id': channelId,
|
||||||
|
'type': channelType,
|
||||||
|
'form_data': formData,
|
||||||
|
'message_id': messageId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return SendActionResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a [reactionType] for this [messageId]
|
||||||
|
/// Set [enforceUnique] to true to remove the existing user reaction
|
||||||
|
Future<SendReactionResponse> sendReaction(
|
||||||
|
String messageId,
|
||||||
|
String reactionType, {
|
||||||
|
Map<String, Object?> extraData = const {},
|
||||||
|
bool enforceUnique = false,
|
||||||
|
}) async {
|
||||||
|
final reaction = Map<String, Object?>.from(extraData)
|
||||||
|
..addAll({'type': reactionType});
|
||||||
|
|
||||||
|
final response = await _client.post(
|
||||||
|
'/messages/$messageId/reaction',
|
||||||
|
data: {
|
||||||
|
'reaction': reaction,
|
||||||
|
'enforce_unique': enforceUnique,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return SendReactionResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a [reactionType] from this [messageId]
|
||||||
|
Future<EmptyResponse> deleteReaction(
|
||||||
|
String messageId,
|
||||||
|
String reactionType,
|
||||||
|
) async {
|
||||||
|
final response = await _client.delete(
|
||||||
|
'/messages/$messageId/reaction/$reactionType',
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all the reactions for a [messageId]
|
||||||
|
Future<QueryReactionsResponse> getReactions(
|
||||||
|
String messageId,
|
||||||
|
PaginationParams options,
|
||||||
|
) async {
|
||||||
|
final response = await _client.get(
|
||||||
|
'/messages/$messageId/reactions',
|
||||||
|
queryParameters: {
|
||||||
|
...options.toJson(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return QueryReactionsResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translates the [messageId] in provided [language]
|
||||||
|
Future<TranslateMessageResponse> translateMessage(
|
||||||
|
String messageId,
|
||||||
|
String language,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/messages/$messageId/translate',
|
||||||
|
data: {'language': language},
|
||||||
|
);
|
||||||
|
return TranslateMessageResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists all the message replies for the [parentId]
|
||||||
|
Future<QueryRepliesResponse> getReplies(
|
||||||
|
String parentId,
|
||||||
|
PaginationParams options,
|
||||||
|
) async {
|
||||||
|
final response = await _client.get(
|
||||||
|
'/messages/$parentId/replies',
|
||||||
|
queryParameters: {
|
||||||
|
...options.toJson(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return QueryRepliesResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class ModerationApi {
|
||||||
|
///
|
||||||
|
ModerationApi(this._client);
|
||||||
|
|
||||||
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
|
/// Mutes a user
|
||||||
|
Future<EmptyResponse> muteUser(String userId) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/moderation/mute',
|
||||||
|
data: {'target_id': userId},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unmutes a user
|
||||||
|
Future<EmptyResponse> unmuteUser(String userId) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/moderation/unmute',
|
||||||
|
data: {'target_id': userId},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutes the channel
|
||||||
|
Future<EmptyResponse> muteChannel(
|
||||||
|
String channelCid, {
|
||||||
|
Duration? expiration,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/moderation/mute/channel',
|
||||||
|
data: {
|
||||||
|
'channel_cid': channelCid,
|
||||||
|
if (expiration != null) 'expiration': expiration.inMilliseconds,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unmutes the channel
|
||||||
|
Future<EmptyResponse> unmuteChannel(
|
||||||
|
String channelCid,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/moderation/unmute/channel',
|
||||||
|
data: {'channel_cid': channelCid},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flag a message
|
||||||
|
Future<EmptyResponse> flagMessage(
|
||||||
|
String messageId,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/moderation/flag',
|
||||||
|
data: {'target_message_id': messageId},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unflag a message
|
||||||
|
Future<EmptyResponse> unflagMessage(
|
||||||
|
String messageId,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/moderation/unflag',
|
||||||
|
data: {'target_message_id': messageId},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flag a user
|
||||||
|
Future<EmptyResponse> flagUser(
|
||||||
|
String userId,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/moderation/flag',
|
||||||
|
data: {'target_user_id': userId},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unflag a user
|
||||||
|
Future<EmptyResponse> unflagUser(
|
||||||
|
String userId,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/moderation/unflag',
|
||||||
|
data: {'target_user_id': userId},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bans a user from all channels
|
||||||
|
Future<EmptyResponse> banUser(
|
||||||
|
String targetUserId, {
|
||||||
|
Map<String, Object?>? options,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/moderation/ban',
|
||||||
|
data: {
|
||||||
|
'target_user_id': targetUserId,
|
||||||
|
if (options != null) ...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove global ban for a user
|
||||||
|
Future<EmptyResponse> unbanUser(
|
||||||
|
String targetUserId, {
|
||||||
|
Map<String, Object?>? options,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.delete(
|
||||||
|
'/moderation/ban',
|
||||||
|
queryParameters: {
|
||||||
|
'target_user_id': targetUserId,
|
||||||
|
if (options != null) ...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return EmptyResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-23
@@ -1,9 +1,10 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'requests.g.dart';
|
part 'requests.g.dart';
|
||||||
|
|
||||||
/// Sorting options
|
/// Sorting options
|
||||||
@JsonSerializable(createFactory: false)
|
@JsonSerializable(includeIfNull: false)
|
||||||
class SortOption<T> {
|
class SortOption<T> {
|
||||||
/// Creates a new SortOption instance
|
/// Creates a new SortOption instance
|
||||||
///
|
///
|
||||||
@@ -18,6 +19,10 @@ class SortOption<T> {
|
|||||||
this.comparator,
|
this.comparator,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Create a new instance from a json
|
||||||
|
factory SortOption.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SortOptionFromJson(json);
|
||||||
|
|
||||||
/// Ascending order
|
/// Ascending order
|
||||||
// ignore: constant_identifier_names
|
// ignore: constant_identifier_names
|
||||||
static const ASC = 1;
|
static const ASC = 1;
|
||||||
@@ -41,8 +46,8 @@ class SortOption<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Pagination options.
|
/// Pagination options.
|
||||||
@JsonSerializable(createFactory: false, includeIfNull: false)
|
@JsonSerializable(includeIfNull: false)
|
||||||
class PaginationParams {
|
class PaginationParams extends Equatable {
|
||||||
/// Creates a new PaginationParams instance
|
/// Creates a new PaginationParams instance
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
@@ -62,6 +67,10 @@ class PaginationParams {
|
|||||||
this.lessThanOrEqual,
|
this.lessThanOrEqual,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Create a new instance from a json
|
||||||
|
factory PaginationParams.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PaginationParamsFromJson(json);
|
||||||
|
|
||||||
/// The amount of items requested from the APIs.
|
/// The amount of items requested from the APIs.
|
||||||
final int limit;
|
final int limit;
|
||||||
|
|
||||||
@@ -106,24 +115,12 @@ class PaginationParams {
|
|||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
List<Object?> get props => [
|
||||||
int get hashCode =>
|
limit,
|
||||||
runtimeType.hashCode ^
|
offset,
|
||||||
limit.hashCode ^
|
greaterThan,
|
||||||
offset.hashCode ^
|
greaterThanOrEqual,
|
||||||
greaterThan.hashCode ^
|
lessThan,
|
||||||
greaterThanOrEqual.hashCode ^
|
lessThanOrEqual,
|
||||||
lessThan.hashCode ^
|
];
|
||||||
lessThanOrEqual.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(covariant PaginationParams other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
limit == other.limit &&
|
|
||||||
offset == other.offset &&
|
|
||||||
greaterThan == other.greaterThan &&
|
|
||||||
greaterThanOrEqual == other.greaterThanOrEqual &&
|
|
||||||
lessThan == other.lessThan &&
|
|
||||||
lessThanOrEqual == other.lessThanOrEqual;
|
|
||||||
}
|
}
|
||||||
+18
@@ -6,12 +6,30 @@ part of 'requests.dart';
|
|||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
|
SortOption<T> _$SortOptionFromJson<T>(Map<String, dynamic> json) {
|
||||||
|
return SortOption<T>(
|
||||||
|
json['field'] as String,
|
||||||
|
direction: json['direction'] as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _$SortOptionToJson<T>(SortOption<T> instance) =>
|
Map<String, dynamic> _$SortOptionToJson<T>(SortOption<T> instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'field': instance.field,
|
'field': instance.field,
|
||||||
'direction': instance.direction,
|
'direction': instance.direction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) {
|
||||||
|
return PaginationParams(
|
||||||
|
limit: json['limit'] as int,
|
||||||
|
offset: json['offset'] as int,
|
||||||
|
greaterThan: json['id_gt'] as String?,
|
||||||
|
greaterThanOrEqual: json['id_gte'] as String?,
|
||||||
|
lessThan: json['id_lt'] as String?,
|
||||||
|
lessThanOrEqual: json['id_lte'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
||||||
final val = <String, dynamic>{
|
final val = <String, dynamic>{
|
||||||
'limit': instance.limit,
|
'limit': instance.limit,
|
||||||
+38
-10
@@ -1,14 +1,15 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/client.dart';
|
import 'package:stream_chat/src/client.dart';
|
||||||
import 'package:stream_chat/src/models/channel_model.dart';
|
import 'package:stream_chat/src/core/models/channel_model.dart';
|
||||||
import 'package:stream_chat/src/models/channel_state.dart';
|
import 'package:stream_chat/src/core/models/channel_state.dart';
|
||||||
import 'package:stream_chat/src/models/device.dart';
|
import 'package:stream_chat/src/core/models/device.dart';
|
||||||
import 'package:stream_chat/src/models/event.dart';
|
import 'package:stream_chat/src/core/models/event.dart';
|
||||||
import 'package:stream_chat/src/models/member.dart';
|
import 'package:stream_chat/src/core/models/member.dart';
|
||||||
import 'package:stream_chat/src/models/message.dart';
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
import 'package:stream_chat/src/models/reaction.dart';
|
import 'package:stream_chat/src/core/models/reaction.dart';
|
||||||
import 'package:stream_chat/src/models/read.dart';
|
import 'package:stream_chat/src/core/models/read.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
import 'package:stream_chat/src/errors/stream_chat_error.dart';
|
||||||
|
|
||||||
part 'responses.g.dart';
|
part 'responses.g.dart';
|
||||||
|
|
||||||
@@ -16,7 +17,34 @@ class _BaseResponse {
|
|||||||
String? duration;
|
String? duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Model response for [StreamChatClient.resync] api call
|
/// Model response for [StreamChatNetworkError] data
|
||||||
|
@JsonSerializable(createToJson: false)
|
||||||
|
class ErrorResponse extends _BaseResponse {
|
||||||
|
///
|
||||||
|
int? code;
|
||||||
|
|
||||||
|
///
|
||||||
|
String? message;
|
||||||
|
|
||||||
|
///
|
||||||
|
@JsonKey(name: 'StatusCode')
|
||||||
|
int? statusCode;
|
||||||
|
|
||||||
|
///
|
||||||
|
String? moreInfo;
|
||||||
|
|
||||||
|
/// Create a new instance from a json
|
||||||
|
static ErrorResponse fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ErrorResponseFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'ErrorResponse(code: $code, '
|
||||||
|
'message: $message, '
|
||||||
|
'statusCode: $statusCode, '
|
||||||
|
'moreInfo: $moreInfo)';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Model response for [StreamChatClient.sync] api call
|
||||||
@JsonSerializable(createToJson: false)
|
@JsonSerializable(createToJson: false)
|
||||||
class SyncResponse extends _BaseResponse {
|
class SyncResponse extends _BaseResponse {
|
||||||
/// The list of events
|
/// The list of events
|
||||||
+9
@@ -6,6 +6,15 @@ part of 'responses.dart';
|
|||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
|
ErrorResponse _$ErrorResponseFromJson(Map<String, dynamic> json) {
|
||||||
|
return ErrorResponse()
|
||||||
|
..duration = json['duration'] as String?
|
||||||
|
..code = json['code'] as int?
|
||||||
|
..message = json['message'] as String?
|
||||||
|
..statusCode = json['StatusCode'] as int?
|
||||||
|
..moreInfo = json['more_info'] as String?;
|
||||||
|
}
|
||||||
|
|
||||||
SyncResponse _$SyncResponseFromJson(Map<String, dynamic> json) {
|
SyncResponse _$SyncResponseFromJson(Map<String, dynamic> json) {
|
||||||
return SyncResponse()
|
return SyncResponse()
|
||||||
..duration = json['duration'] as String?
|
..duration = json['duration'] as String?
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/channel_api.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/device_api.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/general_api.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/guest_api.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/message_api.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/moderation_api.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/connection_id_manager.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/user_api.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/attachment_file_uploader.dart';
|
||||||
|
|
||||||
|
export 'device_api.dart' show PushProvider;
|
||||||
|
|
||||||
|
///
|
||||||
|
class StreamChatApi {
|
||||||
|
///
|
||||||
|
StreamChatApi(
|
||||||
|
String apiKey, {
|
||||||
|
StreamHttpClient? client,
|
||||||
|
StreamHttpClientOptions? options,
|
||||||
|
TokenManager? tokenManager,
|
||||||
|
ConnectionIdManager? connectionIdManager,
|
||||||
|
AttachmentFileUploader? attachmentFileUploader,
|
||||||
|
Logger? logger,
|
||||||
|
}) : _fileUploader = attachmentFileUploader,
|
||||||
|
_client = client ??
|
||||||
|
StreamHttpClient(
|
||||||
|
apiKey,
|
||||||
|
options: options,
|
||||||
|
tokenManager: tokenManager,
|
||||||
|
connectionIdManager: connectionIdManager,
|
||||||
|
logger: logger,
|
||||||
|
);
|
||||||
|
|
||||||
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
|
UserApi? _user;
|
||||||
|
|
||||||
|
///
|
||||||
|
UserApi get user => _user ??= UserApi(_client);
|
||||||
|
|
||||||
|
GuestApi? _guest;
|
||||||
|
|
||||||
|
///
|
||||||
|
GuestApi get guest => _guest ??= GuestApi(_client);
|
||||||
|
|
||||||
|
MessageApi? _message;
|
||||||
|
|
||||||
|
///
|
||||||
|
MessageApi get message => _message ??= MessageApi(_client);
|
||||||
|
|
||||||
|
ChannelApi? _channel;
|
||||||
|
|
||||||
|
///
|
||||||
|
ChannelApi get channel => _channel ??= ChannelApi(_client);
|
||||||
|
|
||||||
|
DeviceApi? _device;
|
||||||
|
|
||||||
|
///
|
||||||
|
DeviceApi get device => _device ??= DeviceApi(_client);
|
||||||
|
|
||||||
|
ModerationApi? _moderation;
|
||||||
|
|
||||||
|
///
|
||||||
|
ModerationApi get moderation => _moderation ??= ModerationApi(_client);
|
||||||
|
|
||||||
|
GeneralApi? _general;
|
||||||
|
|
||||||
|
///
|
||||||
|
GeneralApi get general => _general ??= GeneralApi(_client);
|
||||||
|
|
||||||
|
AttachmentFileUploader? _fileUploader;
|
||||||
|
|
||||||
|
///
|
||||||
|
AttachmentFileUploader get fileUploader =>
|
||||||
|
_fileUploader ??= StreamAttachmentFileUploader(_client);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:stream_chat/src/core/api/requests.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/filter.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class UserApi {
|
||||||
|
///
|
||||||
|
UserApi(this._client);
|
||||||
|
|
||||||
|
final StreamHttpClient _client;
|
||||||
|
|
||||||
|
/// Requests users with a given query.
|
||||||
|
Future<QueryUsersResponse> queryUsers({
|
||||||
|
bool presence = false,
|
||||||
|
Filter? filter,
|
||||||
|
List<SortOption>? sort,
|
||||||
|
PaginationParams? pagination,
|
||||||
|
}) async {
|
||||||
|
final response = await _client.get(
|
||||||
|
'/users',
|
||||||
|
queryParameters: {
|
||||||
|
'payload': jsonEncode({
|
||||||
|
'presence': presence,
|
||||||
|
if (sort != null) 'sort': sort,
|
||||||
|
if (filter != null) 'filter_conditions': filter,
|
||||||
|
if (pagination != null) ...pagination.toJson(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return QueryUsersResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Batch update a list of users
|
||||||
|
Future<UpdateUsersResponse> updateUsers(
|
||||||
|
List<User> users,
|
||||||
|
) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
'/users',
|
||||||
|
data: {
|
||||||
|
'users': {for (final user in users) user.id: user},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return UpdateUsersResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// ignore_for_file: use_setters_to_change_properties
|
||||||
|
|
||||||
|
///
|
||||||
|
class ConnectionIdManager {
|
||||||
|
///
|
||||||
|
ConnectionIdManager({
|
||||||
|
String? connectionId,
|
||||||
|
}) : _connectionId = connectionId;
|
||||||
|
|
||||||
|
String? _connectionId;
|
||||||
|
|
||||||
|
///
|
||||||
|
String? get connectionId => _connectionId;
|
||||||
|
|
||||||
|
///
|
||||||
|
bool get hasConnectionId => _connectionId != null;
|
||||||
|
|
||||||
|
///
|
||||||
|
void setConnectionId(String connectionId) {
|
||||||
|
_connectionId = connectionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
void reset() {
|
||||||
|
_connectionId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/token.dart';
|
||||||
|
|
||||||
|
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||||
|
import 'package:stream_chat/src/errors/chat_error_code.dart';
|
||||||
|
import 'package:stream_chat/src/errors/stream_chat_error.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class AuthInterceptor extends Interceptor {
|
||||||
|
///
|
||||||
|
AuthInterceptor(this._httpClient, this._tokenManager);
|
||||||
|
|
||||||
|
final Dio _httpClient;
|
||||||
|
|
||||||
|
///
|
||||||
|
final TokenManager _tokenManager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onRequest(
|
||||||
|
RequestOptions options,
|
||||||
|
RequestInterceptorHandler handler,
|
||||||
|
) async {
|
||||||
|
late Token token;
|
||||||
|
try {
|
||||||
|
token = await _tokenManager.loadToken();
|
||||||
|
} catch (_) {
|
||||||
|
final error = StreamChatError(ChatErrorCode.undefinedToken);
|
||||||
|
final dioError = StreamChatDioError(
|
||||||
|
error: error,
|
||||||
|
requestOptions: options,
|
||||||
|
);
|
||||||
|
return handler.reject(dioError);
|
||||||
|
}
|
||||||
|
final params = {'user_id': token.userId};
|
||||||
|
final headers = {
|
||||||
|
'Authorization': token.rawValue,
|
||||||
|
'stream-auth-type': token.authType.raw,
|
||||||
|
};
|
||||||
|
options..queryParameters.addAll(params)..headers.addAll(headers);
|
||||||
|
return handler.next(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onError(
|
||||||
|
DioError err,
|
||||||
|
ErrorInterceptorHandler handler,
|
||||||
|
) async {
|
||||||
|
ErrorResponse? error;
|
||||||
|
final data = err.response?.data;
|
||||||
|
if (data != null) error = ErrorResponse.fromJson(data);
|
||||||
|
if (error?.code == ChatErrorCode.tokenExpired.code) {
|
||||||
|
if (_tokenManager.isStatic) return handler.next(err);
|
||||||
|
_httpClient.lock();
|
||||||
|
await _tokenManager.loadToken(refresh: true);
|
||||||
|
_httpClient.unlock();
|
||||||
|
try {
|
||||||
|
final options = err.requestOptions;
|
||||||
|
final response = await _httpClient.request(
|
||||||
|
options.path,
|
||||||
|
cancelToken: options.cancelToken,
|
||||||
|
data: options.data,
|
||||||
|
onReceiveProgress: options.onReceiveProgress,
|
||||||
|
onSendProgress: options.onSendProgress,
|
||||||
|
queryParameters: options.queryParameters,
|
||||||
|
options: Options(
|
||||||
|
method: options.method,
|
||||||
|
sendTimeout: options.sendTimeout,
|
||||||
|
receiveTimeout: options.receiveTimeout,
|
||||||
|
extra: options.extra,
|
||||||
|
headers: options.headers,
|
||||||
|
responseType: options.responseType,
|
||||||
|
contentType: options.contentType,
|
||||||
|
validateStatus: options.validateStatus,
|
||||||
|
receiveDataWhenStatusError: options.receiveDataWhenStatusError,
|
||||||
|
followRedirects: options.followRedirects,
|
||||||
|
maxRedirects: options.maxRedirects,
|
||||||
|
requestEncoder: options.requestEncoder,
|
||||||
|
responseDecoder: options.responseDecoder,
|
||||||
|
listFormat: options.listFormat,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return handler.resolve(response);
|
||||||
|
} on DioError catch (error) {
|
||||||
|
return handler.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handler.next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import 'package:stream_chat/src/core/http/connection_id_manager.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class ConnectionIdInterceptor extends Interceptor {
|
||||||
|
///
|
||||||
|
ConnectionIdInterceptor(this.connectionIdManager);
|
||||||
|
|
||||||
|
///
|
||||||
|
final ConnectionIdManager connectionIdManager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRequest(
|
||||||
|
RequestOptions options,
|
||||||
|
RequestInterceptorHandler handler,
|
||||||
|
) async {
|
||||||
|
if (connectionIdManager.hasConnectionId) {
|
||||||
|
options.queryParameters.addAll({
|
||||||
|
'connection_id': connectionIdManager.connectionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handler.next(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,338 @@
|
|||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
enum InterceptStep {
|
||||||
|
///
|
||||||
|
request,
|
||||||
|
|
||||||
|
///
|
||||||
|
response,
|
||||||
|
|
||||||
|
///
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
typedef LogPrint = void Function(InterceptStep step, Object object);
|
||||||
|
|
||||||
|
void _defaultLogPrint(InterceptStep step, Object object) => print(object);
|
||||||
|
|
||||||
|
///
|
||||||
|
class LoggingInterceptor extends Interceptor {
|
||||||
|
///
|
||||||
|
LoggingInterceptor({
|
||||||
|
this.request = true,
|
||||||
|
this.requestHeader = false,
|
||||||
|
this.requestBody = true,
|
||||||
|
this.responseHeader = false,
|
||||||
|
this.responseBody = true,
|
||||||
|
this.error = true,
|
||||||
|
this.maxWidth = 120,
|
||||||
|
this.compact = true,
|
||||||
|
this.logPrint = _defaultLogPrint,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Print request [Options]
|
||||||
|
final bool request;
|
||||||
|
|
||||||
|
/// Print request header [Options.headers]
|
||||||
|
final bool requestHeader;
|
||||||
|
|
||||||
|
/// Print request data [Options.data]
|
||||||
|
final bool requestBody;
|
||||||
|
|
||||||
|
/// Print [Response.data]
|
||||||
|
final bool responseBody;
|
||||||
|
|
||||||
|
/// Print [Response.headers]
|
||||||
|
final bool responseHeader;
|
||||||
|
|
||||||
|
/// Print error message
|
||||||
|
final bool error;
|
||||||
|
|
||||||
|
/// InitialTab count to logPrint json response
|
||||||
|
static const int initialTab = 1;
|
||||||
|
|
||||||
|
/// 1 tab length
|
||||||
|
static const String tabStep = ' ';
|
||||||
|
|
||||||
|
/// Print compact json response
|
||||||
|
final bool compact;
|
||||||
|
|
||||||
|
/// Width size per logPrint
|
||||||
|
final int maxWidth;
|
||||||
|
|
||||||
|
/// Log printer; defaults logPrint log to console.
|
||||||
|
/// In flutter, you'd better use debugPrint.
|
||||||
|
/// you can also write log in a file.
|
||||||
|
void Function(InterceptStep step, Object object) logPrint;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||||
|
if (request) {
|
||||||
|
_printRequestHeader(_logPrintRequest, options);
|
||||||
|
}
|
||||||
|
if (requestHeader) {
|
||||||
|
_printMapAsTable(
|
||||||
|
_logPrintRequest,
|
||||||
|
options.queryParameters,
|
||||||
|
header: 'Query Parameters',
|
||||||
|
);
|
||||||
|
final requestHeaders = <String, Object?>{...options.headers};
|
||||||
|
requestHeaders['contentType'] = options.contentType?.toString();
|
||||||
|
requestHeaders['responseType'] = options.responseType.toString();
|
||||||
|
requestHeaders['followRedirects'] = options.followRedirects;
|
||||||
|
requestHeaders['connectTimeout'] = options.connectTimeout;
|
||||||
|
requestHeaders['receiveTimeout'] = options.receiveTimeout;
|
||||||
|
_printMapAsTable(_logPrintRequest, requestHeaders, header: 'Headers');
|
||||||
|
_printMapAsTable(_logPrintRequest, options.extra, header: 'Extras');
|
||||||
|
}
|
||||||
|
if (requestBody && options.method != 'GET') {
|
||||||
|
final dynamic data = options.data;
|
||||||
|
if (data != null) {
|
||||||
|
if (data is Map) {
|
||||||
|
_printMapAsTable(
|
||||||
|
_logPrintRequest,
|
||||||
|
options.data as Map?,
|
||||||
|
header: 'Body',
|
||||||
|
);
|
||||||
|
} else if (data is FormData) {
|
||||||
|
final formDataMap = <String, dynamic>{}
|
||||||
|
..addEntries(data.fields)
|
||||||
|
..addEntries(data.files);
|
||||||
|
_printMapAsTable(_logPrintRequest, formDataMap,
|
||||||
|
header: 'Form data | ${data.boundary}');
|
||||||
|
} else {
|
||||||
|
_printBlock(_logPrintRequest, data.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onRequest(options, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onError(DioError err, ErrorInterceptorHandler handler) {
|
||||||
|
if (error) {
|
||||||
|
if (err.type == DioErrorType.response) {
|
||||||
|
final uri = err.response?.requestOptions.uri;
|
||||||
|
_printBoxed(
|
||||||
|
_logPrintError,
|
||||||
|
header:
|
||||||
|
'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}',
|
||||||
|
text: uri.toString(),
|
||||||
|
);
|
||||||
|
if (err.response != null && err.response?.data != null) {
|
||||||
|
_logPrintError('╔ ${err.type.toString()}');
|
||||||
|
_printResponse(_logPrintError, err.response!);
|
||||||
|
}
|
||||||
|
_printLine(_logPrintError, '╚');
|
||||||
|
_logPrintError('');
|
||||||
|
} else {
|
||||||
|
_printBoxed(
|
||||||
|
_logPrintError,
|
||||||
|
header: 'DioError ║ ${err.type}',
|
||||||
|
text: err.message,
|
||||||
|
);
|
||||||
|
_printRequestHeader(_logPrintError, err.requestOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onError(err, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
|
_printResponseHeader(_logPrintResponse, response);
|
||||||
|
if (responseHeader) {
|
||||||
|
final responseHeaders = <String, String>{};
|
||||||
|
response.headers
|
||||||
|
.forEach((k, list) => responseHeaders[k] = list.toString());
|
||||||
|
_printMapAsTable(_logPrintResponse, responseHeaders, header: 'Headers');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseBody) {
|
||||||
|
_logPrintResponse('╔ Body');
|
||||||
|
_logPrintResponse('║');
|
||||||
|
_printResponse(_logPrintResponse, response);
|
||||||
|
_logPrintResponse('║');
|
||||||
|
_printLine(_logPrintResponse, '╚');
|
||||||
|
}
|
||||||
|
super.onResponse(response, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _printBoxed(
|
||||||
|
void Function(Object) logPrint, {
|
||||||
|
String? header,
|
||||||
|
String? text,
|
||||||
|
}) {
|
||||||
|
logPrint('');
|
||||||
|
logPrint('╔╣ $header');
|
||||||
|
logPrint('║ $text');
|
||||||
|
_printLine(logPrint, '╚');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _printResponse(void Function(Object) logPrint, Response response) {
|
||||||
|
if (response.data != null) {
|
||||||
|
if (response.data is Map) {
|
||||||
|
_printPrettyMap(logPrint, response.data as Map);
|
||||||
|
} else if (response.data is List) {
|
||||||
|
logPrint('║${_indent()}[');
|
||||||
|
_printList(logPrint, response.data as List);
|
||||||
|
logPrint('║${_indent()}[');
|
||||||
|
} else {
|
||||||
|
_printBlock(logPrint, response.data.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _printResponseHeader(void Function(Object) logPrint, Response response) {
|
||||||
|
final uri = response.requestOptions.uri;
|
||||||
|
final method = response.requestOptions.method;
|
||||||
|
_printBoxed(
|
||||||
|
logPrint,
|
||||||
|
header:
|
||||||
|
'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}',
|
||||||
|
text: uri.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _printRequestHeader(
|
||||||
|
void Function(Object) logPrint, RequestOptions options) {
|
||||||
|
final uri = options.uri;
|
||||||
|
final method = options.method;
|
||||||
|
_printBoxed(logPrint, header: 'Request ║ $method ', text: uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _printLine(void Function(Object) logPrint,
|
||||||
|
[String pre = '', String suf = '╝']) =>
|
||||||
|
logPrint('$pre${'═' * maxWidth}$suf');
|
||||||
|
|
||||||
|
void _printKV(void Function(Object) logPrint, String? key, Object? v) {
|
||||||
|
final pre = '╟ $key: ';
|
||||||
|
final msg = v.toString();
|
||||||
|
|
||||||
|
if (pre.length + msg.length > maxWidth) {
|
||||||
|
logPrint(pre);
|
||||||
|
_printBlock(logPrint, msg);
|
||||||
|
} else {
|
||||||
|
logPrint('$pre$msg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _printBlock(void Function(Object) logPrint, String msg) {
|
||||||
|
final lines = (msg.length / maxWidth).ceil();
|
||||||
|
for (var i = 0; i < lines; ++i) {
|
||||||
|
logPrint((i >= 0 ? '║ ' : '') +
|
||||||
|
msg.substring(i * maxWidth,
|
||||||
|
math.min<int>(i * maxWidth + maxWidth, msg.length)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _indent([int tabCount = initialTab]) => tabStep * tabCount;
|
||||||
|
|
||||||
|
void _printPrettyMap(
|
||||||
|
void Function(Object) logPrint,
|
||||||
|
Map data, {
|
||||||
|
int tabs = initialTab,
|
||||||
|
bool isListItem = false,
|
||||||
|
bool isLast = false,
|
||||||
|
}) {
|
||||||
|
var _tabs = tabs;
|
||||||
|
final isRoot = _tabs == initialTab;
|
||||||
|
final initialIndent = _indent(_tabs);
|
||||||
|
_tabs++;
|
||||||
|
|
||||||
|
if (isRoot || isListItem) logPrint('║$initialIndent{');
|
||||||
|
|
||||||
|
data.keys.toList().asMap().forEach((index, dynamic key) {
|
||||||
|
final isLast = index == data.length - 1;
|
||||||
|
dynamic value = data[key];
|
||||||
|
if (value is String) {
|
||||||
|
value = '"${value.toString().replaceAll(RegExp(r'(\r|\n)+'), " ")}"';
|
||||||
|
}
|
||||||
|
if (value is Map) {
|
||||||
|
if (compact) {
|
||||||
|
logPrint('║${_indent(_tabs)} $key: $value${!isLast ? ',' : ''}');
|
||||||
|
} else {
|
||||||
|
logPrint('║${_indent(_tabs)} $key: {');
|
||||||
|
_printPrettyMap(logPrint, value, tabs: _tabs);
|
||||||
|
}
|
||||||
|
} else if (value is List) {
|
||||||
|
if (compact) {
|
||||||
|
logPrint('║${_indent(_tabs)} $key: ${value.toString()}');
|
||||||
|
} else {
|
||||||
|
logPrint('║${_indent(_tabs)} $key: [');
|
||||||
|
_printList(logPrint, value, tabs: _tabs);
|
||||||
|
logPrint('║${_indent(_tabs)} ]${isLast ? '' : ','}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final msg = value.toString().replaceAll('\n', '');
|
||||||
|
final indent = _indent(_tabs);
|
||||||
|
final linWidth = maxWidth - indent.length;
|
||||||
|
if (msg.length + indent.length > linWidth) {
|
||||||
|
final lines = (msg.length / linWidth).ceil();
|
||||||
|
for (var i = 0; i < lines; ++i) {
|
||||||
|
logPrint('║${_indent(_tabs)} ${msg.substring(
|
||||||
|
i * linWidth,
|
||||||
|
math.min<int>(i * linWidth + linWidth, msg.length),
|
||||||
|
)}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logPrint('║${_indent(_tabs)} $key: $msg${!isLast ? ',' : ''}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logPrint('║$initialIndent}${isListItem && !isLast ? ',' : ''}');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _printList(
|
||||||
|
void Function(Object) logPrint,
|
||||||
|
List list, {
|
||||||
|
int tabs = initialTab,
|
||||||
|
}) {
|
||||||
|
list.asMap().forEach((i, dynamic e) {
|
||||||
|
final isLast = i == list.length - 1;
|
||||||
|
if (e is Map) {
|
||||||
|
if (compact) {
|
||||||
|
logPrint('║${_indent(tabs)} $e${!isLast ? ',' : ''}');
|
||||||
|
} else {
|
||||||
|
_printPrettyMap(logPrint, e,
|
||||||
|
tabs: tabs + 1, isListItem: true, isLast: isLast);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logPrint('║${_indent(tabs + 2)} $e${isLast ? '' : ','}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _canFlattenMap(Map map) =>
|
||||||
|
map.values.where((dynamic val) => val is Map || val is List).isEmpty &&
|
||||||
|
map.toString().length < maxWidth;
|
||||||
|
|
||||||
|
bool _canFlattenList(List list) =>
|
||||||
|
list.length < 10 && list.toString().length < maxWidth;
|
||||||
|
|
||||||
|
void _printMapAsTable(
|
||||||
|
void Function(Object) logPrint,
|
||||||
|
Map? map, {
|
||||||
|
String? header,
|
||||||
|
}) {
|
||||||
|
if (map == null || map.isEmpty) return;
|
||||||
|
logPrint('╔ $header ');
|
||||||
|
map.forEach((dynamic key, dynamic value) =>
|
||||||
|
_printKV(logPrint, key.toString(), value));
|
||||||
|
_printLine(logPrint, '╚');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _logPrintRequest(Object object) =>
|
||||||
|
logPrint(InterceptStep.request, object);
|
||||||
|
|
||||||
|
void _logPrintResponse(Object object) =>
|
||||||
|
logPrint(InterceptStep.response, object);
|
||||||
|
|
||||||
|
void _logPrintError(Object object) => logPrint(InterceptStep.error, object);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:stream_chat/src/errors/stream_chat_error.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class StreamChatDioError extends DioError {
|
||||||
|
///
|
||||||
|
StreamChatDioError({
|
||||||
|
required this.error,
|
||||||
|
required RequestOptions requestOptions,
|
||||||
|
Response? response,
|
||||||
|
DioErrorType type = DioErrorType.other,
|
||||||
|
}) : super(
|
||||||
|
error: error,
|
||||||
|
requestOptions: requestOptions,
|
||||||
|
response: response,
|
||||||
|
type: type,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final StreamChatError error;
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:meta/meta.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';
|
||||||
|
import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||||
|
import 'package:stream_chat/src/errors/stream_chat_error.dart';
|
||||||
|
import 'package:stream_chat/src/platform_detector/platform_detector.dart';
|
||||||
|
import 'package:stream_chat/version.dart';
|
||||||
|
|
||||||
|
import 'package:stream_chat/src/core/http/connection_id_manager.dart';
|
||||||
|
|
||||||
|
part 'stream_http_client_options.dart';
|
||||||
|
|
||||||
|
/// This is where we configure the base url, headers,
|
||||||
|
/// query parameters and convenient methods for http verbs with error parsing.
|
||||||
|
class StreamHttpClient {
|
||||||
|
/// [StreamHttpClient] constructor
|
||||||
|
StreamHttpClient(
|
||||||
|
this.apiKey, {
|
||||||
|
Dio? dio,
|
||||||
|
StreamHttpClientOptions? options,
|
||||||
|
TokenManager? tokenManager,
|
||||||
|
ConnectionIdManager? connectionIdManager,
|
||||||
|
Logger? logger,
|
||||||
|
}) : _options = options ?? const StreamHttpClientOptions(),
|
||||||
|
httpClient = dio ?? Dio() {
|
||||||
|
httpClient
|
||||||
|
..options.baseUrl = _options.baseUrl
|
||||||
|
..options.receiveTimeout = _options.receiveTimeout.inMilliseconds
|
||||||
|
..options.connectTimeout = _options.connectTimeout.inMilliseconds
|
||||||
|
..options.queryParameters = {'api_key': apiKey}
|
||||||
|
..options.headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Stream-Client': _options.userAgent,
|
||||||
|
'Content-Encoding': 'application/gzip',
|
||||||
|
}
|
||||||
|
..interceptors.addAll([
|
||||||
|
if (tokenManager != null) AuthInterceptor(httpClient, tokenManager),
|
||||||
|
if (connectionIdManager != null)
|
||||||
|
ConnectionIdInterceptor(connectionIdManager),
|
||||||
|
if (logger != null && logger.level != Level.OFF)
|
||||||
|
LoggingInterceptor(
|
||||||
|
requestHeader: true,
|
||||||
|
logPrint: (step, message) {
|
||||||
|
switch (step) {
|
||||||
|
case InterceptStep.request:
|
||||||
|
return logger.info(message);
|
||||||
|
case InterceptStep.response:
|
||||||
|
return logger.info(message);
|
||||||
|
case InterceptStep.error:
|
||||||
|
return logger.severe(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Your project Stream Chat api key.
|
||||||
|
/// Find your API keys here https://getstream.io/dashboard/
|
||||||
|
final String apiKey;
|
||||||
|
|
||||||
|
/// Your project Stream Chat ClientOptions
|
||||||
|
final StreamHttpClientOptions _options;
|
||||||
|
|
||||||
|
/// [Dio] httpClient
|
||||||
|
/// It's been chosen because it's easy to use
|
||||||
|
/// and supports interesting features out of the box
|
||||||
|
/// (Interceptors, Global configuration, FormData, File downloading etc.)
|
||||||
|
@visibleForTesting
|
||||||
|
final Dio httpClient;
|
||||||
|
|
||||||
|
/// Shuts down the [httpClient].
|
||||||
|
///
|
||||||
|
/// If [force] is `false` (the default) the [httpClient] will be kept alive
|
||||||
|
/// until all active connections are done. If [force] is `true` any active
|
||||||
|
/// connections will be closed to immediately release all resources. These
|
||||||
|
/// closed connections will receive an error event to indicate that the client
|
||||||
|
/// was shut down. In both cases trying to establish a new connection after
|
||||||
|
/// calling [close] will throw an exception.
|
||||||
|
void close({bool force = false}) => httpClient.close(force: force);
|
||||||
|
|
||||||
|
StreamChatNetworkError _parseError(DioError err) {
|
||||||
|
// locally thrown dio error
|
||||||
|
if (err is StreamChatDioError) {
|
||||||
|
final code = err.error.code;
|
||||||
|
final message = err.error.message;
|
||||||
|
return StreamChatNetworkError.raw(code: code, message: message);
|
||||||
|
}
|
||||||
|
// real network request dio error
|
||||||
|
return StreamChatNetworkError.fromDioError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handy method to make http GET request with error parsing.
|
||||||
|
Future<Response<T>> get<T>(
|
||||||
|
String path, {
|
||||||
|
Map<String, Object?>? queryParameters,
|
||||||
|
Map<String, Object?>? headers,
|
||||||
|
ProgressCallback? onReceiveProgress,
|
||||||
|
CancelToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await httpClient.get<T>(
|
||||||
|
path,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
options: Options(headers: headers),
|
||||||
|
onReceiveProgress: onReceiveProgress,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} on DioError catch (error) {
|
||||||
|
throw _parseError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handy method to make http POST request with error parsing.
|
||||||
|
Future<Response<T>> post<T>(
|
||||||
|
String path, {
|
||||||
|
Object? data,
|
||||||
|
Map<String, Object?>? queryParameters,
|
||||||
|
Map<String, Object?>? headers,
|
||||||
|
ProgressCallback? onSendProgress,
|
||||||
|
ProgressCallback? onReceiveProgress,
|
||||||
|
CancelToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await httpClient.post<T>(
|
||||||
|
path,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
data: data,
|
||||||
|
options: Options(headers: headers),
|
||||||
|
onSendProgress: onSendProgress,
|
||||||
|
onReceiveProgress: onReceiveProgress,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} on DioError catch (error) {
|
||||||
|
throw _parseError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handy method to make http DELETE request with error parsing.
|
||||||
|
Future<Response<T>> delete<T>(
|
||||||
|
String path, {
|
||||||
|
Map<String, Object?>? queryParameters,
|
||||||
|
Map<String, Object?>? headers,
|
||||||
|
CancelToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await httpClient.delete<T>(
|
||||||
|
path,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
options: Options(headers: headers),
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} on DioError catch (error) {
|
||||||
|
throw _parseError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handy method to make http PATCH request with error parsing.
|
||||||
|
Future<Response<T>> patch<T>(
|
||||||
|
String path, {
|
||||||
|
Object? data,
|
||||||
|
Map<String, Object?>? queryParameters,
|
||||||
|
Map<String, Object?>? headers,
|
||||||
|
ProgressCallback? onSendProgress,
|
||||||
|
ProgressCallback? onReceiveProgress,
|
||||||
|
CancelToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await httpClient.patch<T>(
|
||||||
|
path,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
data: data,
|
||||||
|
options: Options(headers: headers),
|
||||||
|
onSendProgress: onSendProgress,
|
||||||
|
onReceiveProgress: onReceiveProgress,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} on DioError catch (error) {
|
||||||
|
throw _parseError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handy method to make http PUT request with error parsing.
|
||||||
|
Future<Response<T>> put<T>(
|
||||||
|
String path, {
|
||||||
|
Object? data,
|
||||||
|
Map<String, Object?>? queryParameters,
|
||||||
|
Map<String, Object?>? headers,
|
||||||
|
ProgressCallback? onSendProgress,
|
||||||
|
ProgressCallback? onReceiveProgress,
|
||||||
|
CancelToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await httpClient.put<T>(
|
||||||
|
path,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
data: data,
|
||||||
|
options: Options(headers: headers),
|
||||||
|
onSendProgress: onSendProgress,
|
||||||
|
onReceiveProgress: onReceiveProgress,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} on DioError catch (error) {
|
||||||
|
throw _parseError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handy method to post files with error parsing.
|
||||||
|
Future<Response<T>> postFile<T>(
|
||||||
|
String path,
|
||||||
|
MultipartFile file, {
|
||||||
|
Map<String, Object?>? queryParameters,
|
||||||
|
Map<String, Object?>? headers,
|
||||||
|
ProgressCallback? onSendProgress,
|
||||||
|
ProgressCallback? onReceiveProgress,
|
||||||
|
CancelToken? cancelToken,
|
||||||
|
}) async {
|
||||||
|
final formData = FormData.fromMap({'file': file});
|
||||||
|
final response = await post<T>(
|
||||||
|
path,
|
||||||
|
data: formData,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
headers: headers,
|
||||||
|
onSendProgress: onSendProgress,
|
||||||
|
onReceiveProgress: onReceiveProgress,
|
||||||
|
cancelToken: cancelToken,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
part of 'stream_http_client.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
enum Location {
|
||||||
|
///
|
||||||
|
usEast,
|
||||||
|
|
||||||
|
///
|
||||||
|
euWest,
|
||||||
|
|
||||||
|
///
|
||||||
|
mumbai,
|
||||||
|
|
||||||
|
///
|
||||||
|
sydney,
|
||||||
|
|
||||||
|
///
|
||||||
|
singapore,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
extension LocationX on Location {
|
||||||
|
///
|
||||||
|
String get name => {
|
||||||
|
Location.usEast: 'us-east',
|
||||||
|
Location.euWest: 'dublin',
|
||||||
|
Location.mumbai: 'mumbai',
|
||||||
|
Location.sydney: 'sydney',
|
||||||
|
Location.singapore: 'singapore',
|
||||||
|
}[this]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _defaultBaseURL = 'https://chat-us-east-1.stream-io-api.com';
|
||||||
|
|
||||||
|
/// Client options to modify [StreamHttpClient]
|
||||||
|
class StreamHttpClientOptions {
|
||||||
|
/// Instantiates a new [StreamHttpClientOptions]
|
||||||
|
const StreamHttpClientOptions({
|
||||||
|
String? baseUrl,
|
||||||
|
this.location,
|
||||||
|
this.connectTimeout = const Duration(seconds: 6),
|
||||||
|
this.receiveTimeout = const Duration(seconds: 6),
|
||||||
|
}) : _baseUrl = baseUrl ?? _defaultBaseURL;
|
||||||
|
|
||||||
|
final String _baseUrl;
|
||||||
|
|
||||||
|
/// base url to use with client.
|
||||||
|
String get baseUrl {
|
||||||
|
if (location == null) return _baseUrl;
|
||||||
|
const serviceName = 'chat';
|
||||||
|
final locationName = location!.name;
|
||||||
|
const baseDomainName = 'stream-io-api.com';
|
||||||
|
return 'https://$serviceName-proxy-$locationName.$baseDomainName';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// data center to use with client
|
||||||
|
final Location? location;
|
||||||
|
|
||||||
|
/// connect timeout, default to 6s
|
||||||
|
final Duration connectTimeout;
|
||||||
|
|
||||||
|
/// received timeout, default to 6s
|
||||||
|
final Duration receiveTimeout;
|
||||||
|
|
||||||
|
/// Get the current user agent
|
||||||
|
String get userAgent => 'stream-chat-dart-client-'
|
||||||
|
'${CurrentPlatform.name}-'
|
||||||
|
'${PACKAGE_VERSION.split('+')[0]}';
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:jose/jose.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
import 'package:stream_chat/src/core/utils.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
typedef GuestTokenProvider = Future<String> Function(User user);
|
||||||
|
|
||||||
|
///
|
||||||
|
enum AuthType {
|
||||||
|
///
|
||||||
|
jwt,
|
||||||
|
|
||||||
|
///
|
||||||
|
anonymous,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
extension AuthTypeX on AuthType {
|
||||||
|
///
|
||||||
|
String get raw => {
|
||||||
|
AuthType.jwt: 'jwt',
|
||||||
|
AuthType.anonymous: 'anonymous',
|
||||||
|
}[this]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Token designed to store the JWT and the user it is related to.
|
||||||
|
class Token extends Equatable {
|
||||||
|
const Token._({
|
||||||
|
required this.rawValue,
|
||||||
|
required this.userId,
|
||||||
|
required this.authType,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The token that can be used when user is unknown.
|
||||||
|
/// Is used by `anonymous` token provider.
|
||||||
|
factory Token.anonymous({String? userId}) => Token._(
|
||||||
|
rawValue: '',
|
||||||
|
userId: userId ?? randomId(),
|
||||||
|
authType: AuthType.anonymous,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Creates a [Token] instance from the provided [rawValue] if it's valid.
|
||||||
|
factory Token.fromRawValue(String rawValue) {
|
||||||
|
final jwtBody = JsonWebToken.unverified(rawValue);
|
||||||
|
final userId = jwtBody.claims.getTyped<String>('user_id');
|
||||||
|
assert(
|
||||||
|
userId != null,
|
||||||
|
'Invalid `token`, It should contain `user_id`',
|
||||||
|
);
|
||||||
|
return Token._(rawValue: rawValue, userId: userId!, authType: AuthType.jwt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The token which can be used during the development.
|
||||||
|
/// Is used by `development(userId:)` token provider.
|
||||||
|
factory Token.development(String userId) {
|
||||||
|
const devSignature = 'devtoken';
|
||||||
|
const header = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';
|
||||||
|
final payload = json.encode({'user_id': userId});
|
||||||
|
final payloadBytes = utf8.encode(payload);
|
||||||
|
final payloadB64 = base64.encode(payloadBytes);
|
||||||
|
final jwt = '$header.$payloadB64.$devSignature';
|
||||||
|
return Token._(rawValue: jwt, userId: userId, authType: AuthType.jwt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The token which designed to be used for guest users.
|
||||||
|
static Future<Token> guest(User user, GuestTokenProvider provider) async {
|
||||||
|
final rawToken = await provider(user);
|
||||||
|
return Token.fromRawValue(rawToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
final AuthType authType;
|
||||||
|
|
||||||
|
///
|
||||||
|
final String rawValue;
|
||||||
|
|
||||||
|
///
|
||||||
|
final String userId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [authType, rawValue, userId];
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import 'package:stream_chat/src/core/http/token.dart';
|
||||||
|
|
||||||
|
/// A function which can be used to request a Stream Chat API token from your
|
||||||
|
/// own backend server. Function requires a single [userId].
|
||||||
|
typedef TokenProvider = Future<String> Function(String userId);
|
||||||
|
|
||||||
|
///
|
||||||
|
class TokenManager {
|
||||||
|
///
|
||||||
|
TokenManager({
|
||||||
|
String? userId,
|
||||||
|
Token? token,
|
||||||
|
TokenProvider? tokenProvider,
|
||||||
|
}) : _userId = userId,
|
||||||
|
_token = token,
|
||||||
|
_provider = tokenProvider;
|
||||||
|
|
||||||
|
String? _type;
|
||||||
|
Token? _token;
|
||||||
|
|
||||||
|
TokenProvider? _provider;
|
||||||
|
|
||||||
|
String? _userId;
|
||||||
|
|
||||||
|
/// User id to which this TokenManager is configured to
|
||||||
|
String? get userId => _userId;
|
||||||
|
|
||||||
|
///
|
||||||
|
bool get isStatic => _type == 'static';
|
||||||
|
|
||||||
|
///
|
||||||
|
Future<Token> setTokenOrProvider(
|
||||||
|
String userId, {
|
||||||
|
Token? token,
|
||||||
|
TokenProvider? provider,
|
||||||
|
}) async {
|
||||||
|
assert(() {
|
||||||
|
if (token == null && provider == null) {
|
||||||
|
throw AssertionError('Provide at-least token or provider');
|
||||||
|
}
|
||||||
|
if (token != null && provider != null) {
|
||||||
|
throw AssertionError("Can't set both token and provider");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}(), '');
|
||||||
|
|
||||||
|
_userId = userId;
|
||||||
|
|
||||||
|
if (token != null) {
|
||||||
|
_type = 'static';
|
||||||
|
_token = token;
|
||||||
|
}
|
||||||
|
if (provider != null) {
|
||||||
|
_type = 'provider';
|
||||||
|
_provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
Future<Token> loadToken({bool refresh = false}) async {
|
||||||
|
assert(
|
||||||
|
_userId != null && _type != null,
|
||||||
|
'Please call `setTokenOrProvider` before calling `loadToken`',
|
||||||
|
);
|
||||||
|
if (refresh || _token == null) {
|
||||||
|
final rawValue = await _provider!(_userId!);
|
||||||
|
_token = Token.fromRawValue(rawValue);
|
||||||
|
}
|
||||||
|
return _token!;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
void reset() {
|
||||||
|
_userId = null;
|
||||||
|
_token = null;
|
||||||
|
_provider = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/action.dart';
|
import 'package:stream_chat/src/core/models/action.dart';
|
||||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
||||||
import 'package:stream_chat/src/models/serialization.dart';
|
import 'package:stream_chat/src/core/models/serialization.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
part 'attachment.g.dart';
|
part 'attachment.g.dart';
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/command.dart';
|
import 'package:stream_chat/src/core/models/command.dart';
|
||||||
|
|
||||||
part 'channel_config.g.dart';
|
part 'channel_config.g.dart';
|
||||||
|
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/channel_config.dart';
|
import 'package:stream_chat/src/core/models/channel_config.dart';
|
||||||
import 'package:stream_chat/src/models/serialization.dart';
|
import 'package:stream_chat/src/core/models/serialization.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
part 'channel_model.g.dart';
|
part 'channel_model.g.dart';
|
||||||
|
|
||||||
+5
-5
@@ -1,9 +1,9 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/channel_model.dart';
|
import 'package:stream_chat/src/core/models/channel_model.dart';
|
||||||
import 'package:stream_chat/src/models/member.dart';
|
import 'package:stream_chat/src/core/models/member.dart';
|
||||||
import 'package:stream_chat/src/models/message.dart';
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
import 'package:stream_chat/src/models/read.dart';
|
import 'package:stream_chat/src/core/models/read.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
part 'channel_state.g.dart';
|
part 'channel_state.g.dart';
|
||||||
|
|
||||||
+9
-9
@@ -1,7 +1,7 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/channel_model.dart';
|
import 'package:stream_chat/src/core/models/channel_model.dart';
|
||||||
import 'package:stream_chat/src/models/message.dart';
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
import 'package:stream_chat/src/models/serialization.dart';
|
import 'package:stream_chat/src/core/models/serialization.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
part 'event.g.dart';
|
part 'event.g.dart';
|
||||||
@@ -10,11 +10,11 @@ part 'event.g.dart';
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Event {
|
class Event {
|
||||||
/// Constructor used for json serialization
|
/// Constructor used for json serialization
|
||||||
const Event({
|
Event({
|
||||||
this.type,
|
this.type = 'local.event',
|
||||||
this.cid,
|
this.cid,
|
||||||
this.connectionId,
|
this.connectionId,
|
||||||
this.createdAt,
|
DateTime? createdAt,
|
||||||
this.me,
|
this.me,
|
||||||
this.user,
|
this.user,
|
||||||
this.message,
|
this.message,
|
||||||
@@ -29,7 +29,7 @@ class Event {
|
|||||||
this.parentId,
|
this.parentId,
|
||||||
this.extraData = const {},
|
this.extraData = const {},
|
||||||
this.isLocal = true,
|
this.isLocal = true,
|
||||||
});
|
}) : createdAt = createdAt ?? DateTime.now();
|
||||||
|
|
||||||
/// Create a new instance from a json
|
/// Create a new instance from a json
|
||||||
factory Event.fromJson(Map<String, dynamic> json) =>
|
factory Event.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -40,7 +40,7 @@ class Event {
|
|||||||
|
|
||||||
/// The type of the event
|
/// The type of the event
|
||||||
/// [EventType] contains some predefined constant types
|
/// [EventType] contains some predefined constant types
|
||||||
final String? type;
|
final String type;
|
||||||
|
|
||||||
/// The channel cid to which the event belongs
|
/// The channel cid to which the event belongs
|
||||||
final String? cid;
|
final String? cid;
|
||||||
@@ -55,7 +55,7 @@ class Event {
|
|||||||
final String? connectionId;
|
final String? connectionId;
|
||||||
|
|
||||||
/// The date of creation of the event
|
/// The date of creation of the event
|
||||||
final DateTime? createdAt;
|
final DateTime createdAt;
|
||||||
|
|
||||||
/// User object of the health check user
|
/// User object of the health check user
|
||||||
final OwnUser? me;
|
final OwnUser? me;
|
||||||
+2
-2
@@ -8,7 +8,7 @@ part of 'event.dart';
|
|||||||
|
|
||||||
Event _$EventFromJson(Map<String, dynamic> json) {
|
Event _$EventFromJson(Map<String, dynamic> json) {
|
||||||
return Event(
|
return Event(
|
||||||
type: json['type'] as String?,
|
type: json['type'] as String,
|
||||||
cid: json['cid'] as String?,
|
cid: json['cid'] as String?,
|
||||||
connectionId: json['connection_id'] as String?,
|
connectionId: json['connection_id'] as String?,
|
||||||
createdAt: json['created_at'] == null
|
createdAt: json['created_at'] == null
|
||||||
@@ -49,7 +49,7 @@ Map<String, dynamic> _$EventToJson(Event instance) => <String, dynamic>{
|
|||||||
'channel_id': instance.channelId,
|
'channel_id': instance.channelId,
|
||||||
'channel_type': instance.channelType,
|
'channel_type': instance.channelType,
|
||||||
'connection_id': instance.connectionId,
|
'connection_id': instance.connectionId,
|
||||||
'created_at': instance.createdAt?.toIso8601String(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
'me': instance.me?.toJson(),
|
'me': instance.me?.toJson(),
|
||||||
'user': instance.user?.toJson(),
|
'user': instance.user?.toJson(),
|
||||||
'message': instance.message?.toJson(),
|
'message': instance.message?.toJson(),
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
part 'member.g.dart';
|
part 'member.g.dart';
|
||||||
|
|
||||||
+4
-4
@@ -1,9 +1,9 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/attachment.dart';
|
import 'package:stream_chat/src/core/models/attachment.dart';
|
||||||
import 'package:stream_chat/src/models/reaction.dart';
|
import 'package:stream_chat/src/core/models/reaction.dart';
|
||||||
import 'package:stream_chat/src/models/serialization.dart';
|
import 'package:stream_chat/src/core/models/serialization.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
part 'message.g.dart';
|
part 'message.g.dart';
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/channel_model.dart';
|
import 'package:stream_chat/src/core/models/channel_model.dart';
|
||||||
import 'package:stream_chat/src/models/serialization.dart';
|
import 'package:stream_chat/src/core/models/serialization.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
part 'mute.g.dart';
|
part 'mute.g.dart';
|
||||||
|
|
||||||
+16
-4
@@ -1,8 +1,8 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/device.dart';
|
import 'package:stream_chat/src/core/models/device.dart';
|
||||||
import 'package:stream_chat/src/models/mute.dart';
|
import 'package:stream_chat/src/core/models/mute.dart';
|
||||||
import 'package:stream_chat/src/models/serialization.dart';
|
import 'package:stream_chat/src/core/models/serialization.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
part 'own_user.g.dart';
|
part 'own_user.g.dart';
|
||||||
|
|
||||||
@@ -40,6 +40,18 @@ class OwnUser extends User {
|
|||||||
factory OwnUser.fromJson(Map<String, dynamic> json) => _$OwnUserFromJson(
|
factory OwnUser.fromJson(Map<String, dynamic> json) => _$OwnUserFromJson(
|
||||||
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
|
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
|
||||||
|
|
||||||
|
/// Create a new instance from [User] object
|
||||||
|
factory OwnUser.fromUser(User user) => OwnUser(
|
||||||
|
id: user.id,
|
||||||
|
role: user.role,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
lastActive: user.lastActive,
|
||||||
|
online: user.online,
|
||||||
|
banned: user.banned,
|
||||||
|
extraData: user.extraData,
|
||||||
|
);
|
||||||
|
|
||||||
/// List of user devices
|
/// List of user devices
|
||||||
@JsonKey(
|
@JsonKey(
|
||||||
includeIfNull: false,
|
includeIfNull: false,
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/serialization.dart';
|
import 'package:stream_chat/src/core/models/serialization.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
part 'reaction.g.dart';
|
part 'reaction.g.dart';
|
||||||
|
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
part 'read.g.dart';
|
part 'read.g.dart';
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
|
||||||
/// Used to avoid to serialize properties to json
|
/// Used to avoid to serialize properties to json
|
||||||
// ignore: prefer_void_to_null
|
// ignore: prefer_void_to_null
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/models/serialization.dart';
|
import 'package:stream_chat/src/core/models/serialization.dart';
|
||||||
|
|
||||||
part 'user.g.dart';
|
part 'user.g.dart';
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
// This alphabet uses `A-Za-z0-9_-` symbols. The genetic algorithm helped
|
||||||
|
// optimize the gzip compression for this alphabet.
|
||||||
|
const _alphabet =
|
||||||
|
'ModuleSymbhasOwnPr0123456789ABCDEFGHNRVfgctiUvzKqYTJkLxpZXIjQW';
|
||||||
|
|
||||||
|
/// Generates a random String id
|
||||||
|
/// Adopted from: https://github.com/ai/nanoid/blob/main/non-secure/index.js
|
||||||
|
String randomId({int size = 21}) {
|
||||||
|
var id = '';
|
||||||
|
for (var i = 0; i < size; i++) {
|
||||||
|
id += _alphabet[(math.Random().nextInt(32) * 64) | 0];
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import 'package:stream_chat/src/api/requests.dart';
|
import 'package:stream_chat/src/core/api/requests.dart';
|
||||||
import 'package:stream_chat/src/models/channel_model.dart';
|
import 'package:stream_chat/src/core/models/channel_model.dart';
|
||||||
import 'package:stream_chat/src/models/channel_state.dart';
|
import 'package:stream_chat/src/core/models/channel_state.dart';
|
||||||
import 'package:stream_chat/src/models/event.dart';
|
import 'package:stream_chat/src/core/models/event.dart';
|
||||||
import 'package:stream_chat/src/models/filter.dart';
|
import 'package:stream_chat/src/core/models/filter.dart';
|
||||||
import 'package:stream_chat/src/models/member.dart';
|
import 'package:stream_chat/src/core/models/member.dart';
|
||||||
import 'package:stream_chat/src/models/message.dart';
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
import 'package:stream_chat/src/models/reaction.dart';
|
import 'package:stream_chat/src/core/models/reaction.dart';
|
||||||
import 'package:stream_chat/src/models/read.dart';
|
import 'package:stream_chat/src/core/models/read.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
import 'package:stream_chat/src/extensions/iterable_extension.dart';
|
import 'package:stream_chat/src/extensions/iterable_extension.dart';
|
||||||
|
|
||||||
/// A simple client used for persisting chat data locally.
|
/// A simple client used for persisting chat data locally.
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
///
|
||||||
|
enum ChatErrorCode {
|
||||||
|
// client error codes
|
||||||
|
networkFailed,
|
||||||
|
parserError,
|
||||||
|
socketClosed,
|
||||||
|
socketFailure,
|
||||||
|
cantParseConnectionEvent,
|
||||||
|
cantParseEvent,
|
||||||
|
invalidToken,
|
||||||
|
undefinedToken,
|
||||||
|
unableToParseSocketEvent,
|
||||||
|
noErrorBody,
|
||||||
|
|
||||||
|
// server error codes
|
||||||
|
authenticationError,
|
||||||
|
tokenExpired,
|
||||||
|
tokenNotValid,
|
||||||
|
tokenDateIncorrect,
|
||||||
|
tokenSignatureIncorrect,
|
||||||
|
apiKeyNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
extension ChatErrorCodeX on ChatErrorCode {
|
||||||
|
///
|
||||||
|
String get message => {
|
||||||
|
// client error message
|
||||||
|
ChatErrorCode.networkFailed: 'Response is failed. See cause',
|
||||||
|
ChatErrorCode.parserError: 'Unable to parse error',
|
||||||
|
ChatErrorCode.socketClosed: 'Server closed connection',
|
||||||
|
ChatErrorCode.socketFailure: 'See stack trace in logs. '
|
||||||
|
'Intercept error in error handler of setUser',
|
||||||
|
ChatErrorCode.cantParseConnectionEvent:
|
||||||
|
'Unable to parse connection event',
|
||||||
|
ChatErrorCode.cantParseEvent: 'Unable to parse event',
|
||||||
|
ChatErrorCode.invalidToken: 'Invalid token',
|
||||||
|
ChatErrorCode.undefinedToken:
|
||||||
|
'No defined token. Check if client.setUser was called and finished',
|
||||||
|
ChatErrorCode.unableToParseSocketEvent:
|
||||||
|
'Socket event payload either invalid or null',
|
||||||
|
ChatErrorCode.noErrorBody: 'No error body. See http status code',
|
||||||
|
|
||||||
|
// server error message
|
||||||
|
ChatErrorCode.authenticationError:
|
||||||
|
'Unauthenticated, problem with authentication',
|
||||||
|
ChatErrorCode.tokenExpired: 'Token expired, new one must be requested.',
|
||||||
|
ChatErrorCode.tokenNotValid: 'Unauthenticated, token not valid yet',
|
||||||
|
ChatErrorCode.tokenDateIncorrect:
|
||||||
|
'Unauthenticated, token date incorrect',
|
||||||
|
ChatErrorCode.tokenSignatureIncorrect:
|
||||||
|
'Unauthenticated, token signature invalid',
|
||||||
|
ChatErrorCode.apiKeyNotFound:
|
||||||
|
"Api key is not found, verify it if it's correct or was created.",
|
||||||
|
}[this]!;
|
||||||
|
|
||||||
|
///
|
||||||
|
int get code => {
|
||||||
|
// client error codes
|
||||||
|
ChatErrorCode.networkFailed: 1000,
|
||||||
|
ChatErrorCode.parserError: 1001,
|
||||||
|
ChatErrorCode.socketClosed: 1002,
|
||||||
|
ChatErrorCode.socketFailure: 1003,
|
||||||
|
ChatErrorCode.cantParseConnectionEvent: 1004,
|
||||||
|
ChatErrorCode.cantParseEvent: 1005,
|
||||||
|
ChatErrorCode.invalidToken: 1006,
|
||||||
|
ChatErrorCode.undefinedToken: 1007,
|
||||||
|
ChatErrorCode.unableToParseSocketEvent: 1008,
|
||||||
|
ChatErrorCode.noErrorBody: 1009,
|
||||||
|
|
||||||
|
// server error codes
|
||||||
|
ChatErrorCode.authenticationError: 5,
|
||||||
|
ChatErrorCode.tokenExpired: 40,
|
||||||
|
ChatErrorCode.tokenNotValid: 41,
|
||||||
|
ChatErrorCode.tokenDateIncorrect: 42,
|
||||||
|
ChatErrorCode.tokenSignatureIncorrect: 43,
|
||||||
|
ChatErrorCode.apiKeyNotFound: 2,
|
||||||
|
}[this]!;
|
||||||
|
|
||||||
|
///
|
||||||
|
Set<int> get authenticationErrors => {
|
||||||
|
ChatErrorCode.authenticationError,
|
||||||
|
ChatErrorCode.tokenExpired,
|
||||||
|
ChatErrorCode.tokenNotValid,
|
||||||
|
ChatErrorCode.tokenDateIncorrect,
|
||||||
|
ChatErrorCode.tokenSignatureIncorrect,
|
||||||
|
}.map((it) => it.code).toSet();
|
||||||
|
|
||||||
|
///
|
||||||
|
bool isAuthenticationError(int code) => authenticationErrors.contains(code);
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:stream_chat/src/errors/chat_error_code.dart';
|
||||||
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class StreamChatError extends Equatable implements Exception {
|
||||||
|
///
|
||||||
|
StreamChatError(ChatErrorCode errorCode)
|
||||||
|
: code = errorCode.code,
|
||||||
|
message = errorCode.message;
|
||||||
|
|
||||||
|
///
|
||||||
|
const StreamChatError.raw(this.code, this.message);
|
||||||
|
|
||||||
|
/// Error code
|
||||||
|
final int code;
|
||||||
|
|
||||||
|
/// Error message
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [code, message];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'StreamChatError(code: $code, message: $message)';
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
class StreamChatNetworkError extends StreamChatError {
|
||||||
|
///
|
||||||
|
StreamChatNetworkError({
|
||||||
|
required ChatErrorCode errorCode,
|
||||||
|
int? statusCode,
|
||||||
|
this.data,
|
||||||
|
}) : statusCode = statusCode ?? data?.statusCode,
|
||||||
|
super(errorCode);
|
||||||
|
|
||||||
|
///
|
||||||
|
const StreamChatNetworkError.raw({
|
||||||
|
required int code,
|
||||||
|
required String message,
|
||||||
|
this.statusCode,
|
||||||
|
this.data,
|
||||||
|
}) : super.raw(code, message);
|
||||||
|
|
||||||
|
///
|
||||||
|
factory StreamChatNetworkError.fromDioError(DioError error) {
|
||||||
|
final response = error.response;
|
||||||
|
ErrorResponse? errorResponse;
|
||||||
|
final data = response?.data;
|
||||||
|
if (data != null) {
|
||||||
|
errorResponse = ErrorResponse.fromJson(data);
|
||||||
|
}
|
||||||
|
return StreamChatNetworkError.raw(
|
||||||
|
code: errorResponse?.code ?? -1,
|
||||||
|
message: errorResponse?.message ?? response?.statusMessage ?? '',
|
||||||
|
statusCode: errorResponse?.statusCode ?? response?.statusCode,
|
||||||
|
data: errorResponse,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// HTTP status code
|
||||||
|
final int? statusCode;
|
||||||
|
|
||||||
|
/// Response body. please refer to [ErrorResponse].
|
||||||
|
final ErrorResponse? data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [...super.props, statusCode];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'StreamChatNetworkError('
|
||||||
|
'code: $code, '
|
||||||
|
'message: $message, '
|
||||||
|
'statusCode: $statusCode, '
|
||||||
|
'data: $data)';
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ class EventType {
|
|||||||
/// Indicates any type of events
|
/// Indicates any type of events
|
||||||
static const String any = '*';
|
static const String any = '*';
|
||||||
|
|
||||||
|
///
|
||||||
|
static const String healthCheck = 'health.check';
|
||||||
|
|
||||||
/// Event sent when a user starts typing a message
|
/// Event sent when a user starts typing a message
|
||||||
static const String typingStart = 'typing.start';
|
static const String typingStart = 'typing.start';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
///
|
||||||
|
class SocketError {
|
||||||
|
///
|
||||||
|
const SocketError({
|
||||||
|
this.code = -1,
|
||||||
|
this.statusCode = -1,
|
||||||
|
this.message = '',
|
||||||
|
});
|
||||||
|
|
||||||
|
///
|
||||||
|
factory SocketError.fromJson(Map<String, dynamic> json) => SocketError(
|
||||||
|
code: json['code'],
|
||||||
|
statusCode: json['StatusCode'],
|
||||||
|
message: json['message'],
|
||||||
|
);
|
||||||
|
|
||||||
|
///
|
||||||
|
final int code;
|
||||||
|
|
||||||
|
///
|
||||||
|
final int statusCode;
|
||||||
|
|
||||||
|
///
|
||||||
|
final String message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class TimerHelper {
|
||||||
|
final _uuid = const Uuid();
|
||||||
|
late final _timers = <String, Timer>{};
|
||||||
|
|
||||||
|
///
|
||||||
|
String setTimer(
|
||||||
|
Duration duration,
|
||||||
|
void Function() callback, {
|
||||||
|
bool immediate = false,
|
||||||
|
}) {
|
||||||
|
final id = _uuid.v1();
|
||||||
|
final timer = Timer(duration, callback);
|
||||||
|
if (immediate) callback();
|
||||||
|
_timers[id] = timer;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
String setPeriodicTimer(
|
||||||
|
Duration duration,
|
||||||
|
void Function(Timer) callback, {
|
||||||
|
bool immediate = false,
|
||||||
|
}) {
|
||||||
|
final id = _uuid.v1();
|
||||||
|
final timer = Timer.periodic(duration, callback);
|
||||||
|
if (immediate) callback.call(timer);
|
||||||
|
_timers[id] = timer;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
void cancelTimer(String id) {
|
||||||
|
final timer = _timers.remove(id);
|
||||||
|
return timer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
void cancelAllTimers() {
|
||||||
|
for (final t in _timers.values) {
|
||||||
|
t.cancel();
|
||||||
|
}
|
||||||
|
_timers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
bool get hasTimers => _timers.isNotEmpty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
import 'package:stream_chat/src/ws/connection_status.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/token.dart';
|
||||||
|
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/event.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
import 'package:stream_chat/src/event_type.dart';
|
||||||
|
import 'package:stream_chat/src/ws/socket_error.dart';
|
||||||
|
import 'package:stream_chat/src/ws/timer_helper.dart';
|
||||||
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
import 'package:web_socket_channel/status.dart' as status;
|
||||||
|
|
||||||
|
/// Typedef which exposes an [Event] as the only parameter.
|
||||||
|
typedef EventHandler = void Function(Event);
|
||||||
|
|
||||||
|
/// Typedef used for connecting to a websocket. Method returns a
|
||||||
|
/// [WebSocketChannel] and accepts a connection [url] and an optional
|
||||||
|
/// [Iterable] of `protocols`.
|
||||||
|
typedef WebSocketChannelProvider = WebSocketChannel Function(
|
||||||
|
Uri uri, {
|
||||||
|
Iterable<String>? protocols,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _tokenExpiredErrorCode = 40;
|
||||||
|
|
||||||
|
/// A WebSocket connection that reconnects upon failure.
|
||||||
|
class WebSocket with TimerHelper {
|
||||||
|
/// Creates a new websocket
|
||||||
|
/// To connect the WS call [connect]
|
||||||
|
WebSocket({
|
||||||
|
required this.apiKey,
|
||||||
|
required this.baseUrl,
|
||||||
|
required this.tokenManager,
|
||||||
|
this.handler,
|
||||||
|
Logger? logger,
|
||||||
|
this.webSocketChannelProvider,
|
||||||
|
this.reconnectionMonitorInterval = 10,
|
||||||
|
this.healthCheckInterval = 20,
|
||||||
|
this.reconnectionMonitorTimeout = 40,
|
||||||
|
}) : _logger = logger;
|
||||||
|
|
||||||
|
///
|
||||||
|
final String apiKey;
|
||||||
|
|
||||||
|
/// WS base url
|
||||||
|
final String baseUrl;
|
||||||
|
|
||||||
|
///
|
||||||
|
final TokenManager tokenManager;
|
||||||
|
|
||||||
|
/// Functions that will be called every time a new event is received from the
|
||||||
|
/// connection
|
||||||
|
final EventHandler? handler;
|
||||||
|
|
||||||
|
final Logger? _logger;
|
||||||
|
|
||||||
|
/// Connection function
|
||||||
|
/// Used only for testing purpose
|
||||||
|
@visibleForTesting
|
||||||
|
final WebSocketChannelProvider? webSocketChannelProvider;
|
||||||
|
|
||||||
|
/// Interval of the reconnection monitor timer
|
||||||
|
/// This checks that it received a new event in the last
|
||||||
|
/// [reconnectionMonitorTimeout] seconds, otherwise it considers the
|
||||||
|
/// connection unhealthy and reconnects the WS
|
||||||
|
final int reconnectionMonitorInterval;
|
||||||
|
|
||||||
|
/// Interval of the health event sending timer
|
||||||
|
/// This sends a health event every [healthCheckInterval] seconds in order to
|
||||||
|
/// make the server aware that the client is still listening
|
||||||
|
final int healthCheckInterval;
|
||||||
|
|
||||||
|
/// The timeout that uses the reconnection monitor timer to consider the
|
||||||
|
/// connection unhealthy
|
||||||
|
final int reconnectionMonitorTimeout;
|
||||||
|
|
||||||
|
User? _user;
|
||||||
|
String? _connectionId;
|
||||||
|
DateTime? _lastEventAt;
|
||||||
|
WebSocketChannel? _webSocketChannel;
|
||||||
|
StreamSubscription? _webSocketChannelSubscription;
|
||||||
|
|
||||||
|
///
|
||||||
|
Completer<Event>? connectionCompleter;
|
||||||
|
|
||||||
|
///
|
||||||
|
String? get connectionId => _connectionId;
|
||||||
|
|
||||||
|
final _connectionStatusController =
|
||||||
|
BehaviorSubject.seeded(ConnectionStatus.disconnected);
|
||||||
|
|
||||||
|
set _connectionStatus(ConnectionStatus status) =>
|
||||||
|
_connectionStatusController.add(status);
|
||||||
|
|
||||||
|
/// The current connection status value
|
||||||
|
ConnectionStatus get connectionStatus => _connectionStatusController.value!;
|
||||||
|
|
||||||
|
/// This notifies of connection status changes
|
||||||
|
Stream<ConnectionStatus> get connectionStatusStream =>
|
||||||
|
_connectionStatusController.stream.distinct();
|
||||||
|
|
||||||
|
void _initWebSocketChannel(Uri uri) {
|
||||||
|
_logger?.info('Initiating connection with $baseUrl');
|
||||||
|
if (_webSocketChannel != null) {
|
||||||
|
_closeWebSocketChannel();
|
||||||
|
}
|
||||||
|
_webSocketChannel =
|
||||||
|
webSocketChannelProvider?.call(uri) ?? WebSocketChannel.connect(uri);
|
||||||
|
_subscribeToWebSocketChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _closeWebSocketChannel() {
|
||||||
|
_logger?.info('Closing connection with $baseUrl');
|
||||||
|
if (_webSocketChannel != null) {
|
||||||
|
_unsubscribeFromWebSocketChannel();
|
||||||
|
_webSocketChannel?.sink.close(status.goingAway);
|
||||||
|
_webSocketChannel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _subscribeToWebSocketChannel() {
|
||||||
|
_logger?.info('Started listening to $baseUrl');
|
||||||
|
if (_webSocketChannelSubscription != null) {
|
||||||
|
_unsubscribeFromWebSocketChannel();
|
||||||
|
}
|
||||||
|
_webSocketChannelSubscription = _webSocketChannel?.stream.listen(
|
||||||
|
_onDataReceived,
|
||||||
|
onError: _onConnectionError,
|
||||||
|
onDone: _onConnectionClosed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _unsubscribeFromWebSocketChannel() {
|
||||||
|
_logger?.info('Stopped listening to $baseUrl');
|
||||||
|
if (_webSocketChannelSubscription != null) {
|
||||||
|
_webSocketChannelSubscription?.cancel();
|
||||||
|
_webSocketChannelSubscription = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Uri> _buildUri({bool refreshToken = false}) async {
|
||||||
|
final user = _user!;
|
||||||
|
final token = await tokenManager.loadToken(refresh: refreshToken);
|
||||||
|
final params = {
|
||||||
|
'user_id': user.id,
|
||||||
|
'user_details': user,
|
||||||
|
'user_token': token.rawValue,
|
||||||
|
'server_determines_connection_id': true,
|
||||||
|
};
|
||||||
|
final qs = {
|
||||||
|
'json': jsonEncode(params),
|
||||||
|
'api_key': apiKey,
|
||||||
|
'authorization': token.rawValue,
|
||||||
|
'stream-auth-type': token.authType.raw,
|
||||||
|
};
|
||||||
|
final scheme = baseUrl.startsWith('https') ? 'wss' : 'ws';
|
||||||
|
final host = baseUrl.replaceAll(RegExp(r'(^\w+:|^)\/\/'), '');
|
||||||
|
return Uri(
|
||||||
|
scheme: scheme,
|
||||||
|
host: host,
|
||||||
|
pathSegments: ['connect'],
|
||||||
|
queryParameters: qs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _connectRequestInProgress = false;
|
||||||
|
|
||||||
|
/// Connect the WS using the parameters passed in the constructor
|
||||||
|
Future<Event> connect(User user) async {
|
||||||
|
if (_connectRequestInProgress) {
|
||||||
|
throw Exception('''
|
||||||
|
You've called connect twice,
|
||||||
|
can only attempt 1 connection at the time,
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
_connectRequestInProgress = true;
|
||||||
|
_manuallyClosed = false;
|
||||||
|
|
||||||
|
_user = user;
|
||||||
|
_connectionStatus = ConnectionStatus.connecting;
|
||||||
|
connectionCompleter = Completer<Event>();
|
||||||
|
|
||||||
|
final uri = await _buildUri();
|
||||||
|
_initWebSocketChannel(uri);
|
||||||
|
|
||||||
|
return connectionCompleter!.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _reconnectAttempt = 0;
|
||||||
|
bool _reconnectRequestInProgress = false;
|
||||||
|
|
||||||
|
Future<void> _reconnect({bool refreshToken = false}) async {
|
||||||
|
_logger?.info('Retrying connection : $_reconnectAttempt');
|
||||||
|
if (_reconnectRequestInProgress) return;
|
||||||
|
_reconnectRequestInProgress = true;
|
||||||
|
|
||||||
|
_stopMonitoringEvents();
|
||||||
|
// Closing any previously opened web-socket
|
||||||
|
_closeWebSocketChannel();
|
||||||
|
|
||||||
|
_reconnectAttempt += 1;
|
||||||
|
_connectionStatus = ConnectionStatus.connecting;
|
||||||
|
|
||||||
|
final delay = _getReconnectInterval(_reconnectAttempt);
|
||||||
|
setTimer(
|
||||||
|
Duration(milliseconds: delay),
|
||||||
|
() async {
|
||||||
|
final uri = await _buildUri(refreshToken: refreshToken);
|
||||||
|
_initWebSocketChannel(uri);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the reconnect interval based on `reconnectAttempt` in milliseconds
|
||||||
|
int _getReconnectInterval(int reconnectAttempt) {
|
||||||
|
// try to reconnect in 0.25-25 seconds
|
||||||
|
// (random to spread out the load from failures)
|
||||||
|
final max = math.min(500 + reconnectAttempt * 2000, 25000);
|
||||||
|
final min = math.min(
|
||||||
|
math.max(250, (reconnectAttempt - 1) * 2000),
|
||||||
|
25000,
|
||||||
|
);
|
||||||
|
return (math.Random().nextDouble() * (max - min) + min).floor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startMonitoringEvents() {
|
||||||
|
_logger?.info('Starting monitoring events');
|
||||||
|
// cancel all previous timers
|
||||||
|
cancelAllTimers();
|
||||||
|
|
||||||
|
_startHealthCheck();
|
||||||
|
_startReconnectionMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopMonitoringEvents() {
|
||||||
|
_logger?.info('Stopped monitoring events');
|
||||||
|
// reset lastEvent
|
||||||
|
_lastEventAt = null;
|
||||||
|
|
||||||
|
cancelAllTimers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleConnectedEvent(Event event) {
|
||||||
|
// updating connectionId and status
|
||||||
|
_connectionId = event.connectionId;
|
||||||
|
_connectionStatus = ConnectionStatus.connected;
|
||||||
|
|
||||||
|
_logger?.info('Connection successful: $_connectionId');
|
||||||
|
|
||||||
|
// notify user that connection is completed
|
||||||
|
final completer = connectionCompleter;
|
||||||
|
if (completer != null && !completer.isCompleted) {
|
||||||
|
completer.complete(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// start monitoring health-check events
|
||||||
|
_startMonitoringEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleHealthCheckEvent(Event event) {
|
||||||
|
_logger?.info('HealthCheck received : ${event.connectionId}');
|
||||||
|
|
||||||
|
_connectionId = event.connectionId;
|
||||||
|
_connectionStatus = ConnectionStatus.connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onDataReceived(dynamic data) {
|
||||||
|
final jsonData = json.decode(data);
|
||||||
|
final error = jsonData['error'];
|
||||||
|
if (error != null) return _onConnectionError(error);
|
||||||
|
|
||||||
|
// resetting connect, reconnect request flag
|
||||||
|
_resetRequestFlags(resetAttempts: true);
|
||||||
|
|
||||||
|
Event? event;
|
||||||
|
try {
|
||||||
|
event = Event.fromJson(jsonData);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (event == null) return;
|
||||||
|
|
||||||
|
_lastEventAt = DateTime.now();
|
||||||
|
_logger?.info('Event received: ${event.type}');
|
||||||
|
|
||||||
|
if (event.type == EventType.healthCheck) {
|
||||||
|
if (event.me != null) {
|
||||||
|
_handleConnectedEvent(event);
|
||||||
|
} else {
|
||||||
|
_handleHealthCheckEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler?.call(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onConnectionError(error, [stacktrace]) {
|
||||||
|
_logger?.severe('Error occurred', error, stacktrace);
|
||||||
|
|
||||||
|
// resetting connect, reconnect request flag
|
||||||
|
_resetRequestFlags();
|
||||||
|
|
||||||
|
var refreshToken = false;
|
||||||
|
try {
|
||||||
|
final socketError = SocketError.fromJson(json.decode(error));
|
||||||
|
refreshToken = socketError.code == _tokenExpiredErrorCode;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
// refresh token in case it is expired
|
||||||
|
_reconnect(refreshToken: refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _manuallyClosed = false;
|
||||||
|
|
||||||
|
void _onConnectionClosed() {
|
||||||
|
_logger?.info('Connection closed : $connectionId');
|
||||||
|
|
||||||
|
// resetting connect, reconnect request flag
|
||||||
|
_resetRequestFlags();
|
||||||
|
|
||||||
|
// resetting connection
|
||||||
|
_connectionId = null;
|
||||||
|
|
||||||
|
// check if we manually closed the connection
|
||||||
|
if (_manuallyClosed) return;
|
||||||
|
_reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _needsToReconnect {
|
||||||
|
final lastEventAt = _lastEventAt;
|
||||||
|
// means not yet connected or disconnected
|
||||||
|
if (lastEventAt == null) return false;
|
||||||
|
|
||||||
|
// means we missed a health check
|
||||||
|
final now = DateTime.now();
|
||||||
|
return now.difference(lastEventAt).inSeconds > reconnectionMonitorTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetRequestFlags({bool resetAttempts = false}) {
|
||||||
|
_connectRequestInProgress = false;
|
||||||
|
_reconnectRequestInProgress = false;
|
||||||
|
if (resetAttempts) _reconnectAttempt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startReconnectionMonitor() {
|
||||||
|
_logger?.info('Starting reconnection monitor');
|
||||||
|
setPeriodicTimer(
|
||||||
|
Duration(seconds: reconnectionMonitorInterval),
|
||||||
|
(_) {
|
||||||
|
final needsToReconnect = _needsToReconnect;
|
||||||
|
_logger?.info('Needs to reconnect : $needsToReconnect');
|
||||||
|
if (needsToReconnect) _reconnect();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startHealthCheck() {
|
||||||
|
_logger?.info('Starting health check monitor');
|
||||||
|
setPeriodicTimer(
|
||||||
|
Duration(seconds: healthCheckInterval),
|
||||||
|
(_) {
|
||||||
|
_logger?.info('Sending Event: ${EventType.healthCheck}');
|
||||||
|
final event = Event(
|
||||||
|
type: EventType.healthCheck,
|
||||||
|
connectionId: connectionId,
|
||||||
|
);
|
||||||
|
_webSocketChannel?.sink.add(jsonEncode(event));
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disconnects the WS and releases eventual resources
|
||||||
|
void disconnect() {
|
||||||
|
if (connectionStatus == ConnectionStatus.disconnected) return;
|
||||||
|
_connectionStatus = ConnectionStatus.disconnected;
|
||||||
|
|
||||||
|
_logger?.info('Disconnecting $connectionId');
|
||||||
|
|
||||||
|
// resetting user
|
||||||
|
_user = null;
|
||||||
|
connectionCompleter = null;
|
||||||
|
|
||||||
|
_stopMonitoringEvents();
|
||||||
|
|
||||||
|
_manuallyClosed = true;
|
||||||
|
_closeWebSocketChannel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,31 +8,32 @@ export 'package:dio/src/options.dart' show ProgressCallback;
|
|||||||
export 'package:logging/logging.dart' show Logger, Level;
|
export 'package:logging/logging.dart' show Logger, Level;
|
||||||
|
|
||||||
export './src/api/channel.dart';
|
export './src/api/channel.dart';
|
||||||
export './src/api/connection_status.dart';
|
|
||||||
export './src/api/requests.dart';
|
|
||||||
export './src/api/requests.dart';
|
|
||||||
export './src/api/responses.dart';
|
|
||||||
export './src/attachment_file_uploader.dart' show AttachmentFileUploader;
|
|
||||||
export './src/client.dart';
|
export './src/client.dart';
|
||||||
|
export './src/core/api/attachment_file_uploader.dart'
|
||||||
|
show AttachmentFileUploader;
|
||||||
|
export './src/core/models/action.dart';
|
||||||
|
export './src/core/models/attachment.dart';
|
||||||
|
export './src/core/models/attachment_file.dart';
|
||||||
|
export './src/core/models/channel_config.dart';
|
||||||
|
export './src/core/models/channel_model.dart';
|
||||||
|
export './src/core/models/channel_state.dart';
|
||||||
|
export './src/core/models/command.dart';
|
||||||
|
export './src/core/models/device.dart';
|
||||||
|
export './src/core/models/event.dart';
|
||||||
|
export './src/core/models/filter.dart' show Filter;
|
||||||
|
export './src/core/models/member.dart';
|
||||||
|
export './src/core/models/message.dart';
|
||||||
|
export './src/core/models/mute.dart';
|
||||||
|
export './src/core/models/own_user.dart';
|
||||||
|
export './src/core/models/reaction.dart';
|
||||||
|
export './src/core/models/read.dart';
|
||||||
|
export './src/core/models/user.dart';
|
||||||
export './src/db/chat_persistence_client.dart';
|
export './src/db/chat_persistence_client.dart';
|
||||||
export './src/event_type.dart';
|
export './src/event_type.dart';
|
||||||
export './src/exceptions.dart';
|
export './src/exceptions.dart';
|
||||||
export './src/extensions/rate_limit.dart';
|
export './src/extensions/rate_limit.dart';
|
||||||
export './src/extensions/string_extension.dart';
|
export './src/extensions/string_extension.dart';
|
||||||
export './src/models/action.dart';
|
export 'src/core/api/requests.dart';
|
||||||
export './src/models/attachment.dart';
|
export 'src/core/api/requests.dart';
|
||||||
export './src/models/attachment_file.dart';
|
export 'src/core/api/responses.dart';
|
||||||
export './src/models/channel_config.dart';
|
export 'src/ws/connection_status.dart';
|
||||||
export './src/models/channel_model.dart';
|
|
||||||
export './src/models/channel_state.dart';
|
|
||||||
export './src/models/command.dart';
|
|
||||||
export './src/models/device.dart';
|
|
||||||
export './src/models/event.dart';
|
|
||||||
export './src/models/filter.dart' show Filter;
|
|
||||||
export './src/models/member.dart';
|
|
||||||
export './src/models/message.dart';
|
|
||||||
export './src/models/mute.dart';
|
|
||||||
export './src/models/own_user.dart';
|
|
||||||
export './src/models/reaction.dart';
|
|
||||||
export './src/models/read.dart';
|
|
||||||
export './src/models/user.dart';
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ dependencies:
|
|||||||
equatable: ^2.0.0
|
equatable: ^2.0.0
|
||||||
freezed_annotation: ^0.14.0
|
freezed_annotation: ^0.14.0
|
||||||
http_parser: ^4.0.0
|
http_parser: ^4.0.0
|
||||||
|
jose: ^0.3.2
|
||||||
json_annotation: ^4.0.1
|
json_annotation: ^4.0.1
|
||||||
logging: ^1.0.1
|
logging: ^1.0.1
|
||||||
meta: ^1.3.0
|
meta: ^1.3.0
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import 'dart:convert';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio/native_imp.dart';
|
import 'package:dio/native_imp.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
import 'package:stream_chat/src/api/requests.dart';
|
import 'package:stream_chat/src/core/api/requests.dart';
|
||||||
import 'package:stream_chat/src/client.dart';
|
import 'package:stream_chat/src/client.dart';
|
||||||
import 'package:stream_chat/src/event_type.dart';
|
import 'package:stream_chat/src/event_type.dart';
|
||||||
import 'package:stream_chat/src/models/event.dart';
|
import 'package:stream_chat/src/core/models/event.dart';
|
||||||
import 'package:stream_chat/src/models/message.dart';
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
import 'package:stream_chat/src/models/own_user.dart';
|
import 'package:stream_chat/src/core/models/own_user.dart';
|
||||||
import 'package:stream_chat/src/models/reaction.dart';
|
import 'package:stream_chat/src/core/models/reaction.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:stream_chat/src/api/responses.dart';
|
import 'package:stream_chat/src/core/api/responses.dart';
|
||||||
import 'package:stream_chat/src/models/device.dart';
|
import 'package:stream_chat/src/core/models/device.dart';
|
||||||
import 'package:stream_chat/src/models/member.dart';
|
import 'package:stream_chat/src/core/models/member.dart';
|
||||||
import 'package:stream_chat/src/models/message.dart';
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
import 'package:stream_chat/src/models/reaction.dart';
|
import 'package:stream_chat/src/core/models/reaction.dart';
|
||||||
import 'package:stream_chat/src/models/read.dart';
|
import 'package:stream_chat/src/core/models/read.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:stream_chat/src/api/web_socket_channel_stub.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
test('src/api/web_socket_stub_test', () {
|
test('src/api/web_socket_stub_test', () {
|
||||||
expect(
|
expect(
|
||||||
() => connectWebSocket('fakeurl'),
|
() => openConnection('fakeurl'),
|
||||||
throwsA(isA<UnimplementedError>()),
|
throwsA(isA<UnimplementedError>()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
import 'package:stream_chat/src/api/connection_status.dart';
|
import 'package:stream_chat/src/ws/connection_status.dart';
|
||||||
import 'package:stream_chat/src/api/websocket.dart';
|
import 'package:stream_chat/src/ws/websocket.dart';
|
||||||
import 'package:stream_chat/src/models/event.dart';
|
import 'package:stream_chat/src/core/models/event.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
@@ -39,7 +39,7 @@ void main() {
|
|||||||
final ws = WebSocket(
|
final ws = WebSocket(
|
||||||
baseUrl: 'baseurl',
|
baseUrl: 'baseurl',
|
||||||
user: User(id: 'testid'),
|
user: User(id: 'testid'),
|
||||||
logger: Logger('ws'),
|
_logger: Logger('ws'),
|
||||||
connectParams: {'test': 'true'},
|
connectParams: {'test': 'true'},
|
||||||
connectPayload: {'payload': 'test'},
|
connectPayload: {'payload': 'test'},
|
||||||
handler: print,
|
handler: print,
|
||||||
@@ -77,7 +77,7 @@ void main() {
|
|||||||
final ws = WebSocket(
|
final ws = WebSocket(
|
||||||
baseUrl: 'baseurl',
|
baseUrl: 'baseurl',
|
||||||
user: User(id: 'testid'),
|
user: User(id: 'testid'),
|
||||||
logger: Logger('ws'),
|
_logger: Logger('ws'),
|
||||||
connectParams: {'test': 'true'},
|
connectParams: {'test': 'true'},
|
||||||
connectPayload: {'payload': 'test'},
|
connectPayload: {'payload': 'test'},
|
||||||
handler: handleFunc,
|
handler: handleFunc,
|
||||||
@@ -113,7 +113,7 @@ void main() {
|
|||||||
final ws = WebSocket(
|
final ws = WebSocket(
|
||||||
baseUrl: 'baseurl',
|
baseUrl: 'baseurl',
|
||||||
user: User(id: 'testid'),
|
user: User(id: 'testid'),
|
||||||
logger: Logger('ws'),
|
_logger: Logger('ws'),
|
||||||
connectParams: {'test': 'true'},
|
connectParams: {'test': 'true'},
|
||||||
connectPayload: {'payload': 'test'},
|
connectPayload: {'payload': 'test'},
|
||||||
handler: handleFunc,
|
handler: handleFunc,
|
||||||
@@ -153,7 +153,7 @@ void main() {
|
|||||||
final ws = WebSocket(
|
final ws = WebSocket(
|
||||||
baseUrl: 'baseurl',
|
baseUrl: 'baseurl',
|
||||||
user: User(id: 'testid'),
|
user: User(id: 'testid'),
|
||||||
logger: Logger('ws'),
|
_logger: Logger('ws'),
|
||||||
connectParams: {'test': 'true'},
|
connectParams: {'test': 'true'},
|
||||||
connectPayload: {'payload': 'test'},
|
connectPayload: {'payload': 'test'},
|
||||||
handler: handleFunc,
|
handler: handleFunc,
|
||||||
@@ -189,7 +189,7 @@ void main() {
|
|||||||
final ws = WebSocket(
|
final ws = WebSocket(
|
||||||
baseUrl: 'baseurl',
|
baseUrl: 'baseurl',
|
||||||
user: User(id: 'testid'),
|
user: User(id: 'testid'),
|
||||||
logger: Logger('ws'),
|
_logger: Logger('ws'),
|
||||||
connectParams: {'test': 'true'},
|
connectParams: {'test': 'true'},
|
||||||
connectPayload: {'payload': 'test'},
|
connectPayload: {'payload': 'test'},
|
||||||
handler: handleFunc,
|
handler: handleFunc,
|
||||||
@@ -235,7 +235,7 @@ void main() {
|
|||||||
final ws = WebSocket(
|
final ws = WebSocket(
|
||||||
baseUrl: 'baseurl',
|
baseUrl: 'baseurl',
|
||||||
user: User(id: 'testid'),
|
user: User(id: 'testid'),
|
||||||
logger: Logger('ws'),
|
_logger: Logger('ws'),
|
||||||
connectParams: {'test': 'true'},
|
connectParams: {'test': 'true'},
|
||||||
connectPayload: {'payload': 'test'},
|
connectPayload: {'payload': 'test'},
|
||||||
handler: handleFunc,
|
handler: handleFunc,
|
||||||
@@ -280,7 +280,7 @@ void main() {
|
|||||||
final ws = WebSocket(
|
final ws = WebSocket(
|
||||||
baseUrl: 'baseurl',
|
baseUrl: 'baseurl',
|
||||||
user: User(id: 'testid'),
|
user: User(id: 'testid'),
|
||||||
logger: Logger('ws'),
|
_logger: Logger('ws'),
|
||||||
connectParams: {'test': 'true'},
|
connectParams: {'test': 'true'},
|
||||||
connectPayload: {'payload': 'test'},
|
connectPayload: {'payload': 'test'},
|
||||||
handler: handleFunc,
|
handler: handleFunc,
|
||||||
@@ -318,7 +318,7 @@ void main() {
|
|||||||
final ws = WebSocket(
|
final ws = WebSocket(
|
||||||
baseUrl: 'baseurl',
|
baseUrl: 'baseurl',
|
||||||
user: User(id: 'testid'),
|
user: User(id: 'testid'),
|
||||||
logger: Logger('ws'),
|
_logger: Logger('ws'),
|
||||||
connectParams: {'test': 'true'},
|
connectParams: {'test': 'true'},
|
||||||
connectPayload: {'payload': 'test'},
|
connectPayload: {'payload': 'test'},
|
||||||
handler: print,
|
handler: print,
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:dio/native_imp.dart';
|
import 'package:dio/native_imp.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
import 'package:stream_chat/src/api/requests.dart';
|
import 'package:stream_chat/src/core/api/requests.dart';
|
||||||
import 'package:stream_chat/src/client.dart';
|
import 'package:stream_chat/src/client.dart';
|
||||||
import 'package:stream_chat/src/exceptions.dart';
|
import 'package:stream_chat/src/exceptions.dart';
|
||||||
import 'package:stream_chat/src/models/channel_model.dart';
|
import 'package:stream_chat/src/core/models/channel_model.dart';
|
||||||
import 'package:stream_chat/src/models/filter.dart';
|
import 'package:stream_chat/src/core/models/filter.dart';
|
||||||
import 'package:stream_chat/src/models/message.dart';
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
class MockDio extends Mock implements DioForNative {}
|
class MockDio extends Mock implements DioForNative {}
|
||||||
@@ -55,8 +55,8 @@ void main() {
|
|||||||
expect(client.baseURL, 'chat-us-east-1.stream-io-api.com');
|
expect(client.baseURL, 'chat-us-east-1.stream-io-api.com');
|
||||||
expect(client.apiKey, 'api-key');
|
expect(client.apiKey, 'api-key');
|
||||||
expect(client.logLevel, Level.WARNING);
|
expect(client.logLevel, Level.WARNING);
|
||||||
expect(client.httpClient.options.connectTimeout, 6000);
|
expect(client.httpClient._options.connectTimeout, 6000);
|
||||||
expect(client.httpClient.options.receiveTimeout, 6000);
|
expect(client.httpClient._options.receiveTimeout, 6000);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should create the object correctly', overridePrint(() {
|
test('should create the object correctly', overridePrint(() {
|
||||||
@@ -76,8 +76,8 @@ void main() {
|
|||||||
expect(client.baseURL, 'test.com');
|
expect(client.baseURL, 'test.com');
|
||||||
expect(client.apiKey, 'api-key');
|
expect(client.apiKey, 'api-key');
|
||||||
expect(Logger.root.level, Level.INFO);
|
expect(Logger.root.level, Level.INFO);
|
||||||
expect(client.httpClient.options.connectTimeout, 10000);
|
expect(client.httpClient._options.connectTimeout, 10000);
|
||||||
expect(client.httpClient.options.receiveTimeout, 12000);
|
expect(client.httpClient._options.receiveTimeout, 12000);
|
||||||
|
|
||||||
client.logger.warning('test');
|
client.logger.warning('test');
|
||||||
client.logger.config('test config');
|
client.logger.config('test config');
|
||||||
@@ -170,7 +170,7 @@ void main() {
|
|||||||
await client.queryChannelsOnline(
|
await client.queryChannelsOnline(
|
||||||
filter: queryFilter,
|
filter: queryFilter,
|
||||||
sort: sortOptions,
|
sort: sortOptions,
|
||||||
options: options,
|
_options: options,
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
waitForConnect: false,
|
waitForConnect: false,
|
||||||
);
|
);
|
||||||
@@ -419,7 +419,7 @@ void main() {
|
|||||||
await client.queryUsers(
|
await client.queryUsers(
|
||||||
filter: queryFilter,
|
filter: queryFilter,
|
||||||
sort: sortOptions,
|
sort: sortOptions,
|
||||||
options: options,
|
_options: options,
|
||||||
);
|
);
|
||||||
|
|
||||||
verify(() =>
|
verify(() =>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:stream_chat/src/models/filter.dart';
|
import 'package:stream_chat/src/core/models/filter.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('operators', () {
|
group('operators', () {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat/version.dart';
|
import 'package:stream_chat/version.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
@@ -12,13 +14,38 @@ void prepareTest() {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
prepareTest();
|
prepareTest();
|
||||||
test('stream chat version matches pubspec', () {
|
test('stream chat version matches pubspec', () async {
|
||||||
final pubspecPath = '${Directory.current.path}/pubspec.yaml';
|
/// Create a new instance of [StreamChatClient]
|
||||||
final pubspec = File(pubspecPath).readAsStringSync();
|
/// by passing the apikey obtained from your project dashboard.
|
||||||
// ignore: unnecessary_string_escapes
|
final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO);
|
||||||
final regex = RegExp('version:\s*(.*)');
|
|
||||||
final match = regex.firstMatch(pubspec);
|
/// Set the current user. In a production scenario, this should be done using
|
||||||
expect(match, isNotNull);
|
/// a backend to generate a user token using our server SDK.
|
||||||
expect(PACKAGE_VERSION, match?.group(1)?.trim());
|
/// Please see the following for more information:
|
||||||
|
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
|
||||||
|
await client.connectUser(
|
||||||
|
User(
|
||||||
|
id: 'cool-shadow-7',
|
||||||
|
extraData: {
|
||||||
|
'image':
|
||||||
|
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''',
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.banUser('asdasdas');
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// final pubspecPath = '${Directory.current.path}/pubspec.yaml';
|
||||||
|
// final pubspec = File(pubspecPath).readAsStringSync();
|
||||||
|
// // ignore: unnecessary_string_escapes
|
||||||
|
// final regex = RegExp('version:\s*(.*)');
|
||||||
|
// final match = regex.firstMatch(pubspec);
|
||||||
|
// expect(match, isNotNull);
|
||||||
|
// expect(PACKAGE_VERSION, match?.group(1)?.trim());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
# stream_chat:
|
||||||
|
# path: ../../stream_chat
|
||||||
|
# stream_chat_flutter_core:
|
||||||
|
# path: ../../stream_chat_flutter_core
|
||||||
stream_chat_flutter:
|
stream_chat_flutter:
|
||||||
path: ../
|
path: ../
|
||||||
stream_chat_persistence:
|
stream_chat_persistence:
|
||||||
|
|||||||
@@ -132,10 +132,9 @@ class ChannelInfo extends StatelessWidget {
|
|||||||
vertical: VisualDensity.minimumDensity,
|
vertical: VisualDensity.minimumDensity,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () => client
|
||||||
await client.disconnect();
|
..closeConnection()
|
||||||
await client.connect();
|
..openConnection(),
|
||||||
},
|
|
||||||
child: Text(
|
child: Text(
|
||||||
'Try Again',
|
'Try Again',
|
||||||
style: textStyle?.copyWith(
|
style: textStyle?.copyWith(
|
||||||
|
|||||||
@@ -253,10 +253,9 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () => client
|
||||||
await client.disconnect();
|
..closeConnection()
|
||||||
await client.connect();
|
..openConnection(),
|
||||||
},
|
|
||||||
child: Text(
|
child: Text(
|
||||||
'Try Again',
|
'Try Again',
|
||||||
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
|
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
|
||||||
|
|||||||
@@ -58,9 +58,15 @@ class ChannelListView extends StatefulWidget {
|
|||||||
const ChannelListView({
|
const ChannelListView({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.filter,
|
this.filter,
|
||||||
this.options,
|
|
||||||
this.sort,
|
this.sort,
|
||||||
this.pagination,
|
this.state = true,
|
||||||
|
this.watch = true,
|
||||||
|
this.presence = false,
|
||||||
|
this.memberLimit,
|
||||||
|
this.messageLimit,
|
||||||
|
this.pagination = const PaginationParams(
|
||||||
|
limit: 25,
|
||||||
|
),
|
||||||
this.onChannelTap,
|
this.onChannelTap,
|
||||||
this.onChannelLongPress,
|
this.onChannelLongPress,
|
||||||
this.channelWidget,
|
this.channelWidget,
|
||||||
@@ -88,12 +94,6 @@ class ChannelListView extends StatefulWidget {
|
|||||||
/// You can also filter other built-in channel fields.
|
/// You can also filter other built-in channel fields.
|
||||||
final Filter? filter;
|
final Filter? filter;
|
||||||
|
|
||||||
/// Query channels options.
|
|
||||||
///
|
|
||||||
/// state: if true returns the Channel state
|
|
||||||
/// watch: if true listen to changes to this Channel in real time.
|
|
||||||
final Map<String, dynamic>? options;
|
|
||||||
|
|
||||||
/// The sorting used for the channels matching the filters.
|
/// The sorting used for the channels matching the filters.
|
||||||
/// Sorting is based on field and direction, multiple sorting options
|
/// Sorting is based on field and direction, multiple sorting options
|
||||||
/// can be provided.
|
/// can be provided.
|
||||||
@@ -102,11 +102,22 @@ class ChannelListView extends StatefulWidget {
|
|||||||
/// Direction can be ascending or descending.
|
/// Direction can be ascending or descending.
|
||||||
final List<SortOption<ChannelModel>>? sort;
|
final List<SortOption<ChannelModel>>? sort;
|
||||||
|
|
||||||
|
/// If true returns the Channel state
|
||||||
|
final bool state;
|
||||||
|
|
||||||
|
/// If true listen to changes to this Channel in real time.
|
||||||
|
final bool watch;
|
||||||
|
|
||||||
|
final bool presence;
|
||||||
|
|
||||||
|
final int? memberLimit;
|
||||||
|
final int? messageLimit;
|
||||||
|
|
||||||
/// Pagination parameters
|
/// Pagination parameters
|
||||||
/// limit: the number of channels to return (max is 30)
|
/// limit: the number of channels to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
/// message_limit: how many messages should be included to each channel
|
/// message_limit: how many messages should be included to each channel
|
||||||
final PaginationParams? pagination;
|
final PaginationParams pagination;
|
||||||
|
|
||||||
/// Function called when tapping on a channel
|
/// Function called when tapping on a channel
|
||||||
/// By default it calls [Navigator.push] building a [MaterialPageRoute]
|
/// By default it calls [Navigator.push] building a [MaterialPageRoute]
|
||||||
@@ -170,13 +181,14 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget child = ChannelListCore(
|
Widget child = ChannelListCore(
|
||||||
pagination: widget.pagination ??
|
|
||||||
const PaginationParams(
|
|
||||||
limit: 25,
|
|
||||||
),
|
|
||||||
options: widget.options,
|
|
||||||
sort: widget.sort,
|
|
||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
|
sort: widget.sort,
|
||||||
|
state: widget.state,
|
||||||
|
watch: widget.watch,
|
||||||
|
presence: widget.presence,
|
||||||
|
memberLimit: widget.memberLimit,
|
||||||
|
messageLimit: widget.messageLimit,
|
||||||
|
pagination: widget.pagination,
|
||||||
channelListController: _channelListController,
|
channelListController: _channelListController,
|
||||||
listBuilder: widget.listBuilder ?? _buildListView,
|
listBuilder: widget.listBuilder ?? _buildListView,
|
||||||
emptyBuilder: widget.emptyBuilder ?? _buildEmptyWidget,
|
emptyBuilder: widget.emptyBuilder ?? _buildEmptyWidget,
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ class UserListView extends StatefulWidget {
|
|||||||
const UserListView({
|
const UserListView({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.filter,
|
this.filter,
|
||||||
this.options,
|
|
||||||
this.sort,
|
this.sort,
|
||||||
|
this.presence,
|
||||||
this.pagination,
|
this.pagination,
|
||||||
this.onUserTap,
|
this.onUserTap,
|
||||||
this.onUserLongPress,
|
this.onUserLongPress,
|
||||||
@@ -75,12 +75,6 @@ class UserListView extends StatefulWidget {
|
|||||||
/// You can also filter other built-in channel fields.
|
/// You can also filter other built-in channel fields.
|
||||||
final Filter? filter;
|
final Filter? filter;
|
||||||
|
|
||||||
/// Query channels options.
|
|
||||||
///
|
|
||||||
/// state: if true returns the Channel state
|
|
||||||
/// watch: if true listen to changes to this Channel in real time.
|
|
||||||
final Map<String, dynamic>? options;
|
|
||||||
|
|
||||||
/// The sorting used for the channels matching the filters.
|
/// The sorting used for the channels matching the filters.
|
||||||
/// Sorting is based on field and direction, multiple sorting options can
|
/// Sorting is based on field and direction, multiple sorting options can
|
||||||
/// be provided.
|
/// be provided.
|
||||||
@@ -89,6 +83,9 @@ class UserListView extends StatefulWidget {
|
|||||||
/// Direction can be ascending or descending.
|
/// Direction can be ascending or descending.
|
||||||
final List<SortOption>? sort;
|
final List<SortOption>? sort;
|
||||||
|
|
||||||
|
///
|
||||||
|
final bool? presence;
|
||||||
|
|
||||||
/// Pagination parameters
|
/// Pagination parameters
|
||||||
/// limit: the number of users to return (max is 30)
|
/// limit: the number of users to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
@@ -177,9 +174,9 @@ class _UserListViewState extends State<UserListView>
|
|||||||
listBuilder:
|
listBuilder:
|
||||||
widget.listBuilder ?? (context, list) => _buildListView(list),
|
widget.listBuilder ?? (context, list) => _buildListView(list),
|
||||||
pagination: widget.pagination,
|
pagination: widget.pagination,
|
||||||
options: widget.options,
|
|
||||||
sort: widget.sort,
|
sort: widget.sort,
|
||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
|
presence: widget.presence,
|
||||||
groupAlphabetically: widget.groupAlphabetically,
|
groupAlphabetically: widget.groupAlphabetically,
|
||||||
userListController: _userListController,
|
userListController: _userListController,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ flutter:
|
|||||||
- animations/
|
- animations/
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
stream_chat:
|
||||||
|
path: ../stream_chat
|
||||||
|
stream_chat_flutter_core:
|
||||||
|
path: ../stream_chat_flutter_core
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
@@ -63,7 +63,11 @@ class ChannelListCore extends StatefulWidget {
|
|||||||
required this.loadingBuilder,
|
required this.loadingBuilder,
|
||||||
required this.listBuilder,
|
required this.listBuilder,
|
||||||
this.filter,
|
this.filter,
|
||||||
this.options,
|
this.state = true,
|
||||||
|
this.watch = true,
|
||||||
|
this.presence = false,
|
||||||
|
this.memberLimit,
|
||||||
|
this.messageLimit,
|
||||||
this.sort,
|
this.sort,
|
||||||
this.pagination = const PaginationParams(
|
this.pagination = const PaginationParams(
|
||||||
limit: 25,
|
limit: 25,
|
||||||
@@ -94,11 +98,11 @@ class ChannelListCore extends StatefulWidget {
|
|||||||
/// You can also filter other built-in channel fields.
|
/// You can also filter other built-in channel fields.
|
||||||
final Filter? filter;
|
final Filter? filter;
|
||||||
|
|
||||||
/// Query channels options.
|
// bool state = true,
|
||||||
///
|
// bool watch = true,
|
||||||
/// state: if true returns the Channel state
|
// bool presence = false,
|
||||||
/// watch: if true listen to changes to this Channel in real time.
|
// int? memberLimit,
|
||||||
final Map<String, dynamic>? options;
|
// int? messageLimit,
|
||||||
|
|
||||||
/// The sorting used for the channels matching the filters.
|
/// The sorting used for the channels matching the filters.
|
||||||
/// Sorting is based on field and direction, multiple sorting options can be
|
/// Sorting is based on field and direction, multiple sorting options can be
|
||||||
@@ -107,6 +111,17 @@ class ChannelListCore extends StatefulWidget {
|
|||||||
/// _at or member_count. Direction can be ascending or descending.
|
/// _at or member_count. Direction can be ascending or descending.
|
||||||
final List<SortOption<ChannelModel>>? sort;
|
final List<SortOption<ChannelModel>>? sort;
|
||||||
|
|
||||||
|
/// If true returns the Channel state
|
||||||
|
final bool state;
|
||||||
|
|
||||||
|
/// If true listen to changes to this Channel in real time.
|
||||||
|
final bool watch;
|
||||||
|
|
||||||
|
final bool presence;
|
||||||
|
|
||||||
|
final int? memberLimit;
|
||||||
|
final int? messageLimit;
|
||||||
|
|
||||||
/// Pagination parameters
|
/// Pagination parameters
|
||||||
/// limit: the number of channels to return (max is 30)
|
/// limit: the number of channels to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
@@ -149,18 +164,26 @@ class ChannelListCoreState extends State<ChannelListCore> {
|
|||||||
Future<void> loadData() => _channelsBloc.queryChannels(
|
Future<void> loadData() => _channelsBloc.queryChannels(
|
||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
sortOptions: widget.sort,
|
sortOptions: widget.sort,
|
||||||
|
state: widget.state,
|
||||||
|
watch: widget.watch,
|
||||||
|
presence: widget.presence,
|
||||||
|
memberLimit: widget.memberLimit,
|
||||||
|
messageLimit: widget.messageLimit,
|
||||||
paginationParams: widget.pagination,
|
paginationParams: widget.pagination,
|
||||||
options: widget.options,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Fetches more channels with updated pagination and updates the widget
|
/// Fetches more channels with updated pagination and updates the widget
|
||||||
Future<void> paginateData() => _channelsBloc.queryChannels(
|
Future<void> paginateData() => _channelsBloc.queryChannels(
|
||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
sortOptions: widget.sort,
|
sortOptions: widget.sort,
|
||||||
|
state: widget.state,
|
||||||
|
watch: widget.watch,
|
||||||
|
presence: widget.presence,
|
||||||
|
memberLimit: widget.memberLimit,
|
||||||
|
messageLimit: widget.messageLimit,
|
||||||
paginationParams: widget.pagination.copyWith(
|
paginationParams: widget.pagination.copyWith(
|
||||||
offset: _channelsBloc.channels?.length ?? 0,
|
offset: _channelsBloc.channels?.length ?? 0,
|
||||||
),
|
),
|
||||||
options: widget.options,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
StreamSubscription<Event>? _subscription;
|
StreamSubscription<Event>? _subscription;
|
||||||
@@ -200,7 +223,11 @@ class ChannelListCoreState extends State<ChannelListCore> {
|
|||||||
|
|
||||||
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
|
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
|
||||||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
||||||
widget.options?.toString() != oldWidget.options?.toString() ||
|
widget.state != oldWidget.state ||
|
||||||
|
widget.watch != oldWidget.watch ||
|
||||||
|
widget.presence != oldWidget.presence ||
|
||||||
|
widget.messageLimit != oldWidget.messageLimit ||
|
||||||
|
widget.memberLimit != oldWidget.memberLimit ||
|
||||||
widget.pagination.toJson().toString() !=
|
widget.pagination.toJson().toString() !=
|
||||||
oldWidget.pagination.toJson().toString()) {
|
oldWidget.pagination.toJson().toString()) {
|
||||||
loadData();
|
loadData();
|
||||||
|
|||||||
@@ -95,8 +95,13 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
|||||||
Future<void> queryChannels({
|
Future<void> queryChannels({
|
||||||
Filter? filter,
|
Filter? filter,
|
||||||
List<SortOption<ChannelModel>>? sortOptions,
|
List<SortOption<ChannelModel>>? sortOptions,
|
||||||
|
bool state = true,
|
||||||
|
bool watch = true,
|
||||||
|
bool presence = false,
|
||||||
|
int? memberLimit,
|
||||||
|
int? messageLimit,
|
||||||
|
bool waitForConnect = true,
|
||||||
PaginationParams paginationParams = const PaginationParams(limit: 30),
|
PaginationParams paginationParams = const PaginationParams(limit: 30),
|
||||||
Map<String, dynamic>? options,
|
|
||||||
}) async {
|
}) async {
|
||||||
final client = _streamChatCoreState!.client;
|
final client = _streamChatCoreState!.client;
|
||||||
|
|
||||||
@@ -117,7 +122,12 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
|||||||
await for (final channels in client.queryChannels(
|
await for (final channels in client.queryChannels(
|
||||||
filter: filter,
|
filter: filter,
|
||||||
sort: sortOptions,
|
sort: sortOptions,
|
||||||
options: options,
|
state: state,
|
||||||
|
watch: watch,
|
||||||
|
presence: presence,
|
||||||
|
memberLimit: memberLimit,
|
||||||
|
messageLimit: messageLimit,
|
||||||
|
waitForConnect: waitForConnect,
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
)) {
|
)) {
|
||||||
newChannels = channels;
|
newChannels = channels;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class StreamChatCoreState extends State<StreamChatCore>
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
if (state == AppLifecycleState.paused) {
|
if (state == AppLifecycleState.paused) {
|
||||||
if (widget.onBackgroundEventReceived == null) {
|
if (widget.onBackgroundEventReceived == null) {
|
||||||
client.disconnect();
|
client.closeConnection();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_eventSubscription = client.on().listen(
|
_eventSubscription = client.on().listen(
|
||||||
@@ -118,7 +118,7 @@ class StreamChatCoreState extends State<StreamChatCore>
|
|||||||
|
|
||||||
void onTimerComplete() {
|
void onTimerComplete() {
|
||||||
_eventSubscription?.cancel();
|
_eventSubscription?.cancel();
|
||||||
client.disconnect();
|
client.closeConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
_disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete);
|
_disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete);
|
||||||
@@ -128,7 +128,7 @@ class StreamChatCoreState extends State<StreamChatCore>
|
|||||||
_disconnectTimer?.cancel();
|
_disconnectTimer?.cancel();
|
||||||
} else {
|
} else {
|
||||||
if (client.wsConnectionStatus == ConnectionStatus.disconnected) {
|
if (client.wsConnectionStatus == ConnectionStatus.disconnected) {
|
||||||
client.connect();
|
client.openConnection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ class UserListCore extends StatefulWidget {
|
|||||||
required this.listBuilder,
|
required this.listBuilder,
|
||||||
Key? key,
|
Key? key,
|
||||||
this.filter,
|
this.filter,
|
||||||
this.options,
|
|
||||||
this.sort,
|
this.sort,
|
||||||
|
this.presence,
|
||||||
this.pagination,
|
this.pagination,
|
||||||
this.groupAlphabetically = false,
|
this.groupAlphabetically = false,
|
||||||
this.userListController,
|
this.userListController,
|
||||||
@@ -92,18 +92,15 @@ class UserListCore extends StatefulWidget {
|
|||||||
/// You can also filter other built-in channel fields.
|
/// You can also filter other built-in channel fields.
|
||||||
final Filter? filter;
|
final Filter? filter;
|
||||||
|
|
||||||
/// Query channels options.
|
|
||||||
///
|
|
||||||
/// state: if true returns the Channel state
|
|
||||||
/// watch: if true listen to changes to this Channel in real time.
|
|
||||||
final Map<String, dynamic>? options;
|
|
||||||
|
|
||||||
/// The sorting used for the channels matching the filters.
|
/// The sorting used for the channels matching the filters.
|
||||||
/// Sorting is based on field and direction, multiple sorting options can be
|
/// Sorting is based on field and direction, multiple sorting options can be
|
||||||
/// provided. You can sort based on last_updated, last_message_at, updated_at,
|
/// provided. You can sort based on last_updated, last_message_at, updated_at,
|
||||||
/// created_at or member_count. Direction can be ascending or descending.
|
/// created_at or member_count. Direction can be ascending or descending.
|
||||||
final List<SortOption>? sort;
|
final List<SortOption>? sort;
|
||||||
|
|
||||||
|
///
|
||||||
|
final bool? presence;
|
||||||
|
|
||||||
/// Pagination parameters
|
/// Pagination parameters
|
||||||
/// limit: the number of users to return (max is 30)
|
/// limit: the number of users to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
@@ -186,22 +183,22 @@ class UserListCoreState extends State<UserListCore>
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// ignore: public_member_api_docs
|
/// Fetches initial users and updates the widget
|
||||||
Future<void> loadData() => _usersBloc!.queryUsers(
|
Future<void> loadData() => _usersBloc!.queryUsers(
|
||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
sort: widget.sort,
|
sort: widget.sort,
|
||||||
|
presence: widget.presence,
|
||||||
pagination: widget.pagination,
|
pagination: widget.pagination,
|
||||||
options: widget.options,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// ignore: public_member_api_docs
|
/// Fetches more users with updated pagination and updates the widget
|
||||||
Future<void> paginateData() => _usersBloc!.queryUsers(
|
Future<void> paginateData() => _usersBloc!.queryUsers(
|
||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
sort: widget.sort,
|
sort: widget.sort,
|
||||||
|
presence: widget.presence,
|
||||||
pagination: widget.pagination!.copyWith(
|
pagination: widget.pagination!.copyWith(
|
||||||
offset: _usersBloc!.users?.length ?? 0,
|
offset: _usersBloc!.users?.length ?? 0,
|
||||||
),
|
),
|
||||||
options: widget.options,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -209,7 +206,7 @@ class UserListCoreState extends State<UserListCore>
|
|||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
|
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
|
||||||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
||||||
widget.options?.toString() != oldWidget.options?.toString() ||
|
widget.presence != oldWidget.presence ||
|
||||||
widget.pagination?.toJson().toString() !=
|
widget.pagination?.toJson().toString() !=
|
||||||
oldWidget.pagination?.toJson().toString()) {
|
oldWidget.pagination?.toJson().toString()) {
|
||||||
loadData();
|
loadData();
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class UsersBlocState extends State<UsersBloc>
|
|||||||
Future<void> queryUsers({
|
Future<void> queryUsers({
|
||||||
Filter? filter,
|
Filter? filter,
|
||||||
List<SortOption>? sort,
|
List<SortOption>? sort,
|
||||||
Map<String, dynamic>? options,
|
bool? presence,
|
||||||
PaginationParams? pagination,
|
PaginationParams? pagination,
|
||||||
}) async {
|
}) async {
|
||||||
final client = _streamChatCore.client;
|
final client = _streamChatCore.client;
|
||||||
@@ -82,7 +82,7 @@ class UsersBlocState extends State<UsersBloc>
|
|||||||
final usersResponse = await client.queryUsers(
|
final usersResponse = await client.queryUsers(
|
||||||
filter: filter,
|
filter: filter,
|
||||||
sort: sort,
|
sort: sort,
|
||||||
options: options,
|
presence: presence,
|
||||||
pagination: pagination,
|
pagination: pagination,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ dependencies:
|
|||||||
rxdart: ^0.26.0
|
rxdart: ^0.26.0
|
||||||
stream_chat: ^2.0.0-nullsafety.1
|
stream_chat: ^2.0.0-nullsafety.1
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
stream_chat:
|
||||||
|
path: ../stream_chat
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
fake_async: ^1.2.0
|
fake_async: ^1.2.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -142,7 +142,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).thenThrow(error);
|
)).thenThrow(error);
|
||||||
|
|
||||||
@@ -162,7 +166,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -191,7 +199,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).thenAnswer((_) => Stream.value(channels));
|
)).thenAnswer((_) => Stream.value(channels));
|
||||||
|
|
||||||
@@ -211,7 +223,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -240,7 +256,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).thenAnswer((_) => Stream.value(channels));
|
)).thenAnswer((_) => Stream.value(channels));
|
||||||
|
|
||||||
@@ -260,7 +280,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -297,7 +321,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).thenAnswer((_) => Stream.value(channels));
|
)).thenAnswer((_) => Stream.value(channels));
|
||||||
|
|
||||||
@@ -321,7 +349,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -335,7 +367,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: updatedPagination,
|
paginationParams: updatedPagination,
|
||||||
)).thenAnswer((_) => Stream.value(paginatedChannels));
|
)).thenAnswer((_) => Stream.value(paginatedChannels));
|
||||||
|
|
||||||
@@ -355,7 +391,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: updatedPagination,
|
paginationParams: updatedPagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -396,7 +436,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).thenAnswer((_) => Stream.value(channels));
|
)).thenAnswer((_) => Stream.value(channels));
|
||||||
|
|
||||||
@@ -424,7 +468,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: pagination,
|
paginationParams: pagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -436,7 +484,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: updatedPagination,
|
paginationParams: updatedPagination,
|
||||||
)).thenAnswer((_) => Stream.value(updatedChannels));
|
)).thenAnswer((_) => Stream.value(updatedChannels));
|
||||||
|
|
||||||
@@ -451,7 +503,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: updatedPagination,
|
paginationParams: updatedPagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -114,7 +114,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).thenAnswer(
|
)).thenAnswer(
|
||||||
(_) => Stream.fromIterable([offlineChannels, onlineChannels]),
|
(_) => Stream.fromIterable([offlineChannels, onlineChannels]),
|
||||||
@@ -133,7 +137,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -176,7 +184,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).thenThrow(error);
|
)).thenThrow(error);
|
||||||
|
|
||||||
@@ -190,7 +202,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -228,7 +244,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).thenAnswer((_) => Stream.value(channels));
|
)).thenAnswer((_) => Stream.value(channels));
|
||||||
|
|
||||||
@@ -245,7 +265,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -257,7 +281,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
)).thenAnswer(
|
)).thenAnswer(
|
||||||
(_) => Stream.value(newChannels),
|
(_) => Stream.value(newChannels),
|
||||||
@@ -279,7 +307,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -320,7 +352,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
)).thenAnswer((_) => Stream.value(channels));
|
)).thenAnswer((_) => Stream.value(channels));
|
||||||
|
|
||||||
@@ -336,7 +372,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -345,7 +385,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
)).thenThrow(error);
|
)).thenThrow(error);
|
||||||
|
|
||||||
@@ -359,7 +403,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -404,7 +452,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).thenAnswer(
|
)).thenAnswer(
|
||||||
(_) => Stream.value(channels),
|
(_) => Stream.value(channels),
|
||||||
@@ -415,7 +467,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -476,7 +532,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).thenAnswer(
|
)).thenAnswer(
|
||||||
(_) => Stream.value(channels),
|
(_) => Stream.value(channels),
|
||||||
@@ -487,11 +547,16 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
final channelDeletedOrNotificationRemovedEvent = Event(
|
final channelDeletedOrNotificationRemovedEvent = Event(
|
||||||
|
type: EventType.channelDeleted,
|
||||||
channel: EventChannel(
|
channel: EventChannel(
|
||||||
cid: channels.first.cid!,
|
cid: channels.first.cid!,
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
@@ -557,7 +622,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).thenAnswer(
|
)).thenAnswer(
|
||||||
(_) => Stream.value(channels),
|
(_) => Stream.value(channels),
|
||||||
@@ -568,7 +637,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -648,7 +721,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).thenAnswer(
|
)).thenAnswer(
|
||||||
(_) => Stream.value(channels),
|
(_) => Stream.value(channels),
|
||||||
@@ -659,7 +736,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -735,7 +816,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).thenAnswer(
|
)).thenAnswer(
|
||||||
(_) => Stream.value(channels),
|
(_) => Stream.value(channels),
|
||||||
@@ -746,7 +831,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -813,7 +902,11 @@ void main() {
|
|||||||
when(() => mockClient.queryChannels(
|
when(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).thenAnswer(
|
)).thenAnswer(
|
||||||
(_) => Stream.value(channels),
|
(_) => Stream.value(channels),
|
||||||
@@ -824,7 +917,11 @@ void main() {
|
|||||||
verify(() => mockClient.queryChannels(
|
verify(() => mockClient.queryChannels(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
state: any(named: 'state'),
|
||||||
|
watch: any(named: 'watch'),
|
||||||
|
presence: any(named: 'presence'),
|
||||||
|
memberLimit: any(named: 'memberLimit'),
|
||||||
|
messageLimit: any(named: 'messageLimit'),
|
||||||
paginationParams: any(named: 'paginationParams'),
|
paginationParams: any(named: 'paginationParams'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'didChangeAppLifecycleState should call client.disconnect() and return '
|
'didChangeAppLifecycleState should call client.closeConnection and return '
|
||||||
'if onBackgroundEventReceived is null and the widget lifestyle changes to '
|
'if onBackgroundEventReceived is null and the widget lifestyle changes to '
|
||||||
'AppLifecycleState.paused',
|
'AppLifecycleState.paused',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
@@ -69,7 +69,7 @@ void main() {
|
|||||||
expect(find.byKey(streamChatCoreKey), findsOneWidget);
|
expect(find.byKey(streamChatCoreKey), findsOneWidget);
|
||||||
expect(find.byKey(childKey), findsOneWidget);
|
expect(find.byKey(childKey), findsOneWidget);
|
||||||
|
|
||||||
when(() => mockClient.disconnect()).thenAnswer((_) async {
|
when(() => mockClient.closeConnection()).thenAnswer((_) async {
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ void main() {
|
|||||||
|
|
||||||
streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused);
|
streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused);
|
||||||
|
|
||||||
verify(() => mockClient.disconnect()).called(1);
|
verify(() => mockClient.closeConnection()).called(1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -107,9 +107,9 @@ void main() {
|
|||||||
expect(find.byKey(streamChatCoreKey), findsOneWidget);
|
expect(find.byKey(streamChatCoreKey), findsOneWidget);
|
||||||
expect(find.byKey(childKey), findsOneWidget);
|
expect(find.byKey(childKey), findsOneWidget);
|
||||||
|
|
||||||
final event = Event();
|
final event = Event(type: EventType.any);
|
||||||
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
|
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
|
||||||
when(() => mockClient.disconnect()).thenAnswer((_) async {
|
when(() => mockClient.closeConnection()).thenAnswer((_) async {
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ void main() {
|
|||||||
|
|
||||||
await Future.delayed(backgroundKeepAlive);
|
await Future.delayed(backgroundKeepAlive);
|
||||||
|
|
||||||
verify(() => mockClient.disconnect()).called(1);
|
verify(() => mockClient.closeConnection()).called(1);
|
||||||
verifyNever(() => mockOnBackgroundEventReceived.call(event));
|
verifyNever(() => mockOnBackgroundEventReceived.call(event));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -156,7 +156,7 @@ void main() {
|
|||||||
expect(find.byKey(streamChatCoreKey), findsOneWidget);
|
expect(find.byKey(streamChatCoreKey), findsOneWidget);
|
||||||
expect(find.byKey(childKey), findsOneWidget);
|
expect(find.byKey(childKey), findsOneWidget);
|
||||||
|
|
||||||
final event = Event();
|
final event = Event(type: EventType.any);
|
||||||
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
|
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
|
||||||
|
|
||||||
final streamChatCoreState = tester.state<StreamChatCoreState>(
|
final streamChatCoreState = tester.state<StreamChatCoreState>(
|
||||||
@@ -198,10 +198,10 @@ void main() {
|
|||||||
expect(find.byKey(streamChatCoreKey), findsOneWidget);
|
expect(find.byKey(streamChatCoreKey), findsOneWidget);
|
||||||
expect(find.byKey(childKey), findsOneWidget);
|
expect(find.byKey(childKey), findsOneWidget);
|
||||||
|
|
||||||
final event = Event();
|
final event = Event(type: EventType.any);
|
||||||
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
|
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
|
||||||
when(() => mockClient.connect()).thenAnswer((_) async => event);
|
when(() => mockClient.openConnection()).thenAnswer((_) async => event);
|
||||||
when(mockClient.disconnect).thenAnswer((_) async => null);
|
when(() => mockClient.closeConnection()).thenAnswer((_) async => null);
|
||||||
when(() => mockClient.wsConnectionStatus)
|
when(() => mockClient.wsConnectionStatus)
|
||||||
.thenReturn(ConnectionStatus.disconnected);
|
.thenReturn(ConnectionStatus.disconnected);
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ void main() {
|
|||||||
streamChatCoreState
|
streamChatCoreState
|
||||||
.didChangeAppLifecycleState(AppLifecycleState.resumed);
|
.didChangeAppLifecycleState(AppLifecycleState.resumed);
|
||||||
|
|
||||||
verify(() => mockClient.connect()).called(1);
|
verify(() => mockClient.openConnection()).called(1);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenThrow(error);
|
)).thenThrow(error);
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -179,7 +179,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -224,7 +224,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -283,7 +283,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -352,7 +352,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
||||||
|
|
||||||
@@ -379,7 +379,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -393,7 +393,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: updatedPagination,
|
pagination: updatedPagination,
|
||||||
))
|
))
|
||||||
.thenAnswer(
|
.thenAnswer(
|
||||||
@@ -411,7 +411,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: updatedPagination,
|
pagination: updatedPagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -457,7 +457,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
||||||
|
|
||||||
@@ -488,7 +488,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -500,7 +500,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: updatedPagination,
|
pagination: updatedPagination,
|
||||||
))
|
))
|
||||||
.thenAnswer((_) async => QueryUsersResponse()..users = updatedUsers);
|
.thenAnswer((_) async => QueryUsersResponse()..users = updatedUsers);
|
||||||
@@ -515,7 +515,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: updatedPagination,
|
pagination: updatedPagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -121,7 +121,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenThrow(error);
|
)).thenThrow(error);
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -171,7 +171,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: pagination,
|
pagination: pagination,
|
||||||
))
|
))
|
||||||
.thenAnswer(
|
.thenAnswer(
|
||||||
@@ -218,7 +218,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: pagination,
|
pagination: pagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
@@ -254,7 +254,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: any(named: 'pagination'),
|
pagination: any(named: 'pagination'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ void main() {
|
|||||||
when(() => mockClient.queryUsers(
|
when(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: pagination,
|
pagination: pagination,
|
||||||
)).thenThrow(error);
|
)).thenThrow(error);
|
||||||
|
|
||||||
@@ -294,7 +294,7 @@ void main() {
|
|||||||
verify(() => mockClient.queryUsers(
|
verify(() => mockClient.queryUsers(
|
||||||
filter: any(named: 'filter'),
|
filter: any(named: 'filter'),
|
||||||
sort: any(named: 'sort'),
|
sort: any(named: 'sort'),
|
||||||
options: any(named: 'options'),
|
presence: any(named: 'presence'),
|
||||||
pagination: pagination,
|
pagination: pagination,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
return into(connectionEvents).insert(
|
return into(connectionEvents).insert(
|
||||||
ConnectionEventEntity(
|
ConnectionEventEntity(
|
||||||
id: 1,
|
id: 1,
|
||||||
|
type: event.type,
|
||||||
lastSyncAt: connectionInfo?.lastSyncAt,
|
lastSyncAt: connectionInfo?.lastSyncAt,
|
||||||
lastEventAt: event.createdAt ?? connectionInfo?.lastEventAt,
|
lastEventAt: event.createdAt,
|
||||||
totalUnreadCount:
|
totalUnreadCount:
|
||||||
event.totalUnreadCount ?? connectionInfo?.totalUnreadCount,
|
event.totalUnreadCount ?? connectionInfo?.totalUnreadCount,
|
||||||
ownUser: event.me?.toJson() ?? connectionInfo?.ownUser,
|
ownUser: event.me?.toJson() ?? connectionInfo?.ownUser,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user