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]>
This commit is contained in:
Neevash Ramdial (Nash)
2021-03-03 09:44:48 +01:00
committed by GitHub
co-authored by Salvatore Giordano
parent e13ce930a6
commit c5252ab4d4
38 changed files with 1051 additions and 979 deletions
+128 -44
View File
@@ -1,63 +1,147 @@
include: package:pedantic/analysis_options.yaml
analyzer: analyzer:
exclude: exclude:
- lib/**/*.g.dart - lib/**/*.g.dart
- lib/**/*.freezed.dart - lib/**/*.freezed.dart
- example/* - example/*
- test/* - test/*
linter: linter:
rules: rules:
# these rules are documented on and in the same order as - always_use_package_imports
# the Dart Lint rules page to make maintenance easier
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
# - always_declare_return_types
# - always_specify_types
# - annotate_overrides
# - avoid_as
- avoid_empty_else - avoid_empty_else
- avoid_init_to_null - avoid_relative_lib_imports
- avoid_return_types_on_setters - avoid_slow_async_io
- avoid_web_libraries_in_flutter - avoid_types_as_parameter_names
- await_only_futures
- camel_case_types
- cancel_subscriptions - cancel_subscriptions
- close_sinks - close_sinks
# - comment_references # we do not presume as to what people want to reference in their dartdocs
# - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
- control_flow_in_finally - control_flow_in_finally
- empty_constructor_bodies - diagnostic_describe_all_properties
- empty_statements - empty_statements
- hash_and_equals - hash_and_equals
- implementation_imports - invariant_booleans
# - invariant_booleans - iterable_contains_unrelated_type
# - iterable_contains_unrelated_type - list_remove_unrelated_type
- library_names - literal_only_boolean_expressions
# - library_prefixes - no_adjacent_strings_in_list
# - list_remove_unrelated_type - no_duplicate_case_values
# - literal_only_boolean_expressions - no_logic_in_create_state
- non_constant_identifier_names - prefer_void_to_null
# - one_member_abstracts
# - only_throw_errors
# - overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
- prefer_is_not_empty
# - prefer_mixin # https://github.com/dart-lang/language/issues/32
- public_member_api_docs
- slash_for_doc_comments
# - sort_constructors_first
# - sort_unnamed_constructors_first
# - super_goes_last # no longer needed w/ Dart 2
- test_types_in_equals - test_types_in_equals
- throw_in_finally - throw_in_finally
# - type_annotate_public_apis # subset of always_specify_types
- type_init_formals
# - unawaited_futures
- unnecessary_brace_in_string_interps
- unnecessary_getters_setters
- unnecessary_statements - unnecessary_statements
- unrelated_type_equality_checks - unrelated_type_equality_checks
- omit_local_variable_types
- use_key_in_widget_constructors
- valid_regexps - valid_regexps
- always_declare_return_types
- always_put_required_named_parameters_first
- always_require_non_null_named_parameters
- annotate_overrides
- avoid_bool_literals_in_conditional_expressions
- avoid_catching_errors
- avoid_init_to_null
- avoid_null_checks_in_equality_operators
- avoid_positional_boolean_parameters
- avoid_private_typedef_functions
- avoid_redundant_argument_values
- avoid_return_types_on_setters
- avoid_returning_null
- avoid_returning_null_for_void
- avoid_shadowing_type_parameters
- avoid_single_cascade_in_expression_statements
- avoid_unnecessary_containers
- avoid_unused_constructor_parameters
- await_only_futures
- camel_case_extensions
- camel_case_types
- cascade_invocations
- constant_identifier_names
- curly_braces_in_flow_control_structures
- directives_ordering
- empty_catches
- empty_constructor_bodies
- exhaustive_cases
- file_names
- implementation_imports
- join_return_with_assignment
- leading_newlines_in_multiline_strings
- library_names
- library_prefixes
- lines_longer_than_80_chars
- missing_whitespace_between_adjacent_strings
- non_constant_identifier_names
- null_closures
- one_member_abstracts
- only_throw_errors
- package_api_docs
- package_prefixed_library_names
- parameter_assignments
- prefer_adjacent_string_concatenation
- prefer_asserts_in_initializer_lists
- prefer_asserts_with_message
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
- prefer_constructors_over_static_methods
- prefer_contains
- prefer_equal_for_default_values
- prefer_expression_function_bodies
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_function_declarations_over_variables
- prefer_generic_function_type_aliases
- prefer_if_elements_to_conditional_expressions
- prefer_if_null_operators
- prefer_initializing_formals
- prefer_inlined_adds
- prefer_int_literals
- prefer_interpolation_to_compose_strings
- prefer_is_empty
- prefer_is_not_empty
- prefer_is_not_operator
- prefer_null_aware_operators
- prefer_single_quotes
- prefer_spread_collections
- prefer_typing_uninitialized_variables
- provide_deprecation_message
- public_member_api_docs
- recursive_getters
- sized_box_for_whitespace
- slash_for_doc_comments
- sort_child_properties_last
- sort_constructors_first
- sort_unnamed_constructors_first
- type_annotate_public_apis
- type_init_formals
- unnecessary_await_in_return
- unnecessary_brace_in_string_interps
- unnecessary_const
- unnecessary_getters_setters
- unnecessary_lambdas
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_in_if_null_operators
- unnecessary_nullable_for_final_variable_declarations
- unnecessary_parenthesis
- unnecessary_raw_strings
- unnecessary_string_escapes
- unnecessary_string_interpolations
- unnecessary_this
- use_is_even_rather_than_modulo
- use_late_for_private_fields_and_variables
- use_rethrow_when_possible
- use_setters_to_change_properties
- use_to_and_as_if_applicable
- package_names
- sort_pub_dependencies
# To be added when null-safe:
# - cast_nullable_to_non_nullable
#- unnecessary_null_checks
# - tighten_type_of_initializing_formals
# - null_check_on_nullable_type_parameter
+171 -162
View File
@@ -2,9 +2,9 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:pedantic/pedantic.dart' show unawaited;
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:pedantic/pedantic.dart' show unawaited;
import 'package:rxdart/rxdart.dart'; 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/debounce.dart'; import 'package:stream_chat/src/debounce.dart';
@@ -14,13 +14,6 @@ import 'package:stream_chat/src/models/channel_state.dart';
import 'package:stream_chat/src/models/user.dart'; import 'package:stream_chat/src/models/user.dart';
import 'package:stream_chat/stream_chat.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';
/// This a the class that manages a specific channel. /// This a the class that manages a specific channel.
class Channel { class Channel {
/// Create a channel client instance. /// Create a channel client instance.
@@ -57,7 +50,8 @@ class Channel {
set extraData(Map<String, dynamic> extraData) { set extraData(Map<String, dynamic> extraData) {
if (_initializedCompleter.isCompleted) { if (_initializedCompleter.isCompleted) {
throw Exception( 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; _extraData = extraData;
} }
@@ -168,14 +162,15 @@ class Channel {
final Completer<bool> _initializedCompleter = Completer(); final Completer<bool> _initializedCompleter = Completer();
/// True if this is initialized /// 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; Future<bool> get initialized => _initializedCompleter.future;
final _cancelableAttachmentUploadRequest = <String, CancelToken>{}; final _cancelableAttachmentUploadRequest = <String, CancelToken>{};
final _messageAttachmentsUploadCompleter = <String, Completer>{}; final _messageAttachmentsUploadCompleter = <String, Completer>{};
/// Cancels [attachmentId] upload request. Throws exception if the request hasn't /// Cancels [attachmentId] upload request. Throws exception if the request
/// even started yet, Already completed or Already cancelled. /// hasn't even started yet, Already completed or Already cancelled.
/// ///
/// Optionally, provide a [reason] for the cancellation. /// Optionally, provide a [reason] for the cancellation.
void cancelAttachmentUpload( void cancelAttachmentUpload(
@@ -185,7 +180,8 @@ class Channel {
final cancelToken = _cancelableAttachmentUploadRequest[attachmentId]; final cancelToken = _cancelableAttachmentUploadRequest[attachmentId];
if (cancelToken == null) { if (cancelToken == null) {
throw Exception( 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'); if (cancelToken.isCancelled) throw Exception('Already cancelled');
@@ -193,15 +189,14 @@ class Channel {
} }
/// Retries the failed [attachmentId] upload request. /// Retries the failed [attachmentId] upload request.
Future<void> retryAttachmentUpload(String messageId, String attachmentId) { Future<void> retryAttachmentUpload(String messageId, String attachmentId) =>
return _uploadAttachments(messageId, [attachmentId]); _uploadAttachments(messageId, [attachmentId]);
}
Future<void> _uploadAttachments( Future<void> _uploadAttachments(
String messageId, String messageId,
Iterable<String> attachmentIds, Iterable<String> attachmentIds,
) { ) {
var message = state.messages.firstWhere( final message = state.messages.firstWhere(
(it) => it.id == messageId, (it) => it.id == messageId,
orElse: () => null, orElse: () => null,
); );
@@ -228,9 +223,8 @@ class Channel {
client.logger.info('Uploading ${it.id} attachment...'); client.logger.info('Uploading ${it.id} attachment...');
void updateAttachment(Attachment attachment) { void updateAttachment(Attachment attachment) {
final index = message.attachments.indexWhere((it) { final index =
return it.id == attachment.id; message.attachments.indexWhere((it) => it.id == attachment.id);
});
if (index != -1) { if (index != -1) {
message.attachments[index] = attachment; message.attachments[index] = attachment;
state?.addMessage(message); state?.addMessage(message);
@@ -239,7 +233,7 @@ class Channel {
void onSendProgress(int sent, int total) { void onSendProgress(int sent, int total) {
debounce( debounce(
timeout: Duration(seconds: 1), timeout: const Duration(seconds: 1),
target: updateAttachment, target: updateAttachment,
positionalArguments: [ positionalArguments: [
it.copyWith( it.copyWith(
@@ -270,11 +264,17 @@ class Channel {
client.logger.info('Attachment ${it.id} uploaded successfully...'); client.logger.info('Attachment ${it.id} uploaded successfully...');
if (isImage) { if (isImage) {
updateAttachment( updateAttachment(
it.copyWith(imageUrl: url, uploadState: UploadState.success()), it.copyWith(
imageUrl: url,
uploadState: const UploadState.success(),
),
); );
} else { } else {
updateAttachment( updateAttachment(
it.copyWith(assetUrl: url, uploadState: UploadState.success()), it.copyWith(
assetUrl: url,
uploadState: const UploadState.success(),
),
); );
} }
}).catchError((e, stk) { }).catchError((e, stk) {
@@ -305,6 +305,7 @@ class Channel {
(m) => m.id == message?.quotedMessageId, (m) => m.id == message?.quotedMessageId,
orElse: () => null, orElse: () => null,
); );
// ignore: parameter_assignments
message = message.copyWith( message = message.copyWith(
createdAt: message.createdAt ?? DateTime.now(), createdAt: message.createdAt ?? DateTime.now(),
user: _client.state.user, user: _client.state.user,
@@ -313,7 +314,7 @@ class Channel {
attachments: message.attachments?.map( attachments: message.attachments?.map(
(it) { (it) {
if (it.uploadState.isSuccess) return it; if (it.uploadState.isSuccess) return it;
return it.copyWith(uploadState: UploadState.preparing()); return it.copyWith(uploadState: const UploadState.preparing());
}, },
)?.toList(), )?.toList(),
); );
@@ -340,6 +341,7 @@ class Channel {
message.attachments.map((it) => it.id), message.attachments.map((it) => it.id),
)); ));
// ignore: parameter_assignments
message = await attachmentsUploadCompleter.future; message = await attachmentsUploadCompleter.future;
} }
@@ -364,13 +366,14 @@ class Channel {
.remove(message.id) .remove(message.id)
?.completeError('Message Cancelled'); ?.completeError('Message Cancelled');
// ignore: parameter_assignments
message = message.copyWith( message = message.copyWith(
status: MessageSendingStatus.updating, status: MessageSendingStatus.updating,
updatedAt: message.updatedAt ?? DateTime.now(), updatedAt: message.updatedAt ?? DateTime.now(),
attachments: message.attachments?.map( attachments: message.attachments?.map(
(it) { (it) {
if (it.uploadState.isSuccess) return it; if (it.uploadState.isSuccess) return it;
return it.copyWith(uploadState: UploadState.preparing()); return it.copyWith(uploadState: const UploadState.preparing());
}, },
)?.toList(), )?.toList(),
); );
@@ -388,6 +391,7 @@ class Channel {
message.attachments.map((it) => it.id), message.attachments.map((it) => it.id),
)); ));
// ignore: parameter_assignments
message = await attachmentsUploadCompleter.future; message = await attachmentsUploadCompleter.future;
} }
@@ -423,6 +427,7 @@ class Channel {
} }
try { try {
// ignore: parameter_assignments
message = message.copyWith( message = message.copyWith(
type: 'deleted', type: 'deleted',
status: MessageSendingStatus.deleting, status: MessageSendingStatus.deleting,
@@ -456,7 +461,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');
DateTime pinExpires; DateTime pinExpires;
if (timeoutOrExpirationDate is DateTime) { if (timeoutOrExpirationDate is DateTime) {
@@ -475,39 +480,36 @@ class Channel {
} }
/// Unpins provided message /// Unpins provided message
Future<UpdateMessageResponse> unpinMessage(Message message) { Future<UpdateMessageResponse> unpinMessage(Message message) =>
return updateMessage(message.copyWith(pinned: false)); updateMessage(message.copyWith(pinned: false));
}
/// Send a file to this channel /// Send a file to this channel
Future<SendFileResponse> sendFile( Future<SendFileResponse> sendFile(
AttachmentFile file, { AttachmentFile file, {
ProgressCallback onSendProgress, ProgressCallback onSendProgress,
CancelToken cancelToken, CancelToken cancelToken,
}) { }) =>
return _client.sendFile( _client.sendFile(
file, file,
id, id,
type, type,
onSendProgress: onSendProgress, onSendProgress: onSendProgress,
cancelToken: cancelToken, cancelToken: cancelToken,
); );
}
/// Send an image to this channel /// Send an image to this channel
Future<SendImageResponse> sendImage( Future<SendImageResponse> sendImage(
AttachmentFile file, { AttachmentFile file, {
ProgressCallback onSendProgress, ProgressCallback onSendProgress,
CancelToken cancelToken, CancelToken cancelToken,
}) { }) =>
return _client.sendImage( _client.sendImage(
file, file,
id, id,
type, type,
onSendProgress: onSendProgress, onSendProgress: onSendProgress,
cancelToken: cancelToken, cancelToken: cancelToken,
); );
}
/// A message search. /// A message search.
Future<SearchMessagesResponse> search({ Future<SearchMessagesResponse> search({
@@ -515,35 +517,32 @@ class Channel {
Map<String, dynamic> messageFilters, Map<String, dynamic> messageFilters,
List<SortOption> sort, List<SortOption> sort,
PaginationParams paginationParams, PaginationParams paginationParams,
}) { }) =>
return _client.search( _client.search(
{ {
'cid': { 'cid': {
r'$in': [cid], r'$in': [cid],
},
}, },
}, sort: sort,
sort: sort, query: query,
query: query, paginationParams: paginationParams,
paginationParams: paginationParams, messageFilters: messageFilters,
messageFilters: messageFilters, );
);
}
/// Delete a file from this channel /// Delete a file from this channel
Future<EmptyResponse> deleteFile( Future<EmptyResponse> deleteFile(
String url, { String url, {
CancelToken cancelToken, CancelToken cancelToken,
}) { }) =>
return _client.deleteFile(url, id, type, cancelToken: cancelToken); _client.deleteFile(url, id, type, cancelToken: cancelToken);
}
/// Delete an image from this channel /// Delete an image from this channel
Future<EmptyResponse> deleteImage( Future<EmptyResponse> deleteImage(
String url, { String url, {
CancelToken cancelToken, CancelToken cancelToken,
}) { }) =>
return _client.deleteImage(url, id, type, cancelToken: cancelToken); _client.deleteImage(url, id, type, cancelToken: cancelToken);
}
/// Send an event on this channel /// Send an event on this channel
Future<EmptyResponse> sendEvent(Event event) { Future<EmptyResponse> sendEvent(Event event) {
@@ -551,9 +550,7 @@ class Channel {
return _client.post( return _client.post(
'$_channelURL/event', '$_channelURL/event',
data: {'event': event.toJson()}, data: {'event': event.toJson()},
).then((res) { ).then((res) => _client.decode(res.data, EmptyResponse.fromJson));
return _client.decode(res.data, EmptyResponse.fromJson);
});
} }
/// Send a reaction to this channel /// Send a reaction to this channel
@@ -643,11 +640,10 @@ class Channel {
} }
final latestReactions = [...message.latestReactions ?? <Reaction>[]] final latestReactions = [...message.latestReactions ?? <Reaction>[]]
..removeWhere((r) { ..removeWhere((r) =>
return r.userId == reaction.userId && r.userId == reaction.userId &&
r.type == reaction.type && r.type == reaction.type &&
r.messageId == reaction.messageId; r.messageId == reaction.messageId);
});
final ownReactions = [...latestReactions ?? <Reaction>[]] final ownReactions = [...latestReactions ?? <Reaction>[]]
..removeWhere((it) => it.userId != user.id); ..removeWhere((it) => it.userId != user.id);
@@ -825,7 +821,7 @@ class Channel {
}) })
..addAll(options); ..addAll(options);
var response; ChannelState response;
try { try {
response = await query(options: watchOptions); response = await query(options: watchOptions);
@@ -861,7 +857,8 @@ class Channel {
} }
/// List the message replies for a parent message /// 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( Future<QueryRepliesResponse> getReplies(
String parentId, String parentId,
PaginationParams options, { PaginationParams options, {
@@ -940,16 +937,15 @@ class Channel {
} }
/// Creates a new channel /// Creates a new channel
Future<ChannelState> create() async { Future<ChannelState> create() async => query(options: {
return query(options: { 'watch': false,
'watch': false, 'state': false,
'state': false, 'presence': false,
'presence': false, });
});
}
/// Query the API, get messages, members or other channel fields /// 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({ Future<ChannelState> query({
Map<String, dynamic> options = const {}, Map<String, dynamic> options = const {},
PaginationParams messagesPagination, PaginationParams messagesPagination,
@@ -1112,8 +1108,9 @@ class Channel {
}); });
} }
/// Hides the channel from [StreamChatClient.queryChannels] for the user until a message is added /// Hides the channel from [StreamChatClient.queryChannels] for the user
/// If [clearHistory] is set to true - all messages will be removed 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 { Future<EmptyResponse> hide({bool clearHistory = false}) async {
_checkInitialized(); _checkInitialized();
final response = await _client final response = await _client
@@ -1134,28 +1131,28 @@ class Channel {
return _client.decode(response.data, EmptyResponse.fromJson); return _client.decode(response.data, EmptyResponse.fromJson);
} }
/// Stream of [Event] coming from websocket connection specific for the channel /// Stream of [Event] coming from websocket connection specific for the
/// Pass an eventType as parameter in order to filter just a type of event /// channel. Pass an eventType as parameter in order to filter just a type
/// of event
Stream<Event> on([ Stream<Event> on([
String eventType, String eventType,
String eventType2, String eventType2,
String eventType3, String eventType3,
String eventType4, String eventType4,
]) { ]) =>
return _client _client
.on( .on(
eventType, eventType,
eventType2, eventType2,
eventType3, eventType3,
eventType4, eventType4,
) )
.where((e) => e.cid == cid); .where((e) => e.cid == cid);
}
DateTime _lastTypingEvent; DateTime _lastTypingEvent;
/// First of the [EventType.typingStart] and [EventType.typingStop] events based on the users keystrokes. /// First of the [EventType.typingStart] and [EventType.typingStop] events
/// Call this on every keystroke. /// based on the users keystrokes. Call this on every keystroke.
Future<void> keyStroke([String parentId]) async { Future<void> keyStroke([String parentId]) async {
if (config?.typingEvents == false) { if (config?.typingEvents == false) {
return; return;
@@ -1196,15 +1193,14 @@ class Channel {
void _checkInitialized() { void _checkInitialized() {
if (!_initializedCompleter.isCompleted) { if (!_initializedCompleter.isCompleted) {
throw Exception( 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 /// The class that handles the state of the channel listening to the events
class ChannelClientState { class ChannelClientState {
final _subscriptions = <StreamSubscription>[];
/// Creates a new instance listening to events and updating the state /// Creates a new instance listening to events and updating the state
ChannelClientState(this._channel, ChannelState channelState) { ChannelClientState(this._channel, ChannelState channelState) {
retryQueue = RetryQueue( retryQueue = RetryQueue(
@@ -1252,14 +1248,16 @@ class ChannelClientState {
_channel._client.chatPersistenceClient _channel._client.chatPersistenceClient
?.getChannelStateByCid(_channel.cid) ?.getChannelStateByCid(_channel.cid)
?.then((state) { ?.then((state) {
// Replacing the persistence state members with the latest `channelState.members` // Replacing the persistence state members with the latest
// as they may have changes over the time. // `channelState.members` as they may have changes over the time.
updateChannelState(state.copyWith(members: channelState.members)); updateChannelState(state.copyWith(members: channelState.members));
retryFailedMessages(); retryFailedMessages();
}); });
}); });
} }
final _subscriptions = <StreamSubscription>[];
void _computeInitialUnread() { void _computeInitialUnread() {
final userRead = channelState?.read?.firstWhere( final userRead = channelState?.read?.firstWhere(
(r) => r.user.id == _channel._client.state?.user?.id, (r) => r.user.id == _channel._client.state?.user?.id,
@@ -1338,8 +1336,8 @@ class ChannelClientState {
/// Flag which indicates if [ChannelClientState] contain latest/recent messages or not. /// Flag which indicates if [ChannelClientState] contain latest/recent messages or not.
/// This flag should be managed by UI sdks. /// This flag should be managed by UI sdks.
/// When false, any new message (received by WebSocket event - [EventType.messageNew]) will not /// When false, any new message (received by WebSocket event
/// be pushed on to message list. /// - [EventType.messageNew]) will not be pushed on to message list.
bool get isUpToDate => _isUpToDateController.value; bool get isUpToDate => _isUpToDateController.value;
set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate); set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate);
@@ -1357,12 +1355,18 @@ class ChannelClientState {
Future<void> retryFailedMessages() async { Future<void> retryFailedMessages() async {
final failedMessages = final failedMessages =
<Message>[...messages, ...threads.values.expand((v) => v)] <Message>[...messages, ...threads.values.expand((v) => v)]
.where((message) => .where(
message.status != null && (message) =>
message.status != MessageSendingStatus.sent && message.status != null &&
message.createdAt.isBefore(DateTime.now().subtract(Duration( message.status != MessageSendingStatus.sent &&
seconds: 1, message.createdAt.isBefore(
)))) DateTime.now().subtract(
const Duration(
seconds: 1,
),
),
),
)
.toList(); .toList();
retryQueue.add(failedMessages); retryQueue.add(failedMessages);
@@ -1471,27 +1475,32 @@ class ChannelClientState {
return; return;
} }
_subscriptions.add(_channel _subscriptions.add(
.on( _channel
EventType.messageRead, .on(
EventType.notificationMarkRead, EventType.messageRead,
) EventType.notificationMarkRead,
.listen((event) { )
final readList = List<Read>.from(_channelState?.read ?? []); .listen(
final userReadIndex = read?.indexWhere((r) => r.user.id == event.user.id); (event) {
final readList = List<Read>.from(_channelState?.read ?? []);
final userReadIndex =
read?.indexWhere((r) => r.user.id == event.user.id);
if (userReadIndex != null && userReadIndex != -1) { if (userReadIndex != null && userReadIndex != -1) {
final userRead = readList.removeAt(userReadIndex); final userRead = readList.removeAt(userReadIndex);
if (userRead.user?.id == _channel._client.state.user.id) { if (userRead.user?.id == _channel._client.state.user.id) {
_unreadCountController.add(0); _unreadCountController.add(0);
} }
readList.add(Read( readList.add(Read(
user: event.user, user: event.user,
lastRead: event.createdAt, lastRead: event.createdAt,
)); ));
_channelState = _channelState.copyWith(read: readList); _channelState = _channelState.copyWith(read: readList);
} }
})); },
),
);
} }
/// Channel message list /// Channel message list
@@ -1527,11 +1536,8 @@ class ChannelClientState {
List<Member>, Map<String, User>, List<Member>>( List<Member>, Map<String, User>, List<Member>>(
channelStateStream.map((cs) => cs.members), channelStateStream.map((cs) => cs.members),
_channel.client.state.usersStream, _channel.client.state.usersStream,
(members, users) { (members, users) =>
return members members.map((e) => e.copyWith(user: users[e.user.id])).toList(),
.map((e) => e.copyWith(user: users[e.user.id]))
.toList();
},
); );
/// Channel watcher count /// Channel watcher count
@@ -1551,9 +1557,7 @@ class ChannelClientState {
CombineLatestStream.combine2<List<User>, Map<String, User>, List<User>>( CombineLatestStream.combine2<List<User>, Map<String, User>, List<User>>(
channelStateStream.map((cs) => cs.watchers), channelStateStream.map((cs) => cs.watchers),
_channel.client.state.usersStream, _channel.client.state.usersStream,
(watchers, users) { (watchers, users) => watchers.map((e) => users[e.id] ?? e).toList(),
return watchers.map((e) => users[e.id] ?? e).toList();
},
); );
/// Channel read list /// Channel read list
@@ -1625,9 +1629,7 @@ class ChannelClientState {
true) true)
?.toList() ?? ?.toList() ??
[], [],
]; ]..sort(_sortByCreatedAt);
newMessages.sort(_sortByCreatedAt);
final newWatchers = <User>[ final newWatchers = <User>[
...updatedState?.watchers ?? [], ...updatedState?.watchers ?? [],
@@ -1692,10 +1694,9 @@ class ChannelClientState {
set _channelState(ChannelState v) { set _channelState(ChannelState v) {
_channelStateController.add(v); _channelStateController.add(v);
if (_channel._client.persistenceEnabled) { if (_channel._client.persistenceEnabled) {
debounce( debounce(
timeout: Duration(milliseconds: 500), timeout: const Duration(milliseconds: 500),
target: _channel._client.chatPersistenceClient?.updateChannelState, target: _channel._client.chatPersistenceClient?.updateChannelState,
positionalArguments: [v], positionalArguments: [v],
); );
@@ -1735,28 +1736,37 @@ class ChannelClientState {
return; return;
} }
_subscriptions.add(_channel.on(EventType.typingStart).listen((event) { _subscriptions
if (event.user.id != _channel.client.state.user.id) { ..add(
_typings[event.user] = DateTime.now(); _channel.on(EventType.typingStart).listen(
_typingEventsController.add(_typings.keys.toList()); (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()); )
} ..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());
}
},
),
);
} }
Timer _cleaningTimer; Timer _cleaningTimer;
void _startCleaning() { void _startCleaning() {
if (_channel.config?.typingEvents == false) { if (_channel.config?.typingEvents == false) {
return; return;
} }
_cleaningTimer = Timer.periodic(Duration(seconds: 1), (_) { _cleaningTimer = Timer.periodic(const Duration(seconds: 1), (_) {
final now = DateTime.now(); final now = DateTime.now();
if (_channel._lastTypingEvent != null && if (_channel._lastTypingEvent != null &&
@@ -1769,8 +1779,9 @@ class ChannelClientState {
} }
Timer _pinnedMessagesTimer; Timer _pinnedMessagesTimer;
void _startCleaningPinnedMessages() { void _startCleaningPinnedMessages() {
_pinnedMessagesTimer = Timer.periodic(Duration(seconds: 30), (_) { _pinnedMessagesTimer = Timer.periodic(const Duration(seconds: 30), (_) {
final now = DateTime.now(); final now = DateTime.now();
var expiredMessages = channelState.pinnedMessages var expiredMessages = channelState.pinnedMessages
?.where((m) => m.pinExpires?.isBefore(now) == true) ?.where((m) => m.pinExpires?.isBefore(now) == true)
@@ -1781,8 +1792,6 @@ class ChannelClientState {
.map((m) => m.copyWith( .map((m) => m.copyWith(
pinExpires: null, pinExpires: null,
pinned: false, pinned: false,
pinnedAt: null,
pinnedBy: null,
)) ))
.toList(); .toList();
+37 -35
View File
@@ -5,22 +5,6 @@ part 'requests.g.dart';
/// Sorting options /// Sorting options
@JsonSerializable(createFactory: false) @JsonSerializable(createFactory: false)
class SortOption<T> { 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 /// Creates a new SortOption instance
/// ///
/// For example: /// For example:
@@ -34,6 +18,24 @@ class SortOption<T> {
this.comparator, 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 /// Serialize model to json
Map<String, dynamic> toJson() => _$SortOptionToJson(this); Map<String, dynamic> toJson() => _$SortOptionToJson(this);
} }
@@ -41,6 +43,25 @@ class SortOption<T> {
/// Pagination options. /// Pagination options.
@JsonSerializable(createFactory: false, includeIfNull: false) @JsonSerializable(createFactory: false, includeIfNull: false)
class PaginationParams { 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. /// The amount of items requested from the APIs.
final int limit; final int limit;
@@ -63,25 +84,6 @@ class PaginationParams {
@JsonKey(name: 'id_lte') @JsonKey(name: 'id_lte')
final String lessThanOrEqual; 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 /// Serialize model to json
Map<String, dynamic> toJson() => _$PaginationParamsToJson(this); Map<String, dynamic> toJson() => _$PaginationParamsToJson(this);
@@ -1,15 +1,14 @@
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/models/channel_state.dart';
import 'package:stream_chat/src/models/device.dart'; import 'package:stream_chat/src/models/device.dart';
import 'package:stream_chat/src/models/event.dart'; import 'package:stream_chat/src/models/event.dart';
import 'package:stream_chat/src/models/member.dart';
import '../models/channel_model.dart'; import 'package:stream_chat/src/models/message.dart';
import '../models/channel_state.dart'; import 'package:stream_chat/src/models/reaction.dart';
import '../models/member.dart'; import 'package:stream_chat/src/models/read.dart';
import '../models/message.dart'; import 'package:stream_chat/src/models/user.dart';
import '../models/reaction.dart';
import '../models/read.dart';
import '../models/user.dart';
part 'responses.g.dart'; part 'responses.g.dart';
@@ -18,7 +18,8 @@ class RetryPolicy {
final bool Function(StreamChatClient client, int attempt, ApiError apiError) final bool Function(StreamChatClient client, int attempt, ApiError apiError)
shouldRetry; 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( final Duration Function(
StreamChatClient client, int attempt, ApiError apiError) retryTimeout; StreamChatClient client, int attempt, ApiError apiError) retryTimeout;
@@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:logging/logging.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/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';
@@ -12,12 +12,6 @@ import 'package:stream_chat/stream_chat.dart';
/// The retry queue associated to a channel /// The retry queue associated to a channel
class RetryQueue { class RetryQueue {
/// The channel of this queue
final Channel channel;
/// The logger associated to this queue
final Logger logger;
/// Instantiate a new RetryQueue object /// Instantiate a new RetryQueue object
RetryQueue({ RetryQueue({
@required this.channel, @required this.channel,
@@ -30,6 +24,12 @@ class RetryQueue {
_listenFailedEvents(); _listenFailedEvents();
} }
/// The channel of this queue
final Channel channel;
/// The logger associated to this queue
final Logger logger;
final _subscriptions = <StreamSubscription>[]; final _subscriptions = <StreamSubscription>[];
void _listenConnectionRecovered() { void _listenConnectionRecovered() {
+39 -38
View File
@@ -2,28 +2,29 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:meta/meta.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:stream_chat/src/api/connection_status.dart';
import 'package:stream_chat/src/api/web_socket_channel_stub.dart'
import '../models/event.dart';
import '../models/user.dart';
import 'connection_status.dart';
import 'web_socket_channel_stub.dart'
if (dart.library.html) 'web_socket_channel_html.dart' if (dart.library.html) 'web_socket_channel_html.dart'
if (dart.library.io) 'web_socket_channel_io.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 which exposes an [Event] as the only parameter.
typedef EventHandler = void Function(Event); typedef EventHandler = void Function(Event);
/// Typedef used for connecting to a websocket. Method returns a [WebSocketChannel] /// Typedef used for connecting to a websocket. Method returns a
/// and accepts a connection [url] and an optional [Iterable] of `protocols`. /// [WebSocketChannel] and accepts a connection [url] and an optional
/// [Iterable] of `protocols`.
typedef ConnectWebSocket = WebSocketChannel Function(String url, typedef ConnectWebSocket = WebSocketChannel Function(String url,
{Iterable<String> protocols}); {Iterable<String> protocols});
// TODO: parse error even // 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. /// A WebSocket connection that reconnects upon failure.
class WebSocket { class WebSocket {
/// Creates a new websocket /// Creates a new websocket
@@ -75,7 +76,8 @@ class WebSocket {
/// WS connection payload /// WS connection payload
final Map<String, dynamic> connectPayload; 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; final EventHandler handler;
/// A WS specific logger instance /// A WS specific logger instance
@@ -87,8 +89,9 @@ class WebSocket {
final ConnectWebSocket connectFunc; final ConnectWebSocket connectFunc;
/// Interval of the reconnection monitor timer /// Interval of the reconnection monitor timer
/// This checks that it received a new event in the last [reconnectionMonitorTimeout] seconds, /// This checks that it received a new event in the last
/// otherwise it considers the connection unhealthy and reconnects the WS /// [reconnectionMonitorTimeout] seconds, otherwise it considers the
/// connection unhealthy and reconnects the WS
final int reconnectionMonitorInterval; final int reconnectionMonitorInterval;
/// Interval of the health event sending timer /// Interval of the health event sending timer
@@ -96,7 +99,8 @@ class WebSocket {
/// make the server aware that the client is still listening /// make the server aware that the client is still listening
final int healthCheckInterval; 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 int reconnectionMonitorTimeout;
final _connectionStatusController = final _connectionStatusController =
@@ -121,9 +125,7 @@ class WebSocket {
_connecting = false, _connecting = false,
_reconnecting = false; _reconnecting = false;
Event _decodeEvent(String source) { Event _decodeEvent(String source) => Event.fromJson(json.decode(source));
return Event.fromJson(json.decode(source));
}
Completer<Event> _connectionCompleter = Completer<Event>(); Completer<Event> _connectionCompleter = Completer<Event>();
@@ -166,8 +168,8 @@ class WebSocket {
return; return;
} }
logger.info( logger.info('connection closed | closeCode: ${_channel.closeCode} | '
'connection closed | closeCode: ${_channel.closeCode} | closedReason: ${_channel.closeReason}'); 'closedReason: ${_channel.closeReason}');
if (!_reconnecting) { if (!_reconnecting) {
_reconnect(); _reconnect();
@@ -200,8 +202,7 @@ class WebSocket {
} }
Future<void> _onConnectionError(error, [stacktrace]) async { Future<void> _onConnectionError(error, [stacktrace]) async {
logger.severe('error connecting'); logger..severe('error connecting')..severe(error);
logger.severe(error);
if (stacktrace != null) { if (stacktrace != null) {
logger.severe(stacktrace); logger.severe(stacktrace);
} }
@@ -219,21 +220,21 @@ class WebSocket {
} }
} }
void _startReconnectionMonitor() { void _reconnectionTimer(_) {
final reconnectionTimer = (_) { final now = DateTime.now();
final now = DateTime.now(); if (_lastEventAt != null &&
if (_lastEventAt != null && now.difference(_lastEventAt).inSeconds > reconnectionMonitorTimeout) {
now.difference(_lastEventAt).inSeconds > reconnectionMonitorTimeout) { _channel.sink.close();
_channel.sink.close(); }
} }
};
void _startReconnectionMonitor() {
_reconnectionMonitor = Timer.periodic( _reconnectionMonitor = Timer.periodic(
Duration(seconds: reconnectionMonitorInterval), Duration(seconds: reconnectionMonitorInterval),
reconnectionTimer, _reconnectionTimer,
); );
reconnectionTimer(_reconnectionMonitor); _reconnectionTimer(_reconnectionMonitor);
} }
void _reconnectTimer() async { void _reconnectTimer() async {
@@ -283,20 +284,20 @@ class WebSocket {
} }
} }
void _healthCheckTimer(_) {
logger.info('sending health.check');
_channel.sink.add("{'type': 'health.check'}");
}
void _startHealthCheck() { void _startHealthCheck() {
logger.info('start health check monitor'); logger.info('start health check monitor');
final healthCheckTimer = (_) {
logger.info('sending health.check');
_channel.sink.add("{'type': 'health.check'}");
};
_healthCheck = Timer.periodic( _healthCheck = Timer.periodic(
Duration(seconds: healthCheckInterval), Duration(seconds: healthCheckInterval),
healthCheckTimer, _healthCheckTimer,
); );
healthCheckTimer(_healthCheck); _healthCheckTimer(_healthCheck);
} }
/// Disconnects the WS and releases eventual resources /// Disconnects the WS and releases eventual resources
@@ -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/api/responses.dart';
import 'package:stream_chat/src/client.dart';
import 'package:stream_chat/src/models/attachment_file.dart'; import 'package:stream_chat/src/models/attachment_file.dart';
import 'client.dart'; import 'package:stream_chat/src/extensions/string_extension.dart';
import 'extensions/string_extension.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 {
@@ -57,11 +57,11 @@ abstract class AttachmentFileUploader {
/// Stream's default implementation of [AttachmentFileUploader] /// Stream's default implementation of [AttachmentFileUploader]
class StreamAttachmentFileUploader implements AttachmentFileUploader { class StreamAttachmentFileUploader implements AttachmentFileUploader {
final StreamChatClient _client;
/// Creates a new [StreamAttachmentFileUploader] instance. /// Creates a new [StreamAttachmentFileUploader] instance.
const StreamAttachmentFileUploader(this._client); const StreamAttachmentFileUploader(this._client);
final StreamChatClient _client;
@override @override
Future<SendImageResponse> sendImage( Future<SendImageResponse> sendImage(
AttachmentFile file, AttachmentFile file,
+142 -129
View File
@@ -1,36 +1,35 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:stream_chat/src/extensions/map_extension.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:pedantic/pedantic.dart' show unawaited; import 'package:pedantic/pedantic.dart' show unawaited;
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/src/api/channel.dart';
import 'package:stream_chat/src/api/connection_status.dart';
import 'package:stream_chat/src/api/requests.dart';
import 'package:stream_chat/src/api/responses.dart';
import 'package:stream_chat/src/api/retry_policy.dart'; import 'package:stream_chat/src/api/retry_policy.dart';
import 'package:stream_chat/src/api/websocket.dart';
import 'package:stream_chat/src/attachment_file_uploader.dart';
import 'package:stream_chat/src/db/chat_persistence_client.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/models/attachment_file.dart'; import 'package:stream_chat/src/models/attachment_file.dart';
import 'package:stream_chat/src/models/channel_model.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/event.dart';
import 'package:stream_chat/src/models/message.dart';
import 'package:stream_chat/src/models/own_user.dart'; import 'package:stream_chat/src/models/own_user.dart';
import 'package:stream_chat/src/models/user.dart';
import 'package:stream_chat/src/platform_detector/platform_detector.dart'; import 'package:stream_chat/src/platform_detector/platform_detector.dart';
import 'package:stream_chat/version.dart'; import 'package:stream_chat/version.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'attachment_file_uploader.dart'; /// Handler function used for logging records. Function requires a single
import 'api/channel.dart'; /// [LogRecord] as the only parameter.
import 'api/connection_status.dart';
import 'api/requests.dart';
import 'api/responses.dart';
import 'api/websocket.dart';
import 'db/chat_persistence_client.dart';
import 'exceptions.dart';
import 'models/channel_state.dart';
import 'models/event.dart';
import 'models/message.dart';
import 'models/user.dart';
import 'extensions/map_extension.dart';
/// Handler function used for logging records. Function requires a single [LogRecord]
/// as the only parameter.
typedef LogHandlerFunction = void Function(LogRecord record); typedef LogHandlerFunction = void Function(LogRecord record);
/// Used for decoding [Map] data to a generic type `T`. /// Used for decoding [Map] data to a generic type `T`.
@@ -62,7 +61,9 @@ extension on PushProvider {
/// The official Dart client for Stream Chat, /// The official Dart client for Stream Chat,
/// a service for building chat applications. /// a service for building chat applications.
/// This library can be used on any Dart project and on both mobile and web apps with Flutter. /// This library can be used on any Dart project and on both mobile and web apps
/// with Flutter.
///
/// You can sign up for a Stream account at https://getstream.io/chat/ /// You can sign up for a Stream account at https://getstream.io/chat/
/// ///
/// The Chat client will manage API call, event handling and manage the /// The Chat client will manage API call, event handling and manage the
@@ -73,7 +74,8 @@ extension on PushProvider {
/// ``` /// ```
class StreamChatClient { class StreamChatClient {
/// Create a client instance with default options. /// Create a client instance with default options.
/// You should only create the client once and re-use it across your application. /// You should only create the client once and re-use it across your
/// application.
StreamChatClient( StreamChatClient(
this.apiKey, { this.apiKey, {
this.tokenProvider, this.tokenProvider,
@@ -83,6 +85,7 @@ class StreamChatClient {
Duration connectTimeout = const Duration(seconds: 6), Duration connectTimeout = const Duration(seconds: 6),
Duration receiveTimeout = const Duration(seconds: 6), Duration receiveTimeout = const Duration(seconds: 6),
Dio httpClient, Dio httpClient,
// ignore: avoid_unused_constructor_parameters
RetryPolicy retryPolicy, RetryPolicy retryPolicy,
this.attachmentFileUploader, this.attachmentFileUploader,
}) { }) {
@@ -122,30 +125,39 @@ class StreamChatClient {
/// This client state /// This client state
ClientState state; ClientState state;
/// By default the Chat client will write all messages with level Warn or Error to stdout. /// By default the Chat client will write all messages with level Warn or
/// During development you might want to enable more logging information, you can change the default log level when constructing the client. /// Error to stdout.
///
/// During development you might want to enable more logging information,
/// you can change the default log level when constructing the client.
/// ///
/// ```dart /// ```dart
/// final client = StreamChatClient("stream-chat-api-key", logLevel: Level.INFO); /// final client = StreamChatClient("stream-chat-api-key",
/// logLevel: Level.INFO);
/// ``` /// ```
final Level logLevel; final Level logLevel;
/// Client specific logger instance. /// Client specific logger instance.
/// Refer to the class [Logger] to learn more about the specific implementation. /// Refer to the class [Logger] to learn more about the specific
/// implementation.
final Logger logger = Logger.detached('📡'); final Logger logger = Logger.detached('📡');
/// A function that has a parameter of type [LogRecord]. /// A function that has a parameter of type [LogRecord].
/// This is called on every new log record. /// This is called on every new log record.
/// By default the client will use the handler returned by [_getDefaultLogHandler]. /// By default the client will use the handler returned by
/// Setting it you can handle the log messages directly instead of have them written to stdout, /// [_getDefaultLogHandler].
/// this is very convenient if you use an error tracking tool or if you want to centralize your logs into one facility. /// Setting it you can handle the log messages directly instead of have them
/// written to stdout,
/// this is very convenient if you use an error tracking tool or if you want
/// to centralize your logs into one facility.
/// ///
/// ```dart /// ```dart
/// myLogHandlerFunction = (LogRecord record) { /// myLogHandlerFunction = (LogRecord record) {
/// // do something with the record (ie. send it to Sentry or Fabric) /// // do something with the record (ie. send it to Sentry or Fabric)
/// } /// }
/// ///
/// final client = StreamChatClient("stream-chat-api-key", logHandlerFunction: myLogHandlerFunction); /// final client = StreamChatClient("stream-chat-api-key",
/// logHandlerFunction: myLogHandlerFunction);
///``` ///```
LogHandlerFunction logHandlerFunction; LogHandlerFunction logHandlerFunction;
@@ -156,14 +168,18 @@ class StreamChatClient {
/// Your project Stream Chat base url. /// Your project Stream Chat base url.
final String baseURL; final String baseURL;
/// A function in which you send a request to your own backend to get a Stream Chat API token. /// A function in which you send a request to your own backend to get a Stream
/// Chat API token.
///
/// The token will be the return value of the function. /// The token will be the return value of the function.
/// It's used by the client to refresh the token once expired or to connect the user without a predefined token using [connectUserWithProvider]. /// It's used by the client to refresh the token once expired or to connect
/// the user without a predefined token using [connectUserWithProvider].
final TokenProvider tokenProvider; final TokenProvider tokenProvider;
/// [Dio] httpClient /// [Dio] httpClient
/// It's be chosen because it's easy to use and supports interesting features out of the box /// It's be chosen because it's easy to use and supports interesting features
/// (Interceptors, Global configuration, FormData, File downloading etc.) /// out of the box (Interceptors, Global configuration, FormData,
/// File downloading etc.)
@visibleForTesting @visibleForTesting
Dio httpClient = Dio(); Dio httpClient = Dio();
@@ -234,7 +250,7 @@ class StreamChatClient {
(options.data is Map || options.data == null)) { (options.data is Map || options.data == null)) {
options.data = { options.data = {
'connection_id': _connectionId, 'connection_id': _connectionId,
...(options.data ?? {}), ...options.data ?? {},
}; };
} }
@@ -278,7 +294,7 @@ class StreamChatClient {
await _disconnect(); await _disconnect();
final newToken = await tokenProvider(userId); final newToken = await tokenProvider(userId);
await Future.delayed(Duration(seconds: 4)); await Future.delayed(const Duration(seconds: 4));
token = newToken; token = newToken;
httpClient.unlock(); httpClient.unlock();
@@ -312,7 +328,10 @@ class StreamChatClient {
}; };
return (LogRecord record) { return (LogRecord record) {
print( print(
'(${record.time}) ${levelEmojiMapper[record.level.name] ?? record.level.name} ${record.loggerName} ${record.message}'); '(${record.time}) '
'${levelEmojiMapper[record.level.name] ?? record.level.name} '
'${record.loggerName} ${record.message}',
);
if (record.stackTrace != null) { if (record.stackTrace != null) {
print(record.stackTrace); print(record.stackTrace);
} }
@@ -321,11 +340,10 @@ class StreamChatClient {
Logger _detachedLogger( Logger _detachedLogger(
String name, String name,
) { ) =>
return Logger.detached(name) Logger.detached(name)
..level = logLevel ..level = logLevel
..onRecord.listen(logHandlerFunction ?? _getDefaultLogHandler()); ..onRecord.listen(logHandlerFunction ?? _getDefaultLogHandler());
}
void _setupLogger() { void _setupLogger() {
logger.level = logLevel; logger.level = logLevel;
@@ -386,7 +404,8 @@ class StreamChatClient {
/// Set the current user using the [tokenProvider] to fetch the token. /// Set the current user using the [tokenProvider] to fetch the token.
/// It returns a [Future] that resolves when the connection is setup. /// It returns a [Future] that resolves when the connection is setup.
@Deprecated( @Deprecated(
'Use `connectUserWithProvider` instead. Will be removed in Future releases') 'Use `connectUserWithProvider` instead. Will be removed in Future releases',
)
Future<Event> setUserWithProvider(User user) => connectUserWithProvider(user); Future<Event> setUserWithProvider(User user) => connectUserWithProvider(user);
/// Connects the current user using the [tokenProvider] to fetch the token. /// Connects the current user using the [tokenProvider] to fetch the token.
@@ -521,6 +540,7 @@ class StreamChatClient {
}).catchError((err, stacktrace) { }).catchError((err, stacktrace) {
logger.severe('error connecting ws', err, stacktrace); logger.severe('error connecting ws', err, stacktrace);
if (err is Map) { if (err is Map) {
// ignore: only_throw_errors
throw err; throw err;
} }
}); });
@@ -558,13 +578,12 @@ class StreamChatClient {
res.events.sort((a, b) => a.createdAt.compareTo(b.createdAt)); res.events.sort((a, b) => a.createdAt.compareTo(b.createdAt));
res.events.forEach((element) { res.events.forEach((element) {
logger.fine('element.type: ${element.type}'); logger
logger.fine('element.message.text: ${element.message?.text}'); ..fine('element.type: ${element.type}')
..fine('element.message.text: ${element.message?.text}');
}); });
res.events.forEach((event) { res.events.forEach(handleEvent);
handleEvent(event);
});
await chatPersistenceClient?.updateLastSyncAt(DateTime.now()); await chatPersistenceClient?.updateLastSyncAt(DateTime.now());
_synced = true; _synced = true;
@@ -573,9 +592,7 @@ class StreamChatClient {
} }
} }
String _asMap(sort) { String _asMap(sort) => sort?.map((s) => s.toJson().toString())?.join('');
return sort?.map((s) => s.toJson().toString())?.join('');
}
final _queryChannelsStreams = <String, Future<List<Channel>>>{}; final _queryChannelsStreams = <String, Future<List<Channel>>>{};
@@ -584,13 +601,14 @@ class StreamChatClient {
Map<String, dynamic> filter, Map<String, dynamic> filter,
List<SortOption<ChannelModel>> sort, List<SortOption<ChannelModel>> sort,
Map<String, dynamic> options, Map<String, dynamic> options,
PaginationParams paginationParams = const PaginationParams(limit: 10), PaginationParams paginationParams = const PaginationParams(),
int messageLimit, int messageLimit,
bool preferOffline = false, bool preferOffline = false,
bool waitForConnect = true, bool waitForConnect = true,
}) async* { }) async* {
final hash = base64.encode(utf8.encode( final hash = base64.encode(utf8.encode(
'$filter${_asMap(sort)}$options${paginationParams?.toJson()}$messageLimit$preferOffline', '$filter${_asMap(sort)}$options${paginationParams?.toJson()}'
'$messageLimit$preferOffline',
)); ));
if (_queryChannelsStreams.containsKey(hash)) { if (_queryChannelsStreams.containsKey(hash)) {
@@ -627,7 +645,7 @@ class StreamChatClient {
List<SortOption<ChannelModel>> sort, List<SortOption<ChannelModel>> sort,
Map<String, dynamic> options, Map<String, dynamic> options,
int messageLimit, int messageLimit,
PaginationParams paginationParams = const PaginationParams(limit: 10), PaginationParams paginationParams = const PaginationParams(),
bool waitForConnect = true, bool waitForConnect = true,
}) async { }) async {
if (waitForConnect) { if (waitForConnect) {
@@ -650,7 +668,7 @@ class StreamChatClient {
'presence': false, 'presence': false,
}; };
var payload = <String, dynamic>{ final payload = <String, dynamic>{
'filter_conditions': filter, 'filter_conditions': filter,
'sort': sort, 'sort': sort,
}; };
@@ -682,9 +700,12 @@ class StreamChatClient {
); );
if ((res.channels ?? []).isEmpty && (paginationParams?.offset ?? 0) == 0) { if ((res.channels ?? []).isEmpty && (paginationParams?.offset ?? 0) == 0) {
logger.warning('''We could not find any channel for this query. logger.warning(
'''
We could not find any channel for this query.
Please make sure to take a look at the Flutter tutorial: https://getstream.io/chat/flutter/tutorial Please make sure to take a look at the Flutter tutorial: https://getstream.io/chat/flutter/tutorial
If your application already has users and channels, you might need to adjust your query channel as explained in the docs https://getstream.io/chat/docs/query_channels/?language=dart'''); If your application already has users and channels, you might need to adjust your query channel as explained in the docs https://getstream.io/chat/docs/query_channels/?language=dart''',
);
return <Channel>[]; return <Channel>[];
} }
@@ -715,7 +736,7 @@ class StreamChatClient {
Future<List<Channel>> queryChannelsOffline({ Future<List<Channel>> queryChannelsOffline({
@required Map<String, dynamic> filter, @required Map<String, dynamic> filter,
@required List<SortOption<ChannelModel>> sort, @required List<SortOption<ChannelModel>> sort,
PaginationParams paginationParams = const PaginationParams(limit: 10), PaginationParams paginationParams = const PaginationParams(),
}) async { }) async {
final offlineChannels = await chatPersistenceClient?.getChannelStates( final offlineChannels = await chatPersistenceClient?.getChannelStates(
filter: filter, filter: filter,
@@ -771,6 +792,7 @@ class StreamChatClient {
); );
return response; return response;
} on DioError catch (error) { } on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -791,6 +813,7 @@ class StreamChatClient {
); );
return response; return response;
} on DioError catch (error) { } on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -809,6 +832,7 @@ class StreamChatClient {
); );
return response; return response;
} on DioError catch (error) { } on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -827,6 +851,7 @@ class StreamChatClient {
); );
return response; return response;
} on DioError catch (error) { } on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -845,6 +870,7 @@ class StreamChatClient {
); );
return response; return response;
} on DioError catch (error) { } on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -864,8 +890,8 @@ class StreamChatClient {
String get _authType => _anonymous ? 'anonymous' : 'jwt'; String get _authType => _anonymous ? 'anonymous' : 'jwt';
String get _userAgent => String get _userAgent => 'stream-chat-dart-client-${CurrentPlatform.name}-'
'stream-chat-dart-client-${CurrentPlatform.name}-${PACKAGE_VERSION.split('+')[0]}'; '${PACKAGE_VERSION.split('+')[0]}';
Map<String, String> get _commonQueryParams => { Map<String, String> get _commonQueryParams => {
'user_id': state.user?.id, 'user_id': state.user?.id,
@@ -873,14 +899,15 @@ class StreamChatClient {
'connection_id': _connectionId, 'connection_id': _connectionId,
}; };
/// Set the current user with an anonymous id, this triggers a connection to the API. /// Set the current user with an anonymous id, this triggers a connection to
/// It returns a [Future] that resolves when the connection is setup. /// the API. It returns a [Future] that resolves when the connection is setup.
@Deprecated( @Deprecated(
'Use `connectAnonymousUser` instead. Will be removed in Future releases') 'Use `connectAnonymousUser` instead. Will be removed in Future releases')
Future<Event> setAnonymousUser() => connectAnonymousUser(); Future<Event> setAnonymousUser() => connectAnonymousUser();
/// Connects the current user with an anonymous id, this triggers a connection to the API. /// Connects the current user with an anonymous id, this triggers a connection
/// It returns a [Future] that resolves when the connection is setup. /// to the API. It returns a [Future] that resolves when the connection is
/// setup.
Future<Event> connectAnonymousUser() async { Future<Event> connectAnonymousUser() async {
if (_connectCompleter != null && !_connectCompleter.isCompleted) { if (_connectCompleter != null && !_connectCompleter.isCompleted) {
logger.warning('Already connecting'); logger.warning('Already connecting');
@@ -923,14 +950,14 @@ class StreamChatClient {
} }
/// Closes the websocket connection and resets the client /// Closes the websocket connection and resets the client
/// If [flushChatPersistence] is true the client deletes all offline user's data /// If [flushChatPersistence] is true the client deletes all offline
/// If [clearUser] is true the client unsets the current user /// user's data. If [clearUser] is true the client unsets the current user
Future<void> disconnect({ Future<void> disconnect({
bool flushChatPersistence = false, bool flushChatPersistence = false,
bool clearUser = false, bool clearUser = false,
}) async { }) async {
logger.info( logger.info('Disconnecting flushOfflineStorage: $flushChatPersistence; '
'Disconnecting flushOfflineStorage: $flushChatPersistence; clearUser: $clearUser'); 'clearUser: $clearUser');
await chatPersistenceClient?.disconnect(flush: flushChatPersistence); await chatPersistenceClient?.disconnect(flush: flushChatPersistence);
chatPersistenceClient = null; chatPersistenceClient = null;
@@ -966,9 +993,7 @@ class StreamChatClient {
final payload = <String, dynamic>{ final payload = <String, dynamic>{
'filter_conditions': filter ?? {}, 'filter_conditions': filter ?? {},
'sort': sort, 'sort': sort,
}; }..addAll(defaultOptions);
payload.addAll(defaultOptions);
if (pagination != null) { if (pagination != null) {
payload.addAll(pagination.toJson()); payload.addAll(pagination.toJson());
@@ -1016,7 +1041,7 @@ class StreamChatClient {
); );
} }
return true; return true;
}()); }(), 'Check incoming params.');
final payload = { final payload = {
'filter_conditions': filters, 'filter_conditions': filters,
@@ -1041,15 +1066,14 @@ class StreamChatClient {
String channelType, { String channelType, {
ProgressCallback onSendProgress, ProgressCallback onSendProgress,
CancelToken cancelToken, CancelToken cancelToken,
}) { }) =>
return attachmentFileUploader.sendFile( attachmentFileUploader.sendFile(
file, file,
channelId, channelId,
channelType, channelType,
onSendProgress: onSendProgress, onSendProgress: onSendProgress,
cancelToken: cancelToken, cancelToken: cancelToken,
); );
}
/// Send a [image] to the [channelId] of type [channelType] /// Send a [image] to the [channelId] of type [channelType]
Future<SendImageResponse> sendImage( Future<SendImageResponse> sendImage(
@@ -1058,15 +1082,14 @@ class StreamChatClient {
String channelType, { String channelType, {
ProgressCallback onSendProgress, ProgressCallback onSendProgress,
CancelToken cancelToken, CancelToken cancelToken,
}) { }) =>
return attachmentFileUploader.sendImage( attachmentFileUploader.sendImage(
image, image,
channelId, channelId,
channelType, channelType,
onSendProgress: onSendProgress, onSendProgress: onSendProgress,
cancelToken: cancelToken, cancelToken: cancelToken,
); );
}
/// Delete a file from this channel /// Delete a file from this channel
Future<EmptyResponse> deleteFile( Future<EmptyResponse> deleteFile(
@@ -1074,14 +1097,13 @@ class StreamChatClient {
String channelId, String channelId,
String channelType, { String channelType, {
CancelToken cancelToken, CancelToken cancelToken,
}) { }) =>
return attachmentFileUploader.deleteFile( attachmentFileUploader.deleteFile(
url, url,
channelId, channelId,
channelType, channelType,
cancelToken: cancelToken, cancelToken: cancelToken,
); );
}
/// Delete an image from this channel /// Delete an image from this channel
Future<EmptyResponse> deleteImage( Future<EmptyResponse> deleteImage(
@@ -1089,14 +1111,13 @@ class StreamChatClient {
String channelId, String channelId,
String channelType, { String channelType, {
CancelToken cancelToken, CancelToken cancelToken,
}) { }) =>
return attachmentFileUploader.deleteImage( attachmentFileUploader.deleteImage(
url, url,
channelId, channelId,
channelType, channelType,
cancelToken: cancelToken, cancelToken: cancelToken,
); );
}
/// Add a device for Push Notifications. /// Add a device for Push Notifications.
Future<EmptyResponse> addDevice(String id, PushProvider pushProvider) async { Future<EmptyResponse> addDevice(String id, PushProvider pushProvider) async {
@@ -1146,9 +1167,8 @@ class StreamChatClient {
} }
/// Update or Create the given user object. /// Update or Create the given user object.
Future<UpdateUsersResponse> updateUser(User user) async { Future<UpdateUsersResponse> updateUser(User user) async =>
return updateUsers([user]); updateUsers([user]);
}
/// Batch update a list of users /// Batch update a list of users
Future<UpdateUsersResponse> updateUsers(List<User> users) async { Future<UpdateUsersResponse> updateUsers(List<User> users) async {
@@ -1197,23 +1217,21 @@ class StreamChatClient {
Future<EmptyResponse> shadowBan( Future<EmptyResponse> shadowBan(
String targetID, [ String targetID, [
Map<String, dynamic> options = const {}, Map<String, dynamic> options = const {},
]) async { ]) async =>
return banUser(targetID, { banUser(targetID, {
'shadow': true, 'shadow': true,
...options, ...options,
}); });
}
/// Removes shadow ban from a user /// Removes shadow ban from a user
Future<EmptyResponse> removeShadowBan( Future<EmptyResponse> removeShadowBan(
String targetID, [ String targetID, [
Map<String, dynamic> options = const {}, Map<String, dynamic> options = const {},
]) async { ]) async =>
return unbanUser(targetID, { unbanUser(targetID, {
'shadow': true, 'shadow': true,
...options, ...options,
}); });
}
/// Mutes a user /// Mutes a user
Future<EmptyResponse> muteUser(String targetID) async { Future<EmptyResponse> muteUser(String targetID) async {
@@ -1312,7 +1330,7 @@ class StreamChatClient {
throw ArgumentError('Invalid timeout or Expiration date'); throw ArgumentError('Invalid timeout or Expiration date');
} }
return true; return true;
}()); }(), 'Check whether time out is valid');
DateTime pinExpires; DateTime pinExpires;
if (timeoutOrExpirationDate is DateTime) { if (timeoutOrExpirationDate is DateTime) {
@@ -1328,15 +1346,12 @@ class StreamChatClient {
} }
/// Unpins provided message /// Unpins provided message
Future<UpdateMessageResponse> unpinMessage(Message message) { Future<UpdateMessageResponse> unpinMessage(Message message) =>
return updateMessage(message.copyWith(pinned: false)); updateMessage(message.copyWith(pinned: false));
}
} }
/// The class that handles the state of the channel listening to the events /// The class that handles the state of the channel listening to the events
class ClientState { class ClientState {
final _subscriptions = <StreamSubscription>[];
/// Creates a new instance listening to events and updating the state /// Creates a new instance listening to events and updating the state
ClientState(this._client) { ClientState(this._client) {
_subscriptions.addAll([ _subscriptions.addAll([
@@ -1358,16 +1373,12 @@ class ClientState {
.on() .on()
.where((event) => event.unreadChannels != null) .where((event) => event.unreadChannels != null)
.map((e) => e.unreadChannels) .map((e) => e.unreadChannels)
.listen((unreadChannels) { .listen(_unreadChannelsController.add),
_unreadChannelsController.add(unreadChannels);
}),
_client _client
.on() .on()
.where((event) => event.totalUnreadCount != null) .where((event) => event.totalUnreadCount != null)
.map((e) => e.totalUnreadCount) .map((e) => e.totalUnreadCount)
.listen((totalUnreadCount) { .listen(_totalUnreadCountController.add),
_totalUnreadCountController.add(totalUnreadCount);
}),
]); ]);
_listenChannelDeleted(); _listenChannelDeleted();
@@ -1377,6 +1388,8 @@ class ClientState {
_listenUserUpdated(); _listenUserUpdated();
} }
final _subscriptions = <StreamSubscription>[];
/// Used internally for optimistic update of unread count /// Used internally for optimistic update of unread count
set totalUnreadCount(int unreadCount) { set totalUnreadCount(int unreadCount) {
_totalUnreadCountController?.add(unreadCount ?? 0); _totalUnreadCountController?.add(unreadCount ?? 0);
@@ -101,18 +101,17 @@ abstract class ChatPersistenceClient {
Future<void> updateChannelQueries( Future<void> updateChannelQueries(
Map<String, dynamic> filter, Map<String, dynamic> filter,
List<String> cids, List<String> cids,
// ignore: avoid_positional_boolean_parameters
bool clearQueryCache, bool clearQueryCache,
); );
/// Remove a message by [messageId] /// Remove a message by [messageId]
Future<void> deleteMessageById(String messageId) { Future<void> deleteMessageById(String messageId) =>
return deleteMessageByIds([messageId]); deleteMessageByIds([messageId]);
}
/// Remove a pinned message by [messageId] /// Remove a pinned message by [messageId]
Future<void> deletePinnedMessageById(String messageId) { Future<void> deletePinnedMessageById(String messageId) =>
return deletePinnedMessageByIds([messageId]); deletePinnedMessageByIds([messageId]);
}
/// Remove a message by [messageIds] /// Remove a message by [messageIds]
Future<void> deleteMessageByIds(List<String> messageIds); Future<void> deleteMessageByIds(List<String> messageIds);
@@ -121,14 +120,11 @@ abstract class ChatPersistenceClient {
Future<void> deletePinnedMessageByIds(List<String> messageIds); Future<void> deletePinnedMessageByIds(List<String> messageIds);
/// Remove a message by channel [cid] /// Remove a message by channel [cid]
Future<void> deleteMessageByCid(String cid) { Future<void> deleteMessageByCid(String cid) => deleteMessageByCids([cid]);
return deleteMessageByCids([cid]);
}
/// Remove a pinned message by channel [cid] /// Remove a pinned message by channel [cid]
Future<void> deletePinnedMessageByCid(String cid) { Future<void> deletePinnedMessageByCid(String cid) async =>
return deletePinnedMessageByCids([cid]); deletePinnedMessageByCids([cid]);
}
/// Remove a message by message [cids] /// Remove a message by message [cids]
Future<void> deleteMessageByCids(List<String> cids); Future<void> deleteMessageByCids(List<String> cids);
@@ -175,9 +171,8 @@ abstract class ChatPersistenceClient {
Future<void> deleteMembersByCids(List<String> cids); Future<void> deleteMembersByCids(List<String> cids);
/// Update the channel state data using [channelState] /// Update the channel state data using [channelState]
Future<void> updateChannelState(ChannelState channelState) { Future<void> updateChannelState(ChannelState channelState) =>
return updateChannelStates([channelState]); updateChannelStates([channelState]);
}
/// Update list of channel states /// Update list of channel states
Future<void> updateChannelStates(List<ChannelState> channelStates) async { Future<void> updateChannelStates(List<ChannelState> channelStates) async {
@@ -195,31 +190,31 @@ abstract class ChatPersistenceClient {
deleteMembers, deleteMembers,
]); ]);
final channels = channelStates.map((it) { final channels =
return it.channel; channelStates.map((it) => it.channel).where((it) => it != null);
}).where((it) => it != null);
final reactions = channelStates.expand((it) => it.messages).expand((it) { final reactions = channelStates
return [ .expand((it) => it.messages)
if (it.ownReactions != null) .expand((it) => [
...it.ownReactions.where((r) => r.userId != null), if (it.ownReactions != null)
if (it.latestReactions != null) ...it.ownReactions.where((r) => r.userId != null),
...it.latestReactions.where((r) => r.userId != null) if (it.latestReactions != null)
]; ...it.latestReactions.where((r) => r.userId != null)
}).where((it) => it != null); ])
.where((it) => it != null);
final users = channelStates final users = channelStates
.map((cs) => [ .map((cs) => [
cs.channel?.createdBy, cs.channel?.createdBy,
...cs.messages?.map((m) { ...cs.messages
return [ ?.map((m) => [
m.user, m.user,
if (m.latestReactions != null) if (m.latestReactions != null)
...m.latestReactions.map((r) => r.user), ...m.latestReactions.map((r) => r.user),
if (m.ownReactions != null) if (m.ownReactions != null)
...m.ownReactions.map((r) => r.user), ...m.ownReactions.map((r) => r.user),
]; ])
})?.expand((v) => v), ?.expand((v) => v),
if (cs.read != null) ...cs.read.map((r) => r.user), if (cs.read != null) ...cs.read.map((r) => r.user),
if (cs.members != null) ...cs.members.map((m) => m.user), if (cs.members != null) ...cs.members.map((m) => m.user),
]) ])
+9 -10
View File
@@ -2,6 +2,13 @@ import 'dart:convert';
/// Exception related to api calls /// Exception related to api calls
class ApiError extends Error { class ApiError extends Error {
/// Creates a new ApiError instance using the response body and status code
ApiError(this.body, this.status) : jsonData = _decode(body) {
if (jsonData != null && jsonData.containsKey('code')) {
_code = jsonData['code'];
}
}
/// Raw body of the response /// Raw body of the response
final String body; final String body;
@@ -26,13 +33,6 @@ class ApiError extends Error {
} }
} }
/// Creates a new ApiError instance using the response body and status code
ApiError(this.body, this.status) : jsonData = _decode(body) {
if (jsonData != null && jsonData.containsKey('code')) {
_code = jsonData['code'];
}
}
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
@@ -48,7 +48,6 @@ class ApiError extends Error {
body.hashCode ^ jsonData.hashCode ^ status.hashCode ^ _code.hashCode; body.hashCode ^ jsonData.hashCode ^ status.hashCode ^ _code.hashCode;
@override @override
String toString() { String toString() => 'ApiError{body: $body, jsonData: $jsonData, '
return 'ApiError{body: $body, jsonData: $jsonData, status: $status, code: $_code}'; 'status: $status, code: $_code}';
}
} }
@@ -1,7 +1,6 @@
/// Useful extension functions for [Map] /// Useful extension functions for [Map]
extension MapX on Map { extension MapX on Map {
/// Returns a new map with null keys or values removed /// Returns a new map with null keys or values removed
Map<String, dynamic> get nullProtected { Map<String, dynamic> get nullProtected =>
return {...this}..removeWhere((key, value) => key == null || value == null); {...this}..removeWhere((key, value) => key == null || value == null);
}
} }
@@ -5,6 +5,12 @@ part 'action.g.dart';
/// The class that contains the information about an action /// The class that contains the information about an action
@JsonSerializable() @JsonSerializable()
class Action { class Action {
/// Constructor used for json serialization
Action({this.name, this.style, this.text, this.type, this.value});
/// Create a new instance from a json
factory Action.fromJson(Map<String, dynamic> json) => _$ActionFromJson(json);
/// The name of the action /// The name of the action
final String name; final String name;
@@ -20,12 +26,6 @@ class Action {
/// The value of the action /// The value of the action
final String value; final String value;
/// Constructor used for json serialization
Action({this.name, this.style, this.text, this.type, this.value});
/// Create a new instance from a json
factory Action.fromJson(Map<String, dynamic> json) => _$ActionFromJson(json);
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => _$ActionToJson(this); Map<String, dynamic> toJson() => _$ActionToJson(this);
} }
@@ -1,18 +1,61 @@
// ignore_for_file: public_member_api_docs // ignore_for_file: public_member_api_docs
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/models/attachment_file.dart'; import 'package:stream_chat/src/models/attachment_file.dart';
import 'package:stream_chat/src/models/serialization.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'action.dart';
import 'serialization.dart';
part 'attachment.g.dart'; part 'attachment.g.dart';
/// The class that contains the information about an attachment /// The class that contains the information about an attachment
@JsonSerializable(includeIfNull: false) @JsonSerializable(includeIfNull: false)
class Attachment { class Attachment {
///The attachment type based on the URL resource. This can be: audio, image or video /// Constructor used for json serialization
Attachment({
String id,
this.type,
this.titleLink,
String title,
this.thumbUrl,
this.text,
this.pretext,
this.ogScrapeUrl,
this.imageUrl,
this.footerIcon,
this.footer,
this.fields,
this.fallback,
this.color,
this.authorName,
this.authorLink,
this.authorIcon,
this.assetUrl,
this.actions,
this.extraData,
this.file,
UploadState uploadState,
}) : id = id ?? Uuid().v4(),
title = title ?? file?.name,
localUri = file?.path != null ? Uri.parse(file.path) : null {
this.uploadState = uploadState ??
((assetUrl != null || imageUrl != null)
? const UploadState.success()
: const UploadState.preparing());
}
/// Create a new instance from a json
factory Attachment.fromJson(Map<String, dynamic> json) =>
_$AttachmentFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
/// Create a new instance from a db data
factory Attachment.fromData(Map<String, dynamic> json) =>
_$AttachmentFromJson(Serialization.moveToExtraDataFromRoot(
json, topLevelFields + dbSpecificTopLevelFields));
///The attachment type based on the URL resource. This can be: audio,
///image or video
final String type; final String type;
///The link to which the attachment message points to. ///The link to which the attachment message points to.
@@ -21,10 +64,12 @@ class Attachment {
/// The attachment title /// The attachment title
final String title; final String title;
/// The URL to the attached file thumbnail. You can use this to represent the attached link. /// The URL to the attached file thumbnail. You can use this to represent the
/// attached link.
final String thumbUrl; final String thumbUrl;
/// The attachment text. It will be displayed in the channel next to the original message. /// The attachment text. It will be displayed in the channel next to the
/// original message.
final String text; final String text;
/// Optional text that appears above the attachment block /// Optional text that appears above the attachment block
@@ -33,7 +78,8 @@ class Attachment {
/// The original URL that was used to scrape this attachment. /// The original URL that was used to scrape this attachment.
final String ogScrapeUrl; final String ogScrapeUrl;
/// The URL to the attached image. This is present for URL pointing to an image article (eg. Unsplash) /// The URL to the attached image. This is present for URL pointing to an
/// image article (eg. Unsplash)
final String imageUrl; final String imageUrl;
final String footerIcon; final String footerIcon;
final String footer; final String footer;
@@ -100,56 +146,11 @@ class Attachment {
'file', 'file',
]; ];
/// Constructor used for json serialization
Attachment({
String id,
this.type,
this.titleLink,
String title,
this.thumbUrl,
this.text,
this.pretext,
this.ogScrapeUrl,
this.imageUrl,
this.footerIcon,
this.footer,
this.fields,
this.fallback,
this.color,
this.authorName,
this.authorLink,
this.authorIcon,
this.assetUrl,
this.actions,
this.extraData,
this.file,
UploadState uploadState,
}) : id = id ?? Uuid().v4(),
title = title ?? file?.name,
localUri = file?.path != null ? Uri.parse(file.path) : null {
this.uploadState = uploadState ??
((assetUrl != null || imageUrl != null)
? UploadState.success()
: UploadState.preparing());
}
/// Create a new instance from a json
factory Attachment.fromJson(Map<String, dynamic> json) {
return _$AttachmentFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
}
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot( Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
_$AttachmentToJson(this), topLevelFields) _$AttachmentToJson(this), topLevelFields)
..removeWhere((key, value) => dbSpecificTopLevelFields.contains(key)); ..removeWhere((key, value) => dbSpecificTopLevelFields.contains(key));
/// Create a new instance from a db data
factory Attachment.fromData(Map<String, dynamic> json) {
return _$AttachmentFromJson(Serialization.moveToExtraDataFromRoot(
json, topLevelFields + dbSpecificTopLevelFields));
}
/// Serialize to db data /// Serialize to db data
Map<String, dynamic> toData() => Serialization.moveFromExtraDataToRoot( Map<String, dynamic> toData() => Serialization.moveFromExtraDataToRoot(
_$AttachmentToJson(this), topLevelFields + dbSpecificTopLevelFields); _$AttachmentToJson(this), topLevelFields + dbSpecificTopLevelFields);
@@ -1,10 +1,9 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:meta/meta.dart';
part 'attachment_file.freezed.dart'; part 'attachment_file.freezed.dart';
part 'attachment_file.g.dart'; part 'attachment_file.g.dart';
/// Union class to hold various [UploadState] of a attachment. /// Union class to hold various [UploadState] of a attachment.
@@ -57,8 +56,12 @@ class AttachmentFile {
this.size, this.size,
}); });
/// The absolute path for a cached copy of this file. It can be used to create a /// Create a new instance from a json
/// file instance with a descriptor for the given path. factory AttachmentFile.fromJson(Map<String, dynamic> json) =>
_$AttachmentFileFromJson(json);
/// The absolute path for a cached copy of this file. It can be used to
/// create a file instance with a descriptor for the given path.
/// ``` /// ```
/// final File myFile = File(platformFile.path); /// final File myFile = File(platformFile.path);
/// ``` /// ```
@@ -67,8 +70,8 @@ class AttachmentFile {
/// File name including its extension. /// File name including its extension.
final String name; final String name;
/// Byte data for this file. Particularly useful if you want to manipulate its data /// Byte data for this file. Particularly useful if you want to manipulate
/// or easily upload to somewhere else. /// its data or easily upload to somewhere else.
@JsonKey(toJson: _toString, fromJson: _fromString) @JsonKey(toJson: _toString, fromJson: _fromString)
final Uint8List bytes; final Uint8List bytes;
@@ -78,11 +81,6 @@ class AttachmentFile {
/// File extension for this file. /// File extension for this file.
String get extension => name?.split('.')?.last; String get extension => name?.split('.')?.last;
/// Create a new instance from a json
factory AttachmentFile.fromJson(Map<String, dynamic> json) {
return _$AttachmentFileFromJson(json);
}
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => _$AttachmentFileToJson(this); Map<String, dynamic> toJson() => _$AttachmentFileToJson(this);
} }
@@ -1,12 +1,34 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/models/command.dart';
import 'command.dart';
part 'channel_config.g.dart'; part 'channel_config.g.dart';
/// The class that contains the information about the configuration of a channel /// The class that contains the information about the configuration of a channel
@JsonSerializable() @JsonSerializable()
class ChannelConfig { class ChannelConfig {
/// Constructor used for json serialization
ChannelConfig({
this.automod,
this.commands,
this.connectEvents,
this.createdAt,
this.updatedAt,
this.maxMessageLength,
this.messageRetention,
this.mutes,
this.name,
this.reactions,
this.readEvents,
this.replies,
this.search,
this.typingEvents,
this.uploads,
this.urlEnrichment,
});
/// Create a new instance from a json
factory ChannelConfig.fromJson(Map<String, dynamic> json) =>
_$ChannelConfigFromJson(json);
/// Moderation configuration /// Moderation configuration
final String automod; final String automod;
@@ -55,30 +77,6 @@ class ChannelConfig {
/// True if urls appears as attachments /// True if urls appears as attachments
final bool urlEnrichment; final bool urlEnrichment;
/// Constructor used for json serialization
ChannelConfig({
this.automod,
this.commands,
this.connectEvents,
this.createdAt,
this.updatedAt,
this.maxMessageLength,
this.messageRetention,
this.mutes,
this.name,
this.reactions,
this.readEvents,
this.replies,
this.search,
this.typingEvents,
this.uploads,
this.urlEnrichment,
});
/// Create a new instance from a json
factory ChannelConfig.fromJson(Map<String, dynamic> json) =>
_$ChannelConfigFromJson(json);
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => _$ChannelConfigToJson(this); Map<String, dynamic> toJson() => _$ChannelConfigToJson(this);
} }
@@ -1,14 +1,35 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/models/channel_config.dart';
import 'channel_config.dart'; import 'package:stream_chat/src/models/serialization.dart';
import 'serialization.dart'; import 'package:stream_chat/src/models/user.dart';
import 'user.dart';
part 'channel_model.g.dart'; part 'channel_model.g.dart';
/// The class that contains the information about a channel /// The class that contains the information about a channel
@JsonSerializable() @JsonSerializable()
class ChannelModel { class ChannelModel {
/// Constructor used for json serialization
ChannelModel({
this.id,
this.type,
this.cid,
this.config,
this.createdBy,
this.frozen,
this.lastMessageAt,
this.createdAt,
this.updatedAt,
this.deletedAt,
this.memberCount,
this.extraData,
this.team,
});
/// Create a new instance from a json
factory ChannelModel.fromJson(Map<String, dynamic> json) =>
_$ChannelModelFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
/// The id of this channel /// The id of this channel
final String id; final String id;
@@ -76,40 +97,15 @@ class ChannelModel {
'team', 'team',
]; ];
/// Constructor used for json serialization
ChannelModel({
this.id,
this.type,
this.cid,
this.config,
this.createdBy,
this.frozen,
this.lastMessageAt,
this.createdAt,
this.updatedAt,
this.deletedAt,
this.memberCount,
this.extraData,
this.team,
});
/// Shortcut for channel name /// Shortcut for channel name
String get name => String get name =>
extraData?.containsKey('name') == true ? extraData['name'] : cid; extraData?.containsKey('name') == true ? extraData['name'] : cid;
/// Create a new instance from a json
factory ChannelModel.fromJson(Map<String, dynamic> json) {
return _$ChannelModelFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
}
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
return Serialization.moveFromExtraDataToRoot( _$ChannelModelToJson(this),
_$ChannelModelToJson(this), topLevelFields,
topLevelFields, );
);
}
/// Creates a copy of [ChannelModel] with specified attributes overridden. /// Creates a copy of [ChannelModel] with specified attributes overridden.
ChannelModel copyWith({ ChannelModel copyWith({
@@ -143,8 +139,8 @@ class ChannelModel {
team: team ?? this.team, team: team ?? this.team,
); );
/// Returns a new [ChannelModel] that is a combination of this channelModel and the given /// Returns a new [ChannelModel] that is a combination of this channelModel
/// [other] channelModel. /// and the given [other] channelModel.
ChannelModel merge(ChannelModel other) { ChannelModel merge(ChannelModel other) {
if (other == null) return this; if (other == null) return this;
return copyWith( return copyWith(
@@ -1,16 +1,26 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/models/channel_model.dart';
import '../models/read.dart'; import 'package:stream_chat/src/models/member.dart';
import '../models/user.dart'; import 'package:stream_chat/src/models/message.dart';
import 'channel_model.dart'; import 'package:stream_chat/src/models/read.dart';
import 'member.dart'; import 'package:stream_chat/src/models/user.dart';
import 'message.dart';
part 'channel_state.g.dart'; part 'channel_state.g.dart';
/// The class that contains the information about a channel /// The class that contains the information about a channel
@JsonSerializable() @JsonSerializable()
class ChannelState { class ChannelState {
/// Constructor used for json serialization
ChannelState({
this.channel,
this.messages = const [],
this.members = const [],
this.pinnedMessages = const [],
this.watcherCount,
this.watchers = const [],
this.read = const [],
});
/// The channel to which this state belongs /// The channel to which this state belongs
final ChannelModel channel; final ChannelModel channel;
@@ -32,17 +42,6 @@ class ChannelState {
/// The list of channel reads /// The list of channel reads
final List<Read> read; final List<Read> read;
/// Constructor used for json serialization
ChannelState({
this.channel,
this.messages = const [],
this.members = const [],
this.pinnedMessages = const [],
this.watcherCount,
this.watchers = const [],
this.read = const [],
});
/// Create a new instance from a json /// Create a new instance from a json
static ChannelState fromJson(Map<String, dynamic> json) => static ChannelState fromJson(Map<String, dynamic> json) =>
_$ChannelStateFromJson(json); _$ChannelStateFromJson(json);
@@ -5,15 +5,6 @@ part 'command.g.dart';
/// The class that contains the information about a command /// The class that contains the information about a command
@JsonSerializable() @JsonSerializable()
class Command { class Command {
/// The name of the command
final String name;
/// The description explaining the command
final String description;
/// The arguments of the command
final String args;
/// Constructor used for json serialization /// Constructor used for json serialization
Command({ Command({
this.name, this.name,
@@ -25,6 +16,15 @@ class Command {
factory Command.fromJson(Map<String, dynamic> json) => factory Command.fromJson(Map<String, dynamic> json) =>
_$CommandFromJson(json); _$CommandFromJson(json);
/// The name of the command
final String name;
/// The description explaining the command
final String description;
/// The arguments of the command
final String args;
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => _$CommandToJson(this); Map<String, dynamic> toJson() => _$CommandToJson(this);
} }
@@ -5,12 +5,6 @@ part 'device.g.dart';
/// The class that contains the information about a device /// The class that contains the information about a device
@JsonSerializable() @JsonSerializable()
class Device { class Device {
/// The id of the device
final String id;
/// The notification push provider
final String pushProvider;
/// Constructor used for json serialization /// Constructor used for json serialization
Device({ Device({
this.id, this.id,
@@ -20,6 +14,12 @@ class Device {
/// Create a new instance from a json /// Create a new instance from a json
factory Device.fromJson(Map<String, dynamic> json) => _$DeviceFromJson(json); factory Device.fromJson(Map<String, dynamic> json) => _$DeviceFromJson(json);
/// The id of the device
final String id;
/// The notification push provider
final String pushProvider;
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => _$DeviceToJson(this); Map<String, dynamic> toJson() => _$DeviceToJson(this);
} }
+44 -52
View File
@@ -4,17 +4,40 @@ import 'package:stream_chat/src/models/message.dart';
import 'package:stream_chat/src/models/serialization.dart'; import 'package:stream_chat/src/models/serialization.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import '../event_type.dart';
import 'member.dart';
import 'own_user.dart';
import 'reaction.dart';
import 'user.dart';
part 'event.g.dart'; part 'event.g.dart';
/// The class that contains the information about an event /// The class that contains the information about an event
@JsonSerializable() @JsonSerializable()
class Event { class Event {
/// Constructor used for json serialization
Event({
this.type,
this.cid,
this.connectionId,
this.createdAt,
this.me,
this.user,
this.message,
this.totalUnreadCount,
this.unreadChannels,
this.reaction,
this.online,
this.channel,
this.member,
this.channelId,
this.channelType,
this.parentId,
this.extraData,
}) : isLocal = true;
/// Create a new instance from a json
factory Event.fromJson(Map<String, dynamic> json) =>
_$EventFromJson(Serialization.moveToExtraDataFromRoot(
json,
topLevelFields,
))
..isLocal = false;
/// 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;
@@ -71,27 +94,6 @@ class Event {
@JsonKey(includeIfNull: false) @JsonKey(includeIfNull: false)
final Map<String, dynamic> extraData; final Map<String, dynamic> extraData;
/// Constructor used for json serialization
Event({
this.type,
this.cid,
this.connectionId,
this.createdAt,
this.me,
this.user,
this.message,
this.totalUnreadCount,
this.unreadChannels,
this.reaction,
this.online,
this.channel,
this.member,
this.channelId,
this.channelType,
this.parentId,
this.extraData,
}) : isLocal = true;
/// Known top level fields. /// Known top level fields.
/// Useful for [Serialization] methods. /// Useful for [Serialization] methods.
static final topLevelFields = [ static final topLevelFields = [
@@ -114,15 +116,6 @@ class Event {
'is_local', 'is_local',
]; ];
/// Create a new instance from a json
factory Event.fromJson(Map<String, dynamic> json) {
return _$EventFromJson(Serialization.moveToExtraDataFromRoot(
json,
topLevelFields,
))
..isLocal = false;
}
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot( Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
_$EventToJson(this), _$EventToJson(this),
@@ -133,16 +126,6 @@ class Event {
/// The channel embedded in the event object /// The channel embedded in the event object
@JsonSerializable() @JsonSerializable()
class EventChannel extends ChannelModel { class EventChannel extends ChannelModel {
/// A paginated list of channel members
final List<Member> members;
/// Known top level fields.
/// Useful for [Serialization] methods.
static final topLevelFields = [
'members',
...ChannelModel.topLevelFields,
];
/// Constructor used for json serialization /// Constructor used for json serialization
EventChannel({ EventChannel({
this.members, this.members,
@@ -174,12 +157,21 @@ class EventChannel extends ChannelModel {
); );
/// Create a new instance from a json /// Create a new instance from a json
factory EventChannel.fromJson(Map<String, dynamic> json) { factory EventChannel.fromJson(Map<String, dynamic> json) =>
return _$EventChannelFromJson(Serialization.moveToExtraDataFromRoot( _$EventChannelFromJson(Serialization.moveToExtraDataFromRoot(
json, json,
topLevelFields, topLevelFields,
)); ));
}
/// A paginated list of channel members
final List<Member> members;
/// Known top level fields.
/// Useful for [Serialization] methods.
static final topLevelFields = [
'members',
...ChannelModel.topLevelFields,
];
/// Serialize to json /// Serialize to json
@override @override
+26 -26
View File
@@ -1,12 +1,35 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/models/user.dart';
import '../models/user.dart';
part 'member.g.dart'; part 'member.g.dart';
/// The class that contains the information about the user membership in a channel /// The class that contains the information about the user membership
/// in a channel
@JsonSerializable() @JsonSerializable()
class Member { class Member {
/// Constructor used for json serialization
Member({
this.user,
this.inviteAcceptedAt,
this.inviteRejectedAt,
this.invited,
this.role,
this.userId,
this.isModerator,
this.createdAt,
this.updatedAt,
this.banned,
this.shadowBanned,
});
/// Create a new instance from a json
factory Member.fromJson(Map<String, dynamic> json) {
final member = _$MemberFromJson(json);
return member.copyWith(
userId: member.user?.id,
);
}
/// The interested user /// The interested user
final User user; final User user;
@@ -40,29 +63,6 @@ class Member {
/// The last date of update /// The last date of update
final DateTime updatedAt; final DateTime updatedAt;
/// Constructor used for json serialization
Member({
this.user,
this.inviteAcceptedAt,
this.inviteRejectedAt,
this.invited,
this.role,
this.userId,
this.isModerator,
this.createdAt,
this.updatedAt,
this.banned,
this.shadowBanned,
});
/// Create a new instance from a json
factory Member.fromJson(Map<String, dynamic> json) {
final member = _$MemberFromJson(json);
return member.copyWith(
userId: member.user?.id,
);
}
/// Creates a copy of [Member] with specified attributes overridden. /// Creates a copy of [Member] with specified attributes overridden.
Member copyWith({ Member copyWith({
User user, User user,
@@ -1,11 +1,10 @@
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/models/reaction.dart';
import 'package:stream_chat/src/models/serialization.dart';
import 'package:stream_chat/src/models/user.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'attachment.dart';
import 'reaction.dart';
import 'serialization.dart';
import 'user.dart';
part 'message.g.dart'; part 'message.g.dart';
class _PinExpires { class _PinExpires {
@@ -29,9 +28,11 @@ enum MessageSendingStatus {
failed, failed,
/// Message failed to updated /// Message failed to updated
// ignore: constant_identifier_names
failed_update, failed_update,
/// Message failed to delete /// Message failed to delete
// ignore: constant_identifier_names
failed_delete, failed_delete,
/// Message correctly sent /// Message correctly sent
@@ -41,7 +42,45 @@ enum MessageSendingStatus {
/// The class that contains the information about a message /// The class that contains the information about a message
@JsonSerializable() @JsonSerializable()
class Message { class Message {
/// The message ID. This is either created by Stream or set client side when the message is added. /// Constructor used for json serialization
Message({
String id,
this.text,
this.type,
this.attachments,
this.mentionedUsers,
this.silent,
this.shadowed,
this.reactionCounts,
this.reactionScores,
this.latestReactions,
this.ownReactions,
this.parentId,
this.quotedMessage,
this.quotedMessageId,
this.replyCount = 0,
this.threadParticipants,
this.showInChannel,
this.command,
this.createdAt,
this.updatedAt,
this.user,
this.pinned = false,
this.pinnedAt,
DateTime pinExpires,
this.pinnedBy,
this.extraData,
this.deletedAt,
this.status = MessageSendingStatus.sent,
}) : id = id ?? Uuid().v4(),
pinExpires = pinExpires?.toUtc();
/// Create a new instance from a json
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
/// The message ID. This is either created by Stream or set client side when
/// the message is added.
final String id; final String id;
/// The text of this message /// The text of this message
@@ -55,7 +94,8 @@ class Message {
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
final String type; final String type;
/// The list of attachments, either provided by the user or generated from a command or as a result of URL scraping. /// The list of attachments, either provided by the user or generated from a
/// command or as a result of URL scraping.
@JsonKey(includeIfNull: false) @JsonKey(includeIfNull: false)
final List<Attachment> attachments; final List<Attachment> attachments;
@@ -188,43 +228,6 @@ class Message {
'pinned_by', 'pinned_by',
]; ];
/// Constructor used for json serialization
Message({
String id,
this.text,
this.type,
this.attachments,
this.mentionedUsers,
this.silent,
this.shadowed,
this.reactionCounts,
this.reactionScores,
this.latestReactions,
this.ownReactions,
this.parentId,
this.quotedMessage,
this.quotedMessageId,
this.replyCount = 0,
this.threadParticipants,
this.showInChannel,
this.command,
this.createdAt,
this.updatedAt,
this.user,
this.pinned = false,
this.pinnedAt,
DateTime pinExpires,
this.pinnedBy,
this.extraData,
this.deletedAt,
this.status = MessageSendingStatus.sent,
}) : id = id ?? Uuid().v4(),
pinExpires = pinExpires?.toUtc();
/// Create a new instance from a json
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot( Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
_$MessageToJson(this), topLevelFields); _$MessageToJson(this), topLevelFields);
@@ -267,7 +270,7 @@ class Message {
throw ArgumentError('`pinExpires` can only be set as DateTime or null'); throw ArgumentError('`pinExpires` can only be set as DateTime or null');
} }
return true; return true;
}()); }(), 'Validate type for pinExpires');
return Message( return Message(
id: id ?? this.id, id: id ?? this.id,
text: text ?? this.text, text: text ?? this.text,
@@ -300,8 +303,8 @@ class Message {
); );
} }
/// Returns a new [Message] that is a combination of this message and the given /// Returns a new [Message] that is a combination of this message and the
/// [other] message. /// given [other] message.
Message merge(Message other) { Message merge(Message other) {
if (other == null) return this; if (other == null) return this;
return copyWith( return copyWith(
@@ -344,6 +347,12 @@ class TranslatedMessage extends Message {
/// Constructor used for json serialization /// Constructor used for json serialization
TranslatedMessage(this.i18n); TranslatedMessage(this.i18n);
/// Create a new instance from a json
factory TranslatedMessage.fromJson(Map<String, dynamic> json) =>
_$TranslatedMessageFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields),
);
/// A Map of /// A Map of
final Map<String, String> i18n; final Map<String, String> i18n;
@@ -354,13 +363,6 @@ class TranslatedMessage extends Message {
...Message.topLevelFields, ...Message.topLevelFields,
]; ];
/// Create a new instance from a json
factory TranslatedMessage.fromJson(Map<String, dynamic> json) {
return _$TranslatedMessageFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields),
);
}
/// Serialize to json /// Serialize to json
@override @override
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot( Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
@@ -1,14 +1,19 @@
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/models/channel_model.dart';
import 'package:stream_chat/src/models/serialization.dart';
import 'serialization.dart'; import 'package:stream_chat/src/models/user.dart';
import 'user.dart';
part 'mute.g.dart'; part 'mute.g.dart';
/// The class that contains the information about a muted user /// The class that contains the information about a muted user
@JsonSerializable() @JsonSerializable()
class Mute { class Mute {
/// Constructor used for json serialization
Mute({this.user, this.channel, this.createdAt, this.updatedAt});
/// Create a new instance from a json
factory Mute.fromJson(Map<String, dynamic> json) => _$MuteFromJson(json);
/// The user that performed the muting action /// The user that performed the muting action
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
final User user; final User user;
@@ -25,12 +30,6 @@ class Mute {
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
final DateTime updatedAt; final DateTime updatedAt;
/// Constructor used for json serialization
Mute({this.user, this.channel, this.createdAt, this.updatedAt});
/// Create a new instance from a json
factory Mute.fromJson(Map<String, dynamic> json) => _$MuteFromJson(json);
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => _$MuteToJson(this); Map<String, dynamic> toJson() => _$MuteToJson(this);
} }
@@ -1,9 +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 'device.dart'; import 'package:stream_chat/src/models/mute.dart';
import 'mute.dart'; import 'package:stream_chat/src/models/serialization.dart';
import 'serialization.dart'; import 'package:stream_chat/src/models/user.dart';
import 'user.dart';
part 'own_user.g.dart'; part 'own_user.g.dart';
@@ -11,6 +10,36 @@ part 'own_user.g.dart';
/// This object can be found in [Event] /// This object can be found in [Event]
@JsonSerializable() @JsonSerializable()
class OwnUser extends User { class OwnUser extends User {
/// Constructor used for json serialization
OwnUser({
this.devices,
this.mutes,
this.totalUnreadCount,
this.unreadChannels,
this.channelMutes,
String id,
String role,
DateTime createdAt,
DateTime updatedAt,
DateTime lastActive,
bool online,
Map<String, dynamic> extraData,
bool banned,
}) : super(
id: id,
role: role,
createdAt: createdAt,
updatedAt: updatedAt,
lastActive: lastActive,
online: online,
extraData: extraData,
banned: banned,
);
/// Create a new instance from a json
factory OwnUser.fromJson(Map<String, dynamic> json) => _$OwnUserFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
/// List of user devices /// List of user devices
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
final List<Device> devices; final List<Device> devices;
@@ -42,42 +71,8 @@ class OwnUser extends User {
...User.topLevelFields, ...User.topLevelFields,
]; ];
/// Constructor used for json serialization
OwnUser({
this.devices,
this.mutes,
this.totalUnreadCount,
this.unreadChannels,
this.channelMutes,
String id,
String role,
DateTime createdAt,
DateTime updatedAt,
DateTime lastActive,
bool online,
Map<String, dynamic> extraData,
bool banned,
}) : super(
id: id,
role: role,
createdAt: createdAt,
updatedAt: updatedAt,
lastActive: lastActive,
online: online,
extraData: extraData,
banned: banned,
);
/// Create a new instance from a json
factory OwnUser.fromJson(Map<String, dynamic> json) {
return _$OwnUserFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
}
/// Serialize to json /// Serialize to json
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
return Serialization.moveFromExtraDataToRoot( _$OwnUserToJson(this), topLevelFields);
_$OwnUserToJson(this), topLevelFields);
}
} }
@@ -1,13 +1,27 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/models/serialization.dart';
import 'serialization.dart'; import 'package:stream_chat/src/models/user.dart';
import 'user.dart';
part 'reaction.g.dart'; part 'reaction.g.dart';
/// The class that defines a reaction /// The class that defines a reaction
@JsonSerializable() @JsonSerializable()
class Reaction { class Reaction {
/// Constructor used for json serialization
Reaction({
this.messageId,
this.createdAt,
this.type,
this.user,
String userId,
this.score,
this.extraData,
}) : userId = userId ?? user?.id;
/// Create a new instance from a json
factory Reaction.fromJson(Map<String, dynamic> json) => _$ReactionFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
/// The messageId to which the reaction belongs /// The messageId to which the reaction belongs
final String messageId; final String messageId;
@@ -43,28 +57,9 @@ class Reaction {
'score', 'score',
]; ];
/// Constructor used for json serialization
Reaction({
this.messageId,
this.createdAt,
this.type,
this.user,
String userId,
this.score,
this.extraData,
}) : userId = userId ?? user?.id;
/// Create a new instance from a json
factory Reaction.fromJson(Map<String, dynamic> json) {
return _$ReactionFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
}
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
return Serialization.moveFromExtraDataToRoot( _$ReactionToJson(this), topLevelFields);
_$ReactionToJson(this), topLevelFields);
}
/// Creates a copy of [Reaction] with specified attributes overridden. /// Creates a copy of [Reaction] with specified attributes overridden.
Reaction copyWith({ Reaction copyWith({
@@ -75,20 +70,19 @@ class Reaction {
String userId, String userId,
int score, int score,
Map<String, dynamic> extraData, Map<String, dynamic> extraData,
}) { }) =>
return Reaction( Reaction(
messageId: messageId ?? this.messageId, messageId: messageId ?? this.messageId,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
type: type ?? this.type, type: type ?? this.type,
user: user ?? this.user, user: user ?? this.user,
userId: userId ?? this.userId, userId: userId ?? this.userId,
score: score ?? this.score, score: score ?? this.score,
extraData: extraData ?? this.extraData, extraData: extraData ?? this.extraData,
); );
}
/// Returns a new [Reaction] that is a combination of this reaction and the given /// Returns a new [Reaction] that is a combination of this reaction and the
/// [other] reaction. /// given [other] reaction.
Reaction merge(Reaction other) { Reaction merge(Reaction other) {
if (other == null) return this; if (other == null) return this;
return copyWith( return copyWith(
+10 -11
View File
@@ -1,21 +1,11 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/models/user.dart';
import 'user.dart';
part 'read.g.dart'; part 'read.g.dart';
/// The class that defines a read event /// The class that defines a read event
@JsonSerializable() @JsonSerializable()
class Read { class Read {
/// Date of the read event
final DateTime lastRead;
/// User who sent the event
final User user;
/// Number of unread messages
final int unreadMessages;
/// Constructor used for json serialization /// Constructor used for json serialization
Read({ Read({
this.lastRead, this.lastRead,
@@ -26,6 +16,15 @@ class Read {
/// Create a new instance from a json /// Create a new instance from a json
factory Read.fromJson(Map<String, dynamic> json) => _$ReadFromJson(json); factory Read.fromJson(Map<String, dynamic> json) => _$ReadFromJson(json);
/// Date of the read event
final DateTime lastRead;
/// User who sent the event
final User user;
/// Number of unread messages
final int unreadMessages;
/// Serialize to json /// Serialize to json
Map<String, dynamic> toJson() => _$ReadToJson(this); Map<String, dynamic> toJson() => _$ReadToJson(this);
} }
@@ -1,6 +1,7 @@
import 'user.dart'; import 'package:stream_chat/src/models/user.dart';
/// Used to avoid to serialize properties to json /// Used to avoid to serialize properties to json
// ignore: prefer_void_to_null
Null readonly(_) => null; Null readonly(_) => null;
/// Helper class for serialization to and from json /// Helper class for serialization to and from json
@@ -9,9 +10,8 @@ class Serialization {
static const Function readOnly = readonly; static const Function readOnly = readonly;
/// List of users to list of userIds /// List of users to list of userIds
static List<String> userIds(List<User> users) { static List<String> userIds(List<User> users) =>
return users?.map((u) => u.id)?.toList(); users?.map((u) => u.id)?.toList();
}
/// Takes unknown json keys and puts them in the `extra_data` key /// Takes unknown json keys and puts them in the `extra_data` key
static Map<String, dynamic> moveToExtraDataFromRoot( static Map<String, dynamic> moveToExtraDataFromRoot(
@@ -34,7 +34,8 @@ class Serialization {
}); });
} }
/// Takes values in `extra_data` key and puts them on the root level of the json map /// Takes values in `extra_data` key and puts them on the root level of
/// the json map
static Map<String, dynamic> moveFromExtraDataToRoot( static Map<String, dynamic> moveFromExtraDataToRoot(
Map<String, dynamic> json, Map<String, dynamic> json,
List<String> topLevelFields, List<String> topLevelFields,
+48 -53
View File
@@ -1,12 +1,53 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/models/serialization.dart';
import 'serialization.dart';
part 'user.g.dart'; part 'user.g.dart';
/// The class that defines the user model /// The class that defines the user model
@JsonSerializable() @JsonSerializable()
class User { class User {
/// Constructor used for json serialization
User({
this.id,
this.role,
this.createdAt,
this.updatedAt,
this.lastActive,
this.online,
this.extraData,
this.banned,
this.teams,
});
/// Create a new instance from a json
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
/// Use this named constructor to create a new user instance
User.init(
this.id, {
this.online,
this.extraData,
}) : createdAt = null,
updatedAt = null,
lastActive = null,
banned = null,
teams = null,
role = null;
/// Known top level fields.
/// Useful for [Serialization] methods.
static const topLevelFields = [
'id',
'role',
'created_at',
'updated_at',
'last_active',
'online',
'banned',
'teams',
];
/// User id /// User id
final String id; final String id;
@@ -42,43 +83,8 @@ class User {
@JsonKey(includeIfNull: false) @JsonKey(includeIfNull: false)
final Map<String, dynamic> extraData; final Map<String, dynamic> extraData;
/// Known top level fields. @override
/// Useful for [Serialization] methods. int get hashCode => id.hashCode;
static const topLevelFields = [
'id',
'role',
'created_at',
'updated_at',
'last_active',
'online',
'banned',
'teams',
];
/// Use this named constructor to create a new user instance
User.init(
this.id, {
this.online,
this.extraData,
}) : createdAt = null,
updatedAt = null,
lastActive = null,
banned = null,
teams = null,
role = null;
/// Constructor used for json serialization
User({
this.id,
this.role,
this.createdAt,
this.updatedAt,
this.lastActive,
this.online,
this.extraData,
this.banned,
this.teams,
});
/// Shortcut for user name /// Shortcut for user name
String get name => String get name =>
@@ -86,23 +92,12 @@ class User {
? extraData['name'] ? extraData['name']
: id; : id;
/// Create a new instance from a json
factory User.fromJson(Map<String, dynamic> json) {
return _$UserFromJson(
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
}
/// Serialize to json
Map<String, dynamic> toJson() {
return Serialization.moveFromExtraDataToRoot(
_$UserToJson(this), topLevelFields);
}
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
other is User && runtimeType == other.runtimeType && id == other.id; other is User && runtimeType == other.runtimeType && id == other.id;
@override /// Serialize to json
int get hashCode => id.hashCode; Map<String, dynamic> toJson() =>
Serialization.moveFromExtraDataToRoot(_$UserToJson(this), topLevelFields);
} }
@@ -1,29 +1,29 @@
import 'platform_detector_stub.dart' import 'package:stream_chat/src/platform_detector/platform_detector_stub.dart'
if (dart.library.html) 'platform_detector_web.dart' if (dart.library.html) 'platform_detector_web.dart'
if (dart.library.io) 'platform_detector_io.dart'; if (dart.library.io) 'platform_detector_io.dart';
/// Possible platforms /// Possible platforms
enum PlatformType { enum PlatformType {
/// ///
Android, android,
/// ///
Ios, ios,
/// ///
Web, web,
/// ///
MacOS, macOS,
/// ///
Windows, windows,
/// ///
Linux, linux,
/// ///
Fuchsia, fuchsia,
} }
/// Utility class that provides information on the current platform /// Utility class that provides information on the current platform
@@ -31,42 +31,42 @@ class CurrentPlatform {
CurrentPlatform._(); CurrentPlatform._();
/// True if the app is running on android /// True if the app is running on android
static bool get isAndroid => type == PlatformType.Android; static bool get isAndroid => type == PlatformType.android;
/// True if the app is running on ios /// True if the app is running on ios
static bool get isIos => type == PlatformType.Ios; static bool get isIos => type == PlatformType.ios;
/// True if the app is running on web /// True if the app is running on web
static bool get isWeb => type == PlatformType.Web; static bool get isWeb => type == PlatformType.web;
/// True if the app is running on macos /// True if the app is running on macos
static bool get isMacOS => type == PlatformType.MacOS; static bool get isMacOS => type == PlatformType.macOS;
/// True if the app is running on windows /// True if the app is running on windows
static bool get isWindows => type == PlatformType.Windows; static bool get isWindows => type == PlatformType.windows;
/// True if the app is running on linux /// True if the app is running on linux
static bool get isLinux => type == PlatformType.Linux; static bool get isLinux => type == PlatformType.linux;
/// True if the app is running on fuchsia /// True if the app is running on fuchsia
static bool get isFuchsia => type == PlatformType.Fuchsia; static bool get isFuchsia => type == PlatformType.fuchsia;
/// Returns a string version of the platform /// Returns a string version of the platform
static String get name { static String get name {
switch (type) { switch (type) {
case PlatformType.Android: case PlatformType.android:
return 'android'; return 'android';
case PlatformType.Ios: case PlatformType.ios:
return 'ios'; return 'ios';
case PlatformType.Web: case PlatformType.web:
return 'web'; return 'web';
case PlatformType.MacOS: case PlatformType.macOS:
return 'macos'; return 'macos';
case PlatformType.Windows: case PlatformType.windows:
return 'windows'; return 'windows';
case PlatformType.Linux: case PlatformType.linux:
return 'linux'; return 'linux';
case PlatformType.Fuchsia: case PlatformType.fuchsia:
return 'fuchsia'; return 'fuchsia';
default: default:
return ''; return '';
@@ -1,12 +1,12 @@
import 'dart:io'; import 'dart:io';
import 'platform_detector.dart'; import 'package:stream_chat/src/platform_detector/platform_detector.dart';
/// Version running on native systems /// Version running on native systems
PlatformType get currentPlatform { PlatformType get currentPlatform {
if (Platform.isWindows) return PlatformType.Windows; if (Platform.isWindows) return PlatformType.windows;
if (Platform.isFuchsia) return PlatformType.Fuchsia; if (Platform.isFuchsia) return PlatformType.fuchsia;
if (Platform.isMacOS) return PlatformType.MacOS; if (Platform.isMacOS) return PlatformType.macOS;
if (Platform.isLinux) return PlatformType.Linux; if (Platform.isLinux) return PlatformType.linux;
if (Platform.isIOS) return PlatformType.Ios; if (Platform.isIOS) return PlatformType.ios;
return PlatformType.Android; return PlatformType.android;
} }
@@ -1,4 +1,4 @@
import 'platform_detector.dart'; import 'package:stream_chat/src/platform_detector/platform_detector.dart';
/// Stub implementation /// Stub implementation
PlatformType get currentPlatform { PlatformType get currentPlatform {
@@ -1,4 +1,4 @@
import 'platform_detector.dart'; import 'package:stream_chat/src/platform_detector/platform_detector.dart';
/// Version running on web /// Version running on web
PlatformType get currentPlatform => PlatformType.Web; PlatformType get currentPlatform => PlatformType.web;
+2 -2
View File
@@ -12,7 +12,9 @@ export './src/api/requests.dart';
export './src/api/responses.dart'; export './src/api/responses.dart';
export './src/attachment_file_uploader.dart' show AttachmentFileUploader; export './src/attachment_file_uploader.dart' show AttachmentFileUploader;
export './src/client.dart'; export './src/client.dart';
export './src/db/chat_persistence_client.dart';
export './src/event_type.dart'; export './src/event_type.dart';
export './src/extensions/string_extension.dart';
export './src/models/action.dart'; export './src/models/action.dart';
export './src/models/attachment.dart'; export './src/models/attachment.dart';
export './src/models/attachment_file.dart'; export './src/models/attachment_file.dart';
@@ -29,5 +31,3 @@ export './src/models/own_user.dart';
export './src/models/reaction.dart'; export './src/models/reaction.dart';
export './src/models/read.dart'; export './src/models/read.dart';
export './src/models/user.dart'; export './src/models/user.dart';
export './src/extensions/string_extension.dart';
export './src/db/chat_persistence_client.dart';
+1
View File
@@ -2,4 +2,5 @@ import 'package:stream_chat/src/client.dart';
/// Current package version /// Current package version
/// Used in [StreamChatClient] to build the `x-stream-client` header /// Used in [StreamChatClient] to build the `x-stream-client` header
// ignore: constant_identifier_names
const PACKAGE_VERSION = '1.3.2+1-beta'; const PACKAGE_VERSION = '1.3.2+1-beta';
+13 -12
View File
@@ -9,23 +9,24 @@ environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"
dependencies: dependencies:
json_annotation: ^3.0.1
logging: ^0.11.4
dio: ^3.0.10
web_socket_channel: ^1.1.0
uuid: ^2.2.2
async: ^2.4.2 async: ^2.4.2
rxdart: ^0.25.0
collection: ^1.14.13 collection: ^1.14.13
pedantic: ^1.9.2 dio: ^3.0.10
meta: ^1.2.4
mime: ^0.9.7
freezed_annotation: ^0.12.0 freezed_annotation: ^0.12.0
http_parser: ^3.1.4 http_parser: ^3.1.4
json_annotation: ^3.0.1
logging: ^0.11.4
meta: ^1.2.4
mime: ^0.9.7
rxdart: ^0.25.0
uuid: ^2.2.2
web_socket_channel: ^1.1.0
dev_dependencies: dev_dependencies:
build_runner: ^1.10.0 build_runner: ^1.10.0
json_serializable: ^3.3.0
test: ^1.15.7
mockito: ^4.1.1
freezed: ^0.12.7 freezed: ^0.12.7
json_serializable: ^3.3.0
mockito: ^4.1.1
test: ^1.15.7
@@ -188,7 +188,7 @@ void main() {
} }
}; };
final query = 'hello'; const query = 'hello';
final queryParams = { final queryParams = {
'payload': json.encode({ 'payload': json.encode({
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// It shows a date divider depending on the date difference /// It shows a date divider depending on the date difference