Release/1.4.0 beta (#318)
* fix: fixed long system messages overflowing * fix: fixed long system messages overflowing * [UI-Kit] Refactor, add support for downloading attachments. Signed-off-by: Sahil Kumar <[email protected]> * fix: header casing * [UI-Kit] Disable attachment download dialog pop using back button Signed-off-by: Sahil Kumar <[email protected]> * stream_chat_flutter fix svg on web * [UI-Kit] Pop attachment download dialog in case of any error. Signed-off-by: Sahil Kumar <[email protected]> * restore svg * feat: add pr title linter * fix test * fix client headers * update system message design * update gh token env var * add scopes * expose systemMessageBuilder in messagelistview * fix(ui): fix button size * add dispatch workflow * add dispatch workflow * update dispatch acton * add runson * fix ref * fix ref * update secret * specify repo * fix: list scroll keyboard behaviour (#304) * fix: Keyboard now closes by clicking on space or scrolling. * fix: Exposed system and normal message tap builders * fmt: dartfmt * feat: Add support for messages filter in `MessageListView` and `MessageListCore` (#303) * [MessageListView, MessageListCore] Add support for message filter Signed-off-by: Sahil Kumar <[email protected]> * [UI-Kit -> Pubspec] Remove `stream_chat_flutter_core` relative import Signed-off-by: Sahil Kumar <[email protected]> * add relative import * add relative import * remove svg for web * remove badge logic on web * style: Update for team lint (#297) * Update for team lint * fix tests * remove pedantic and sort deps * remove jiffy from system message Co-authored-by: Salvatore Giordano <[email protected]> * add section highlighting sample app repo (#307) Co-authored-by: Salvatore Giordano <[email protected]> * add null check to debounce * fix messagelistview loading errors * remove core relative import * fix: fixes message newline issue (#308) * fix: minor * fix: generate image thumbnails and use transparent image as placeholder for cached network images (#310) * update cdn handling * fix image attachment with new cdn * fix(ui): Fix message input permission request crash (#311) Signed-off-by: Sahil Kumar <[email protected]> * fix(llc): update member presence and add skip_push to message (#314) * update member presence * add skip_push to message * fix tests * fix: Fixed shimmer overflow (#316) * fix dialogs * fix(llc): Fix attachment upload state uneven progress. (#315) * feat(llc): Add rate limiter functions. fix(llc): Fix attachment upload state uneven progress. Signed-off-by: Sahil Kumar <[email protected]> * feat(llc): Export `async.dart` and `rate_limit.dart` Signed-off-by: Sahil Kumar <[email protected]> * refactor(llc): Replace our `rate_limit` implementation with the dart port of js `lodash` to better handlw `cancel` and `flush` functions. Signed-off-by: Sahil Kumar <[email protected]> * fix(UI-Kit): Make android example run on real devices Signed-off-by: Sahil Kumar <[email protected]> * fix offline behaviour Co-authored-by: Salvatore Giordano <[email protected]> * feat: Introduce onAttachmentTap on messages attachment (#309) * introdce onAttachmentTap on message * correct indent * sdfsd Co-authored-by: Salvatore Giordano <[email protected]> * feat: added customization options in main widgets (#312) * polish channelpreview customization options * polish channelheader customization options * polish messageinput customization options * polish threadheader customization options * polish channellistheader customization options * show the parent message if no messages in thread * fix edit message * fix channelinfo textstyle * fix review * fix useravatar * add ontitle tap to thread header * add custom message actions * add borderradius and button builder * add ontap to messageinput custom send button * fix bottom sheet * add doc * use placeholder image * add doc * fix listtile density * remove subtitle from ChannelListHeaderTheme.copyWith * add InputDecoration.merge extension * fix: use shimmer in images * fix: remove notification badge logic from the sdk and move it to sample app * fix import * fix: Method being called during build (#317) * method call bug fix * dartfmt * fix: show error messages as system and keep them in the message input (#319) * fix: pre release (#324) * use share_plus plugin * bump dependency versions * fix ui for web * unfocus messageinput only on commands * fix default error for messagesearchlistview * fix thumbnail animation * fix lint * ignore example in linter * update changelogs * fix lint * fix lint * update stream_chat pana min * add doc Co-authored-by: Sahil Kumar <[email protected]> Co-authored-by: Deven Joshi <[email protected]> Co-authored-by: Neevash Ramdial (Nash) <[email protected]> Co-authored-by: Nelson Nunes <[email protected]>
This commit is contained in:
co-authored by
Sahil Kumar
Deven Joshi
Neevash Ramdial
Nelson Nunes
parent
3332a48a0e
commit
4488fa6c3a
@@ -2,24 +2,16 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:pedantic/pedantic.dart' show unawaited;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:stream_chat/src/api/retry_queue.dart';
|
||||
import 'package:stream_chat/src/debounce.dart';
|
||||
import 'package:stream_chat/src/event_type.dart';
|
||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
||||
import 'package:stream_chat/src/models/channel_state.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
import '../client.dart';
|
||||
import '../models/event.dart';
|
||||
import '../models/member.dart';
|
||||
import '../models/message.dart';
|
||||
import 'requests.dart';
|
||||
import 'responses.dart';
|
||||
import 'package:stream_chat/src/extensions/rate_limit.dart';
|
||||
|
||||
/// This a the class that manages a specific channel.
|
||||
class Channel {
|
||||
@@ -57,7 +49,8 @@ class Channel {
|
||||
set extraData(Map<String, dynamic> extraData) {
|
||||
if (_initializedCompleter.isCompleted) {
|
||||
throw Exception(
|
||||
'Once the channel is initialized you should use channel.update to update channel data');
|
||||
'Once the channel is initialized you should use channel.update '
|
||||
'to update channel data');
|
||||
}
|
||||
_extraData = extraData;
|
||||
}
|
||||
@@ -168,14 +161,15 @@ class Channel {
|
||||
final Completer<bool> _initializedCompleter = Completer();
|
||||
|
||||
/// True if this is initialized
|
||||
/// Call [watch] to initialize the client or instantiate it using [Channel.fromState]
|
||||
/// Call [watch] to initialize the client or instantiate it using
|
||||
/// [Channel.fromState]
|
||||
Future<bool> get initialized => _initializedCompleter.future;
|
||||
|
||||
final _cancelableAttachmentUploadRequest = <String, CancelToken>{};
|
||||
final _messageAttachmentsUploadCompleter = <String, Completer>{};
|
||||
|
||||
/// Cancels [attachmentId] upload request. Throws exception if the request hasn't
|
||||
/// even started yet, Already completed or Already cancelled.
|
||||
/// Cancels [attachmentId] upload request. Throws exception if the request
|
||||
/// hasn't even started yet, Already completed or Already cancelled.
|
||||
///
|
||||
/// Optionally, provide a [reason] for the cancellation.
|
||||
void cancelAttachmentUpload(
|
||||
@@ -185,7 +179,8 @@ class Channel {
|
||||
final cancelToken = _cancelableAttachmentUploadRequest[attachmentId];
|
||||
if (cancelToken == null) {
|
||||
throw Exception(
|
||||
"Upload request for this Attachment hasn't started yet or else Already completed",
|
||||
"Upload request for this Attachment hasn't started yet or else "
|
||||
'Already completed',
|
||||
);
|
||||
}
|
||||
if (cancelToken.isCancelled) throw Exception('Already cancelled');
|
||||
@@ -193,15 +188,14 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Retries the failed [attachmentId] upload request.
|
||||
Future<void> retryAttachmentUpload(String messageId, String attachmentId) {
|
||||
return _uploadAttachments(messageId, [attachmentId]);
|
||||
}
|
||||
Future<void> retryAttachmentUpload(String messageId, String attachmentId) =>
|
||||
_uploadAttachments(messageId, [attachmentId]);
|
||||
|
||||
Future<void> _uploadAttachments(
|
||||
String messageId,
|
||||
Iterable<String> attachmentIds,
|
||||
) {
|
||||
var message = state.messages.firstWhere(
|
||||
final message = state.messages.firstWhere(
|
||||
(it) => it.id == messageId,
|
||||
orElse: () => null,
|
||||
);
|
||||
@@ -224,29 +218,29 @@ class Channel {
|
||||
}
|
||||
|
||||
client.logger.info('Found ${attachments.length} attachments');
|
||||
|
||||
void updateAttachment(Attachment attachment) {
|
||||
final index =
|
||||
message.attachments.indexWhere((it) => it.id == attachment.id);
|
||||
if (index != -1) {
|
||||
message.attachments[index] = attachment;
|
||||
state?.addMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
return Future.wait(attachments.map((it) {
|
||||
client.logger.info('Uploading ${it.id} attachment...');
|
||||
|
||||
void updateAttachment(Attachment attachment) {
|
||||
final index = message.attachments.indexWhere((it) {
|
||||
return it.id == attachment.id;
|
||||
});
|
||||
if (index != -1) {
|
||||
message.attachments[index] = attachment;
|
||||
state?.addMessage(message);
|
||||
}
|
||||
}
|
||||
final throttledUpdateAttachment = updateAttachment.throttled(
|
||||
const Duration(milliseconds: 500),
|
||||
);
|
||||
|
||||
void onSendProgress(int sent, int total) {
|
||||
debounce(
|
||||
timeout: Duration(seconds: 1),
|
||||
target: updateAttachment,
|
||||
positionalArguments: [
|
||||
it.copyWith(
|
||||
uploadState: UploadState.inProgress(uploaded: sent, total: total),
|
||||
),
|
||||
],
|
||||
);
|
||||
throttledUpdateAttachment([
|
||||
it.copyWith(
|
||||
uploadState: UploadState.inProgress(uploaded: sent, total: total),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
final isImage = it.type == 'image';
|
||||
@@ -270,18 +264,26 @@ class Channel {
|
||||
client.logger.info('Attachment ${it.id} uploaded successfully...');
|
||||
if (isImage) {
|
||||
updateAttachment(
|
||||
it.copyWith(imageUrl: url, uploadState: UploadState.success()),
|
||||
it.copyWith(
|
||||
imageUrl: url,
|
||||
uploadState: const UploadState.success(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
updateAttachment(
|
||||
it.copyWith(assetUrl: url, uploadState: UploadState.success()),
|
||||
it.copyWith(
|
||||
assetUrl: url,
|
||||
uploadState: const UploadState.success(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}).catchError((e, stk) {
|
||||
client.logger.severe('error uploading the attachment', e, stk);
|
||||
updateAttachment(
|
||||
it.copyWith(uploadState: UploadState.failed(error: e.toString())),
|
||||
);
|
||||
}).whenComplete(() {
|
||||
throttledUpdateAttachment?.cancel();
|
||||
_cancelableAttachmentUploadRequest.remove(it.id);
|
||||
});
|
||||
})).whenComplete(() {
|
||||
@@ -305,6 +307,7 @@ class Channel {
|
||||
(m) => m.id == message?.quotedMessageId,
|
||||
orElse: () => null,
|
||||
);
|
||||
// ignore: parameter_assignments
|
||||
message = message.copyWith(
|
||||
createdAt: message.createdAt ?? DateTime.now(),
|
||||
user: _client.state.user,
|
||||
@@ -313,7 +316,7 @@ class Channel {
|
||||
attachments: message.attachments?.map(
|
||||
(it) {
|
||||
if (it.uploadState.isSuccess) return it;
|
||||
return it.copyWith(uploadState: UploadState.preparing());
|
||||
return it.copyWith(uploadState: const UploadState.preparing());
|
||||
},
|
||||
)?.toList(),
|
||||
);
|
||||
@@ -335,11 +338,13 @@ class Channel {
|
||||
_messageAttachmentsUploadCompleter[message.id] =
|
||||
attachmentsUploadCompleter;
|
||||
|
||||
unawaited(_uploadAttachments(
|
||||
// ignore: unawaited_futures
|
||||
_uploadAttachments(
|
||||
message.id,
|
||||
message.attachments.map((it) => it.id),
|
||||
));
|
||||
);
|
||||
|
||||
// ignore: parameter_assignments
|
||||
message = await attachmentsUploadCompleter.future;
|
||||
}
|
||||
|
||||
@@ -364,13 +369,14 @@ class Channel {
|
||||
.remove(message.id)
|
||||
?.completeError('Message Cancelled');
|
||||
|
||||
// ignore: parameter_assignments
|
||||
message = message.copyWith(
|
||||
status: MessageSendingStatus.updating,
|
||||
updatedAt: message.updatedAt ?? DateTime.now(),
|
||||
attachments: message.attachments?.map(
|
||||
(it) {
|
||||
if (it.uploadState.isSuccess) return it;
|
||||
return it.copyWith(uploadState: UploadState.preparing());
|
||||
return it.copyWith(uploadState: const UploadState.preparing());
|
||||
},
|
||||
)?.toList(),
|
||||
);
|
||||
@@ -383,11 +389,13 @@ class Channel {
|
||||
_messageAttachmentsUploadCompleter[message.id] =
|
||||
attachmentsUploadCompleter;
|
||||
|
||||
unawaited(_uploadAttachments(
|
||||
// ignore: unawaited_futures
|
||||
_uploadAttachments(
|
||||
message.id,
|
||||
message.attachments.map((it) => it.id),
|
||||
));
|
||||
);
|
||||
|
||||
// ignore: parameter_assignments
|
||||
message = await attachmentsUploadCompleter.future;
|
||||
}
|
||||
|
||||
@@ -423,6 +431,7 @@ class Channel {
|
||||
}
|
||||
|
||||
try {
|
||||
// ignore: parameter_assignments
|
||||
message = message.copyWith(
|
||||
type: 'deleted',
|
||||
status: MessageSendingStatus.deleting,
|
||||
@@ -456,7 +465,7 @@ class Channel {
|
||||
throw ArgumentError('Invalid timeout or Expiration date');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
}(), 'Check for invalid token or expiration date');
|
||||
|
||||
DateTime pinExpires;
|
||||
if (timeoutOrExpirationDate is DateTime) {
|
||||
@@ -475,39 +484,36 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Unpins provided message
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) {
|
||||
return updateMessage(message.copyWith(pinned: false));
|
||||
}
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) =>
|
||||
updateMessage(message.copyWith(pinned: false));
|
||||
|
||||
/// Send a file to this channel
|
||||
Future<SendFileResponse> sendFile(
|
||||
AttachmentFile file, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.sendFile(
|
||||
file,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
_client.sendFile(
|
||||
file,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// Send an image to this channel
|
||||
Future<SendImageResponse> sendImage(
|
||||
AttachmentFile file, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.sendImage(
|
||||
file,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
_client.sendImage(
|
||||
file,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// A message search.
|
||||
Future<SearchMessagesResponse> search({
|
||||
@@ -515,35 +521,32 @@ class Channel {
|
||||
Map<String, dynamic> messageFilters,
|
||||
List<SortOption> sort,
|
||||
PaginationParams paginationParams,
|
||||
}) {
|
||||
return _client.search(
|
||||
{
|
||||
'cid': {
|
||||
r'$in': [cid],
|
||||
}) =>
|
||||
_client.search(
|
||||
{
|
||||
'cid': {
|
||||
r'$in': [cid],
|
||||
},
|
||||
},
|
||||
},
|
||||
sort: sort,
|
||||
query: query,
|
||||
paginationParams: paginationParams,
|
||||
messageFilters: messageFilters,
|
||||
);
|
||||
}
|
||||
sort: sort,
|
||||
query: query,
|
||||
paginationParams: paginationParams,
|
||||
messageFilters: messageFilters,
|
||||
);
|
||||
|
||||
/// Delete a file from this channel
|
||||
Future<EmptyResponse> deleteFile(
|
||||
String url, {
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.deleteFile(url, id, type, cancelToken: cancelToken);
|
||||
}
|
||||
}) =>
|
||||
_client.deleteFile(url, id, type, cancelToken: cancelToken);
|
||||
|
||||
/// Delete an image from this channel
|
||||
Future<EmptyResponse> deleteImage(
|
||||
String url, {
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.deleteImage(url, id, type, cancelToken: cancelToken);
|
||||
}
|
||||
}) =>
|
||||
_client.deleteImage(url, id, type, cancelToken: cancelToken);
|
||||
|
||||
/// Send an event on this channel
|
||||
Future<EmptyResponse> sendEvent(Event event) {
|
||||
@@ -551,9 +554,7 @@ class Channel {
|
||||
return _client.post(
|
||||
'$_channelURL/event',
|
||||
data: {'event': event.toJson()},
|
||||
).then((res) {
|
||||
return _client.decode(res.data, EmptyResponse.fromJson);
|
||||
});
|
||||
).then((res) => _client.decode(res.data, EmptyResponse.fromJson));
|
||||
}
|
||||
|
||||
/// Send a reaction to this channel
|
||||
@@ -643,11 +644,10 @@ class Channel {
|
||||
}
|
||||
|
||||
final latestReactions = [...message.latestReactions ?? <Reaction>[]]
|
||||
..removeWhere((r) {
|
||||
return r.userId == reaction.userId &&
|
||||
r.type == reaction.type &&
|
||||
r.messageId == reaction.messageId;
|
||||
});
|
||||
..removeWhere((r) =>
|
||||
r.userId == reaction.userId &&
|
||||
r.type == reaction.type &&
|
||||
r.messageId == reaction.messageId);
|
||||
|
||||
final ownReactions = [...latestReactions ?? <Reaction>[]]
|
||||
..removeWhere((it) => it.userId != user.id);
|
||||
@@ -825,7 +825,7 @@ class Channel {
|
||||
})
|
||||
..addAll(options);
|
||||
|
||||
var response;
|
||||
ChannelState response;
|
||||
|
||||
try {
|
||||
response = await query(options: watchOptions);
|
||||
@@ -861,7 +861,8 @@ class Channel {
|
||||
}
|
||||
|
||||
/// List the message replies for a parent message
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already in the offline storage
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already
|
||||
/// in the offline storage
|
||||
Future<QueryRepliesResponse> getReplies(
|
||||
String parentId,
|
||||
PaginationParams options, {
|
||||
@@ -940,16 +941,15 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Creates a new channel
|
||||
Future<ChannelState> create() async {
|
||||
return query(options: {
|
||||
'watch': false,
|
||||
'state': false,
|
||||
'presence': false,
|
||||
});
|
||||
}
|
||||
Future<ChannelState> create() async => query(options: {
|
||||
'watch': false,
|
||||
'state': false,
|
||||
'presence': false,
|
||||
});
|
||||
|
||||
/// Query the API, get messages, members or other channel fields
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already in the offline storage
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already
|
||||
/// in the offline storage
|
||||
Future<ChannelState> query({
|
||||
Map<String, dynamic> options = const {},
|
||||
PaginationParams messagesPagination,
|
||||
@@ -1112,8 +1112,9 @@ class Channel {
|
||||
});
|
||||
}
|
||||
|
||||
/// Hides the channel from [StreamChatClient.queryChannels] for the user until a message is added
|
||||
/// If [clearHistory] is set to true - all messages will be removed for the user
|
||||
/// 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> hide({bool clearHistory = false}) async {
|
||||
_checkInitialized();
|
||||
final response = await _client
|
||||
@@ -1134,28 +1135,28 @@ class Channel {
|
||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Stream of [Event] coming from websocket connection specific for the channel
|
||||
/// Pass an eventType as parameter in order to filter just a type of event
|
||||
/// Stream of [Event] coming from websocket connection specific for the
|
||||
/// channel. Pass an eventType as parameter in order to filter just a type
|
||||
/// of event
|
||||
Stream<Event> on([
|
||||
String eventType,
|
||||
String eventType2,
|
||||
String eventType3,
|
||||
String eventType4,
|
||||
]) {
|
||||
return _client
|
||||
.on(
|
||||
eventType,
|
||||
eventType2,
|
||||
eventType3,
|
||||
eventType4,
|
||||
)
|
||||
.where((e) => e.cid == cid);
|
||||
}
|
||||
]) =>
|
||||
_client
|
||||
.on(
|
||||
eventType,
|
||||
eventType2,
|
||||
eventType3,
|
||||
eventType4,
|
||||
)
|
||||
.where((e) => e.cid == cid);
|
||||
|
||||
DateTime _lastTypingEvent;
|
||||
|
||||
/// First of the [EventType.typingStart] and [EventType.typingStop] events based on the users keystrokes.
|
||||
/// Call this on every keystroke.
|
||||
/// First of the [EventType.typingStart] and [EventType.typingStop] events
|
||||
/// based on the users keystrokes. Call this on every keystroke.
|
||||
Future<void> keyStroke([String parentId]) async {
|
||||
if (config?.typingEvents == false) {
|
||||
return;
|
||||
@@ -1196,17 +1197,21 @@ class Channel {
|
||||
void _checkInitialized() {
|
||||
if (!_initializedCompleter.isCompleted) {
|
||||
throw Exception(
|
||||
"Channel $cid hasn't been initialized yet. Make sure to call .watch() or to instantiate the client using [Channel.fromState]");
|
||||
"Channel $cid hasn't been initialized yet. Make sure to call .watch()"
|
||||
' or to instantiate the client using [Channel.fromState]');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The class that handles the state of the channel listening to the events
|
||||
class ChannelClientState {
|
||||
final _subscriptions = <StreamSubscription>[];
|
||||
|
||||
/// Creates a new instance listening to events and updating the state
|
||||
ChannelClientState(this._channel, ChannelState channelState) {
|
||||
ChannelClientState(
|
||||
this._channel,
|
||||
ChannelState channelState,
|
||||
) : _debouncedUpdatePersistenceChannelState = _channel
|
||||
?._client?.chatPersistenceClient?.updateChannelState
|
||||
?.debounced(const Duration(seconds: 1)) {
|
||||
retryQueue = RetryQueue(
|
||||
channel: _channel,
|
||||
logger: Logger('RETRY QUEUE ${_channel.cid}'),
|
||||
@@ -1252,14 +1257,16 @@ class ChannelClientState {
|
||||
_channel._client.chatPersistenceClient
|
||||
?.getChannelStateByCid(_channel.cid)
|
||||
?.then((state) {
|
||||
// Replacing the persistence state members with the latest `channelState.members`
|
||||
// as they may have changes over the time.
|
||||
// Replacing the persistence state members with the latest
|
||||
// `channelState.members` as they may have changes over the time.
|
||||
updateChannelState(state.copyWith(members: channelState.members));
|
||||
retryFailedMessages();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
final _subscriptions = <StreamSubscription>[];
|
||||
|
||||
void _computeInitialUnread() {
|
||||
final userRead = channelState?.read?.firstWhere(
|
||||
(r) => r.user.id == _channel._client.state?.user?.id,
|
||||
@@ -1277,11 +1284,16 @@ class ChannelClientState {
|
||||
m.attachments?.isNotEmpty == true &&
|
||||
m.attachments?.any((e) {
|
||||
final url = e.imageUrl ?? e.assetUrl;
|
||||
if (url == null || !url.contains('stream-io-cdn.com')) {
|
||||
if (url == null || !url.contains('')) {
|
||||
return false;
|
||||
}
|
||||
final uri = Uri.parse(url);
|
||||
if (uri.host != 'stream-io-cdn.com' ||
|
||||
uri.queryParameters['Expires'] == null) {
|
||||
return false;
|
||||
}
|
||||
final expiration =
|
||||
DateTime.parse(Uri.parse(url).queryParameters['Expires']);
|
||||
DateTime.parse(uri.queryParameters['Expires']);
|
||||
return expiration.isBefore(DateTime.now());
|
||||
}) ==
|
||||
true)
|
||||
@@ -1338,8 +1350,8 @@ class ChannelClientState {
|
||||
|
||||
/// Flag which indicates if [ChannelClientState] contain latest/recent messages or not.
|
||||
/// This flag should be managed by UI sdks.
|
||||
/// When false, any new message (received by WebSocket event - [EventType.messageNew]) will not
|
||||
/// be pushed on to message list.
|
||||
/// When false, any new message (received by WebSocket event
|
||||
/// - [EventType.messageNew]) will not be pushed on to message list.
|
||||
bool get isUpToDate => _isUpToDateController.value;
|
||||
|
||||
set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate);
|
||||
@@ -1357,12 +1369,18 @@ class ChannelClientState {
|
||||
Future<void> retryFailedMessages() async {
|
||||
final failedMessages =
|
||||
<Message>[...messages, ...threads.values.expand((v) => v)]
|
||||
.where((message) =>
|
||||
message.status != null &&
|
||||
message.status != MessageSendingStatus.sent &&
|
||||
message.createdAt.isBefore(DateTime.now().subtract(Duration(
|
||||
seconds: 1,
|
||||
))))
|
||||
.where(
|
||||
(message) =>
|
||||
message.status != null &&
|
||||
message.status != MessageSendingStatus.sent &&
|
||||
message.createdAt.isBefore(
|
||||
DateTime.now().subtract(
|
||||
const Duration(
|
||||
seconds: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
retryQueue.add(failedMessages);
|
||||
@@ -1471,27 +1489,32 @@ class ChannelClientState {
|
||||
return;
|
||||
}
|
||||
|
||||
_subscriptions.add(_channel
|
||||
.on(
|
||||
EventType.messageRead,
|
||||
EventType.notificationMarkRead,
|
||||
)
|
||||
.listen((event) {
|
||||
final readList = List<Read>.from(_channelState?.read ?? []);
|
||||
final userReadIndex = read?.indexWhere((r) => r.user.id == event.user.id);
|
||||
_subscriptions.add(
|
||||
_channel
|
||||
.on(
|
||||
EventType.messageRead,
|
||||
EventType.notificationMarkRead,
|
||||
)
|
||||
.listen(
|
||||
(event) {
|
||||
final readList = List<Read>.from(_channelState?.read ?? []);
|
||||
final userReadIndex =
|
||||
read?.indexWhere((r) => r.user.id == event.user.id);
|
||||
|
||||
if (userReadIndex != null && userReadIndex != -1) {
|
||||
final userRead = readList.removeAt(userReadIndex);
|
||||
if (userRead.user?.id == _channel._client.state.user.id) {
|
||||
_unreadCountController.add(0);
|
||||
}
|
||||
readList.add(Read(
|
||||
user: event.user,
|
||||
lastRead: event.createdAt,
|
||||
));
|
||||
_channelState = _channelState.copyWith(read: readList);
|
||||
}
|
||||
}));
|
||||
if (userReadIndex != null && userReadIndex != -1) {
|
||||
final userRead = readList.removeAt(userReadIndex);
|
||||
if (userRead.user?.id == _channel._client.state.user.id) {
|
||||
_unreadCountController.add(0);
|
||||
}
|
||||
readList.add(Read(
|
||||
user: event.user,
|
||||
lastRead: event.createdAt,
|
||||
));
|
||||
_channelState = _channelState.copyWith(read: readList);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Channel message list
|
||||
@@ -1527,11 +1550,8 @@ class ChannelClientState {
|
||||
List<Member>, Map<String, User>, List<Member>>(
|
||||
channelStateStream.map((cs) => cs.members),
|
||||
_channel.client.state.usersStream,
|
||||
(members, users) {
|
||||
return members
|
||||
.map((e) => e.copyWith(user: users[e.user.id]))
|
||||
.toList();
|
||||
},
|
||||
(members, users) =>
|
||||
members.map((e) => e.copyWith(user: users[e.user.id])).toList(),
|
||||
);
|
||||
|
||||
/// Channel watcher count
|
||||
@@ -1551,9 +1571,7 @@ class ChannelClientState {
|
||||
CombineLatestStream.combine2<List<User>, Map<String, User>, List<User>>(
|
||||
channelStateStream.map((cs) => cs.watchers),
|
||||
_channel.client.state.usersStream,
|
||||
(watchers, users) {
|
||||
return watchers.map((e) => users[e.id] ?? e).toList();
|
||||
},
|
||||
(watchers, users) => watchers.map((e) => users[e.id] ?? e).toList(),
|
||||
);
|
||||
|
||||
/// Channel read list
|
||||
@@ -1625,9 +1643,7 @@ class ChannelClientState {
|
||||
true)
|
||||
?.toList() ??
|
||||
[],
|
||||
];
|
||||
|
||||
newMessages.sort(_sortByCreatedAt);
|
||||
]..sort(_sortByCreatedAt);
|
||||
|
||||
final newWatchers = <User>[
|
||||
...updatedState?.watchers ?? [],
|
||||
@@ -1690,16 +1706,11 @@ class ChannelClientState {
|
||||
ChannelState get channelState => _channelStateController.value;
|
||||
BehaviorSubject<ChannelState> _channelStateController;
|
||||
|
||||
final Debounce _debouncedUpdatePersistenceChannelState;
|
||||
|
||||
set _channelState(ChannelState v) {
|
||||
_channelStateController.add(v);
|
||||
|
||||
if (_channel._client.persistenceEnabled) {
|
||||
debounce(
|
||||
timeout: Duration(milliseconds: 500),
|
||||
target: _channel._client.chatPersistenceClient?.updateChannelState,
|
||||
positionalArguments: [v],
|
||||
);
|
||||
}
|
||||
_debouncedUpdatePersistenceChannelState?.call([v]);
|
||||
}
|
||||
|
||||
/// The channel threads related to this channel
|
||||
@@ -1735,28 +1746,62 @@ class ChannelClientState {
|
||||
return;
|
||||
}
|
||||
|
||||
_subscriptions.add(_channel.on(EventType.typingStart).listen((event) {
|
||||
if (event.user.id != _channel.client.state.user.id) {
|
||||
_typings[event.user] = DateTime.now();
|
||||
_typingEventsController.add(_typings.keys.toList());
|
||||
}
|
||||
}));
|
||||
|
||||
_subscriptions.add(_channel.on(EventType.typingStop).listen((event) {
|
||||
if (event.user.id != _channel.client.state.user.id) {
|
||||
_typings.remove(event.user);
|
||||
_typingEventsController.add(_typings.keys.toList());
|
||||
}
|
||||
}));
|
||||
_subscriptions
|
||||
..add(
|
||||
_channel.on(EventType.typingStart).listen(
|
||||
(event) {
|
||||
if (event.user.id != _channel.client.state.user.id) {
|
||||
_typings[event.user] = DateTime.now();
|
||||
_typingEventsController.add(_typings.keys.toList());
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
..add(
|
||||
_channel.on(EventType.typingStop).listen(
|
||||
(event) {
|
||||
if (event.user.id != _channel.client.state.user.id) {
|
||||
_typings.remove(event.user);
|
||||
_typingEventsController.add(_typings.keys.toList());
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
..add(
|
||||
_channel
|
||||
.on()
|
||||
.where((event) =>
|
||||
event.user != null &&
|
||||
members?.any((m) => m.userId == event.user.id) == true)
|
||||
.listen(
|
||||
(event) {
|
||||
final newMembers = List<Member>.from(members);
|
||||
final oldMemberIndex =
|
||||
newMembers.indexWhere((m) => m.userId == event.user.id);
|
||||
if (oldMemberIndex > -1) {
|
||||
final oldMember = newMembers.removeAt(oldMemberIndex);
|
||||
updateChannelState(ChannelState(
|
||||
members: [
|
||||
...newMembers,
|
||||
oldMember.copyWith(
|
||||
user: event.user,
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Timer _cleaningTimer;
|
||||
|
||||
void _startCleaning() {
|
||||
if (_channel.config?.typingEvents == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
_cleaningTimer = Timer.periodic(Duration(seconds: 1), (_) {
|
||||
_cleaningTimer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
final now = DateTime.now();
|
||||
|
||||
if (_channel._lastTypingEvent != null &&
|
||||
@@ -1769,8 +1814,9 @@ class ChannelClientState {
|
||||
}
|
||||
|
||||
Timer _pinnedMessagesTimer;
|
||||
|
||||
void _startCleaningPinnedMessages() {
|
||||
_pinnedMessagesTimer = Timer.periodic(Duration(seconds: 30), (_) {
|
||||
_pinnedMessagesTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||
final now = DateTime.now();
|
||||
var expiredMessages = channelState.pinnedMessages
|
||||
?.where((m) => m.pinExpires?.isBefore(now) == true)
|
||||
@@ -1781,8 +1827,6 @@ class ChannelClientState {
|
||||
.map((m) => m.copyWith(
|
||||
pinExpires: null,
|
||||
pinned: false,
|
||||
pinnedAt: null,
|
||||
pinnedBy: null,
|
||||
))
|
||||
.toList();
|
||||
|
||||
@@ -1811,6 +1855,7 @@ class ChannelClientState {
|
||||
|
||||
/// Call this method to dispose this object
|
||||
void dispose() {
|
||||
_debouncedUpdatePersistenceChannelState?.cancel();
|
||||
_unreadCountController.close();
|
||||
retryQueue.dispose();
|
||||
_subscriptions.forEach((s) => s.cancel());
|
||||
|
||||
@@ -5,22 +5,6 @@ part 'requests.g.dart';
|
||||
/// Sorting options
|
||||
@JsonSerializable(createFactory: false)
|
||||
class SortOption<T> {
|
||||
/// Ascending order
|
||||
static const ASC = 1;
|
||||
|
||||
/// Descending order
|
||||
static const DESC = -1;
|
||||
|
||||
/// A sorting field name
|
||||
final String field;
|
||||
|
||||
/// A sorting direction
|
||||
final int direction;
|
||||
|
||||
/// Sorting field Comparator required for offline sorting
|
||||
@JsonKey(ignore: true)
|
||||
final Comparator<T> comparator;
|
||||
|
||||
/// Creates a new SortOption instance
|
||||
///
|
||||
/// For example:
|
||||
@@ -34,6 +18,24 @@ class SortOption<T> {
|
||||
this.comparator,
|
||||
});
|
||||
|
||||
/// Ascending order
|
||||
// ignore: constant_identifier_names
|
||||
static const ASC = 1;
|
||||
|
||||
/// Descending order
|
||||
// ignore: constant_identifier_names
|
||||
static const DESC = -1;
|
||||
|
||||
/// A sorting field name
|
||||
final String field;
|
||||
|
||||
/// A sorting direction
|
||||
final int direction;
|
||||
|
||||
/// Sorting field Comparator required for offline sorting
|
||||
@JsonKey(ignore: true)
|
||||
final Comparator<T> comparator;
|
||||
|
||||
/// Serialize model to json
|
||||
Map<String, dynamic> toJson() => _$SortOptionToJson(this);
|
||||
}
|
||||
@@ -41,6 +43,25 @@ class SortOption<T> {
|
||||
/// Pagination options.
|
||||
@JsonSerializable(createFactory: false, includeIfNull: false)
|
||||
class PaginationParams {
|
||||
/// Creates a new PaginationParams instance
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// // limit to 50
|
||||
/// final paginationParams = PaginationParams(limit: 50);
|
||||
///
|
||||
/// // limit to 50 with offset
|
||||
/// final paginationParams = PaginationParams(limit: 50, offset: 50);
|
||||
/// ```
|
||||
const PaginationParams({
|
||||
this.limit = 10,
|
||||
this.offset = 0,
|
||||
this.greaterThan,
|
||||
this.greaterThanOrEqual,
|
||||
this.lessThan,
|
||||
this.lessThanOrEqual,
|
||||
});
|
||||
|
||||
/// The amount of items requested from the APIs.
|
||||
final int limit;
|
||||
|
||||
@@ -63,25 +84,6 @@ class PaginationParams {
|
||||
@JsonKey(name: 'id_lte')
|
||||
final String lessThanOrEqual;
|
||||
|
||||
/// Creates a new PaginationParams instance
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// // limit to 50
|
||||
/// final paginationParams = PaginationParams(limit: 50);
|
||||
///
|
||||
/// // limit to 50 with offset
|
||||
/// final paginationParams = PaginationParams(limit: 50, offset: 50);
|
||||
/// ```
|
||||
const PaginationParams({
|
||||
this.limit = 10,
|
||||
this.offset = 0,
|
||||
this.greaterThan,
|
||||
this.greaterThanOrEqual,
|
||||
this.lessThan,
|
||||
this.lessThanOrEqual,
|
||||
});
|
||||
|
||||
/// Serialize model to json
|
||||
Map<String, dynamic> toJson() => _$PaginationParamsToJson(this);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'requests.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Map<String, dynamic> _$SortOptionToJson(SortOption instance) =>
|
||||
Map<String, dynamic> _$SortOptionToJson<T>(SortOption<T> instance) =>
|
||||
<String, dynamic>{
|
||||
'field': instance.field,
|
||||
'direction': instance.direction,
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/client.dart';
|
||||
import 'package:stream_chat/src/models/channel_model.dart';
|
||||
import 'package:stream_chat/src/models/channel_state.dart';
|
||||
import 'package:stream_chat/src/models/device.dart';
|
||||
import 'package:stream_chat/src/models/event.dart';
|
||||
|
||||
import '../models/channel_model.dart';
|
||||
import '../models/channel_state.dart';
|
||||
import '../models/member.dart';
|
||||
import '../models/message.dart';
|
||||
import '../models/reaction.dart';
|
||||
import '../models/read.dart';
|
||||
import '../models/user.dart';
|
||||
import 'package:stream_chat/src/models/member.dart';
|
||||
import 'package:stream_chat/src/models/message.dart';
|
||||
import 'package:stream_chat/src/models/reaction.dart';
|
||||
import 'package:stream_chat/src/models/read.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
part 'responses.g.dart';
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ class RetryPolicy {
|
||||
final bool Function(StreamChatClient client, int attempt, ApiError apiError)
|
||||
shouldRetry;
|
||||
|
||||
/// In the case that we want to retry a failed request the retryTimeout method is called to determine the timeout
|
||||
/// In the case that we want to retry a failed request the retryTimeout
|
||||
/// method is called to determine the timeout
|
||||
final Duration Function(
|
||||
StreamChatClient client, int attempt, ApiError apiError) retryTimeout;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:stream_chat/src/api/channel.dart';
|
||||
import 'package:stream_chat/src/api/retry_policy.dart';
|
||||
import 'package:stream_chat/src/event_type.dart';
|
||||
@@ -12,12 +12,6 @@ import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
/// The retry queue associated to a channel
|
||||
class RetryQueue {
|
||||
/// The channel of this queue
|
||||
final Channel channel;
|
||||
|
||||
/// The logger associated to this queue
|
||||
final Logger logger;
|
||||
|
||||
/// Instantiate a new RetryQueue object
|
||||
RetryQueue({
|
||||
@required this.channel,
|
||||
@@ -30,6 +24,12 @@ class RetryQueue {
|
||||
_listenFailedEvents();
|
||||
}
|
||||
|
||||
/// The channel of this queue
|
||||
final Channel channel;
|
||||
|
||||
/// The logger associated to this queue
|
||||
final Logger logger;
|
||||
|
||||
final _subscriptions = <StreamSubscription>[];
|
||||
|
||||
void _listenConnectionRecovered() {
|
||||
|
||||
@@ -2,28 +2,29 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
import '../models/event.dart';
|
||||
import '../models/user.dart';
|
||||
import 'connection_status.dart';
|
||||
import 'web_socket_channel_stub.dart'
|
||||
import 'package:stream_chat/src/api/connection_status.dart';
|
||||
import 'package:stream_chat/src/api/web_socket_channel_stub.dart'
|
||||
if (dart.library.html) 'web_socket_channel_html.dart'
|
||||
if (dart.library.io) 'web_socket_channel_io.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 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 original error
|
||||
// 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
|
||||
@@ -75,7 +76,8 @@ class WebSocket {
|
||||
/// WS connection payload
|
||||
final Map<String, dynamic> connectPayload;
|
||||
|
||||
/// Functions that will be called every time a new event is received from the connection
|
||||
/// Functions that will be called every time a new event is received from the
|
||||
/// connection
|
||||
final EventHandler handler;
|
||||
|
||||
/// A WS specific logger instance
|
||||
@@ -87,8 +89,9 @@ class WebSocket {
|
||||
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
|
||||
/// 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
|
||||
@@ -96,7 +99,8 @@ class WebSocket {
|
||||
/// 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
|
||||
/// The timeout that uses the reconnection monitor timer to consider the
|
||||
/// connection unhealthy
|
||||
final int reconnectionMonitorTimeout;
|
||||
|
||||
final _connectionStatusController =
|
||||
@@ -121,9 +125,7 @@ class WebSocket {
|
||||
_connecting = false,
|
||||
_reconnecting = false;
|
||||
|
||||
Event _decodeEvent(String source) {
|
||||
return Event.fromJson(json.decode(source));
|
||||
}
|
||||
Event _decodeEvent(String source) => Event.fromJson(json.decode(source));
|
||||
|
||||
Completer<Event> _connectionCompleter = Completer<Event>();
|
||||
|
||||
@@ -166,8 +168,8 @@ class WebSocket {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'connection closed | closeCode: ${_channel.closeCode} | closedReason: ${_channel.closeReason}');
|
||||
logger.info('connection closed | closeCode: ${_channel.closeCode} | '
|
||||
'closedReason: ${_channel.closeReason}');
|
||||
|
||||
if (!_reconnecting) {
|
||||
_reconnect();
|
||||
@@ -200,8 +202,7 @@ class WebSocket {
|
||||
}
|
||||
|
||||
Future<void> _onConnectionError(error, [stacktrace]) async {
|
||||
logger.severe('error connecting');
|
||||
logger.severe(error);
|
||||
logger..severe('error connecting')..severe(error);
|
||||
if (stacktrace != null) {
|
||||
logger.severe(stacktrace);
|
||||
}
|
||||
@@ -219,21 +220,21 @@ class WebSocket {
|
||||
}
|
||||
}
|
||||
|
||||
void _startReconnectionMonitor() {
|
||||
final reconnectionTimer = (_) {
|
||||
final now = DateTime.now();
|
||||
if (_lastEventAt != null &&
|
||||
now.difference(_lastEventAt).inSeconds > reconnectionMonitorTimeout) {
|
||||
_channel.sink.close();
|
||||
}
|
||||
};
|
||||
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,
|
||||
);
|
||||
|
||||
reconnectionTimer(_reconnectionMonitor);
|
||||
_reconnectionTimer(_reconnectionMonitor);
|
||||
}
|
||||
|
||||
void _reconnectTimer() async {
|
||||
@@ -283,20 +284,20 @@ class WebSocket {
|
||||
}
|
||||
}
|
||||
|
||||
void _healthCheckTimer(_) {
|
||||
logger.info('sending health.check');
|
||||
_channel.sink.add("{'type': 'health.check'}");
|
||||
}
|
||||
|
||||
void _startHealthCheck() {
|
||||
logger.info('start health check monitor');
|
||||
|
||||
final healthCheckTimer = (_) {
|
||||
logger.info('sending health.check');
|
||||
_channel.sink.add("{'type': 'health.check'}");
|
||||
};
|
||||
|
||||
_healthCheck = Timer.periodic(
|
||||
Duration(seconds: healthCheckInterval),
|
||||
healthCheckTimer,
|
||||
_healthCheckTimer,
|
||||
);
|
||||
|
||||
healthCheckTimer(_healthCheck);
|
||||
_healthCheckTimer(_healthCheck);
|
||||
}
|
||||
|
||||
/// Disconnects the WS and releases eventual resources
|
||||
|
||||
Reference in New Issue
Block a user