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:
co-authored by
Salvatore Giordano
parent
e13ce930a6
commit
c5252ab4d4
@@ -1,63 +1,147 @@
|
||||
include: package:pedantic/analysis_options.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
exclude:
|
||||
- lib/**/*.g.dart
|
||||
- lib/**/*.freezed.dart
|
||||
- example/*
|
||||
- test/*
|
||||
|
||||
linter:
|
||||
rules:
|
||||
# these rules are documented on and in the same order as
|
||||
# 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
|
||||
linter:
|
||||
rules:
|
||||
- always_use_package_imports
|
||||
- avoid_empty_else
|
||||
- avoid_init_to_null
|
||||
- avoid_return_types_on_setters
|
||||
- avoid_web_libraries_in_flutter
|
||||
- await_only_futures
|
||||
- camel_case_types
|
||||
- avoid_relative_lib_imports
|
||||
- avoid_slow_async_io
|
||||
- avoid_types_as_parameter_names
|
||||
- cancel_subscriptions
|
||||
- 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
|
||||
- empty_constructor_bodies
|
||||
- diagnostic_describe_all_properties
|
||||
- empty_statements
|
||||
- hash_and_equals
|
||||
- implementation_imports
|
||||
# - invariant_booleans
|
||||
# - iterable_contains_unrelated_type
|
||||
- library_names
|
||||
# - library_prefixes
|
||||
# - list_remove_unrelated_type
|
||||
# - literal_only_boolean_expressions
|
||||
- non_constant_identifier_names
|
||||
# - 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
|
||||
- invariant_booleans
|
||||
- iterable_contains_unrelated_type
|
||||
- list_remove_unrelated_type
|
||||
- literal_only_boolean_expressions
|
||||
- no_adjacent_strings_in_list
|
||||
- no_duplicate_case_values
|
||||
- no_logic_in_create_state
|
||||
- prefer_void_to_null
|
||||
- test_types_in_equals
|
||||
- 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
|
||||
- unrelated_type_equality_checks
|
||||
- valid_regexps
|
||||
- omit_local_variable_types
|
||||
- use_key_in_widget_constructors
|
||||
- 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
|
||||
@@ -2,9 +2,9 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:pedantic/pedantic.dart' show unawaited;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:pedantic/pedantic.dart' show unawaited;
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:stream_chat/src/api/retry_queue.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/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.
|
||||
class Channel {
|
||||
/// Create a channel client instance.
|
||||
@@ -57,7 +50,8 @@ class Channel {
|
||||
set extraData(Map<String, dynamic> extraData) {
|
||||
if (_initializedCompleter.isCompleted) {
|
||||
throw Exception(
|
||||
'Once the channel is initialized you should use channel.update to update channel data');
|
||||
'Once the channel is initialized you should use channel.update '
|
||||
'to update channel data');
|
||||
}
|
||||
_extraData = extraData;
|
||||
}
|
||||
@@ -168,14 +162,15 @@ class Channel {
|
||||
final Completer<bool> _initializedCompleter = Completer();
|
||||
|
||||
/// True if this is initialized
|
||||
/// Call [watch] to initialize the client or instantiate it using [Channel.fromState]
|
||||
/// Call [watch] to initialize the client or instantiate it using
|
||||
/// [Channel.fromState]
|
||||
Future<bool> get initialized => _initializedCompleter.future;
|
||||
|
||||
final _cancelableAttachmentUploadRequest = <String, CancelToken>{};
|
||||
final _messageAttachmentsUploadCompleter = <String, Completer>{};
|
||||
|
||||
/// Cancels [attachmentId] upload request. Throws exception if the request hasn't
|
||||
/// even started yet, Already completed or Already cancelled.
|
||||
/// Cancels [attachmentId] upload request. Throws exception if the request
|
||||
/// hasn't even started yet, Already completed or Already cancelled.
|
||||
///
|
||||
/// Optionally, provide a [reason] for the cancellation.
|
||||
void cancelAttachmentUpload(
|
||||
@@ -185,7 +180,8 @@ class Channel {
|
||||
final cancelToken = _cancelableAttachmentUploadRequest[attachmentId];
|
||||
if (cancelToken == null) {
|
||||
throw Exception(
|
||||
"Upload request for this Attachment hasn't started yet or else Already completed",
|
||||
"Upload request for this Attachment hasn't started yet or else "
|
||||
'Already completed',
|
||||
);
|
||||
}
|
||||
if (cancelToken.isCancelled) throw Exception('Already cancelled');
|
||||
@@ -193,15 +189,14 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Retries the failed [attachmentId] upload request.
|
||||
Future<void> retryAttachmentUpload(String messageId, String attachmentId) {
|
||||
return _uploadAttachments(messageId, [attachmentId]);
|
||||
}
|
||||
Future<void> retryAttachmentUpload(String messageId, String attachmentId) =>
|
||||
_uploadAttachments(messageId, [attachmentId]);
|
||||
|
||||
Future<void> _uploadAttachments(
|
||||
String messageId,
|
||||
Iterable<String> attachmentIds,
|
||||
) {
|
||||
var message = state.messages.firstWhere(
|
||||
final message = state.messages.firstWhere(
|
||||
(it) => it.id == messageId,
|
||||
orElse: () => null,
|
||||
);
|
||||
@@ -228,9 +223,8 @@ class Channel {
|
||||
client.logger.info('Uploading ${it.id} attachment...');
|
||||
|
||||
void updateAttachment(Attachment attachment) {
|
||||
final index = message.attachments.indexWhere((it) {
|
||||
return it.id == attachment.id;
|
||||
});
|
||||
final index =
|
||||
message.attachments.indexWhere((it) => it.id == attachment.id);
|
||||
if (index != -1) {
|
||||
message.attachments[index] = attachment;
|
||||
state?.addMessage(message);
|
||||
@@ -239,7 +233,7 @@ class Channel {
|
||||
|
||||
void onSendProgress(int sent, int total) {
|
||||
debounce(
|
||||
timeout: Duration(seconds: 1),
|
||||
timeout: const Duration(seconds: 1),
|
||||
target: updateAttachment,
|
||||
positionalArguments: [
|
||||
it.copyWith(
|
||||
@@ -270,11 +264,17 @@ class Channel {
|
||||
client.logger.info('Attachment ${it.id} uploaded successfully...');
|
||||
if (isImage) {
|
||||
updateAttachment(
|
||||
it.copyWith(imageUrl: url, uploadState: UploadState.success()),
|
||||
it.copyWith(
|
||||
imageUrl: url,
|
||||
uploadState: const UploadState.success(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
updateAttachment(
|
||||
it.copyWith(assetUrl: url, uploadState: UploadState.success()),
|
||||
it.copyWith(
|
||||
assetUrl: url,
|
||||
uploadState: const UploadState.success(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}).catchError((e, stk) {
|
||||
@@ -305,6 +305,7 @@ class Channel {
|
||||
(m) => m.id == message?.quotedMessageId,
|
||||
orElse: () => null,
|
||||
);
|
||||
// ignore: parameter_assignments
|
||||
message = message.copyWith(
|
||||
createdAt: message.createdAt ?? DateTime.now(),
|
||||
user: _client.state.user,
|
||||
@@ -313,7 +314,7 @@ class Channel {
|
||||
attachments: message.attachments?.map(
|
||||
(it) {
|
||||
if (it.uploadState.isSuccess) return it;
|
||||
return it.copyWith(uploadState: UploadState.preparing());
|
||||
return it.copyWith(uploadState: const UploadState.preparing());
|
||||
},
|
||||
)?.toList(),
|
||||
);
|
||||
@@ -340,6 +341,7 @@ class Channel {
|
||||
message.attachments.map((it) => it.id),
|
||||
));
|
||||
|
||||
// ignore: parameter_assignments
|
||||
message = await attachmentsUploadCompleter.future;
|
||||
}
|
||||
|
||||
@@ -364,13 +366,14 @@ class Channel {
|
||||
.remove(message.id)
|
||||
?.completeError('Message Cancelled');
|
||||
|
||||
// ignore: parameter_assignments
|
||||
message = message.copyWith(
|
||||
status: MessageSendingStatus.updating,
|
||||
updatedAt: message.updatedAt ?? DateTime.now(),
|
||||
attachments: message.attachments?.map(
|
||||
(it) {
|
||||
if (it.uploadState.isSuccess) return it;
|
||||
return it.copyWith(uploadState: UploadState.preparing());
|
||||
return it.copyWith(uploadState: const UploadState.preparing());
|
||||
},
|
||||
)?.toList(),
|
||||
);
|
||||
@@ -388,6 +391,7 @@ class Channel {
|
||||
message.attachments.map((it) => it.id),
|
||||
));
|
||||
|
||||
// ignore: parameter_assignments
|
||||
message = await attachmentsUploadCompleter.future;
|
||||
}
|
||||
|
||||
@@ -423,6 +427,7 @@ class Channel {
|
||||
}
|
||||
|
||||
try {
|
||||
// ignore: parameter_assignments
|
||||
message = message.copyWith(
|
||||
type: 'deleted',
|
||||
status: MessageSendingStatus.deleting,
|
||||
@@ -456,7 +461,7 @@ class Channel {
|
||||
throw ArgumentError('Invalid timeout or Expiration date');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
}(), 'Check for invalid token or expiration date');
|
||||
|
||||
DateTime pinExpires;
|
||||
if (timeoutOrExpirationDate is DateTime) {
|
||||
@@ -475,39 +480,36 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Unpins provided message
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) {
|
||||
return updateMessage(message.copyWith(pinned: false));
|
||||
}
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) =>
|
||||
updateMessage(message.copyWith(pinned: false));
|
||||
|
||||
/// Send a file to this channel
|
||||
Future<SendFileResponse> sendFile(
|
||||
AttachmentFile file, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.sendFile(
|
||||
file,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
_client.sendFile(
|
||||
file,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// Send an image to this channel
|
||||
Future<SendImageResponse> sendImage(
|
||||
AttachmentFile file, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.sendImage(
|
||||
file,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
_client.sendImage(
|
||||
file,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// A message search.
|
||||
Future<SearchMessagesResponse> search({
|
||||
@@ -515,35 +517,32 @@ class Channel {
|
||||
Map<String, dynamic> messageFilters,
|
||||
List<SortOption> sort,
|
||||
PaginationParams paginationParams,
|
||||
}) {
|
||||
return _client.search(
|
||||
{
|
||||
'cid': {
|
||||
r'$in': [cid],
|
||||
}) =>
|
||||
_client.search(
|
||||
{
|
||||
'cid': {
|
||||
r'$in': [cid],
|
||||
},
|
||||
},
|
||||
},
|
||||
sort: sort,
|
||||
query: query,
|
||||
paginationParams: paginationParams,
|
||||
messageFilters: messageFilters,
|
||||
);
|
||||
}
|
||||
sort: sort,
|
||||
query: query,
|
||||
paginationParams: paginationParams,
|
||||
messageFilters: messageFilters,
|
||||
);
|
||||
|
||||
/// Delete a file from this channel
|
||||
Future<EmptyResponse> deleteFile(
|
||||
String url, {
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.deleteFile(url, id, type, cancelToken: cancelToken);
|
||||
}
|
||||
}) =>
|
||||
_client.deleteFile(url, id, type, cancelToken: cancelToken);
|
||||
|
||||
/// Delete an image from this channel
|
||||
Future<EmptyResponse> deleteImage(
|
||||
String url, {
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.deleteImage(url, id, type, cancelToken: cancelToken);
|
||||
}
|
||||
}) =>
|
||||
_client.deleteImage(url, id, type, cancelToken: cancelToken);
|
||||
|
||||
/// Send an event on this channel
|
||||
Future<EmptyResponse> sendEvent(Event event) {
|
||||
@@ -551,9 +550,7 @@ class Channel {
|
||||
return _client.post(
|
||||
'$_channelURL/event',
|
||||
data: {'event': event.toJson()},
|
||||
).then((res) {
|
||||
return _client.decode(res.data, EmptyResponse.fromJson);
|
||||
});
|
||||
).then((res) => _client.decode(res.data, EmptyResponse.fromJson));
|
||||
}
|
||||
|
||||
/// Send a reaction to this channel
|
||||
@@ -643,11 +640,10 @@ class Channel {
|
||||
}
|
||||
|
||||
final latestReactions = [...message.latestReactions ?? <Reaction>[]]
|
||||
..removeWhere((r) {
|
||||
return r.userId == reaction.userId &&
|
||||
r.type == reaction.type &&
|
||||
r.messageId == reaction.messageId;
|
||||
});
|
||||
..removeWhere((r) =>
|
||||
r.userId == reaction.userId &&
|
||||
r.type == reaction.type &&
|
||||
r.messageId == reaction.messageId);
|
||||
|
||||
final ownReactions = [...latestReactions ?? <Reaction>[]]
|
||||
..removeWhere((it) => it.userId != user.id);
|
||||
@@ -825,7 +821,7 @@ class Channel {
|
||||
})
|
||||
..addAll(options);
|
||||
|
||||
var response;
|
||||
ChannelState response;
|
||||
|
||||
try {
|
||||
response = await query(options: watchOptions);
|
||||
@@ -861,7 +857,8 @@ class Channel {
|
||||
}
|
||||
|
||||
/// List the message replies for a parent message
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already in the offline storage
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already
|
||||
/// in the offline storage
|
||||
Future<QueryRepliesResponse> getReplies(
|
||||
String parentId,
|
||||
PaginationParams options, {
|
||||
@@ -940,16 +937,15 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Creates a new channel
|
||||
Future<ChannelState> create() async {
|
||||
return query(options: {
|
||||
'watch': false,
|
||||
'state': false,
|
||||
'presence': false,
|
||||
});
|
||||
}
|
||||
Future<ChannelState> create() async => query(options: {
|
||||
'watch': false,
|
||||
'state': false,
|
||||
'presence': false,
|
||||
});
|
||||
|
||||
/// Query the API, get messages, members or other channel fields
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already in the offline storage
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already
|
||||
/// in the offline storage
|
||||
Future<ChannelState> query({
|
||||
Map<String, dynamic> options = const {},
|
||||
PaginationParams messagesPagination,
|
||||
@@ -1112,8 +1108,9 @@ class Channel {
|
||||
});
|
||||
}
|
||||
|
||||
/// Hides the channel from [StreamChatClient.queryChannels] for the user until a message is added
|
||||
/// If [clearHistory] is set to true - all messages will be removed for the user
|
||||
/// Hides the channel from [StreamChatClient.queryChannels] for the user
|
||||
/// until a message is added If [clearHistory] is set to true - all messages
|
||||
/// will be removed for the user
|
||||
Future<EmptyResponse> hide({bool clearHistory = false}) async {
|
||||
_checkInitialized();
|
||||
final response = await _client
|
||||
@@ -1134,28 +1131,28 @@ class Channel {
|
||||
return _client.decode(response.data, EmptyResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Stream of [Event] coming from websocket connection specific for the channel
|
||||
/// Pass an eventType as parameter in order to filter just a type of event
|
||||
/// Stream of [Event] coming from websocket connection specific for the
|
||||
/// channel. Pass an eventType as parameter in order to filter just a type
|
||||
/// of event
|
||||
Stream<Event> on([
|
||||
String eventType,
|
||||
String eventType2,
|
||||
String eventType3,
|
||||
String eventType4,
|
||||
]) {
|
||||
return _client
|
||||
.on(
|
||||
eventType,
|
||||
eventType2,
|
||||
eventType3,
|
||||
eventType4,
|
||||
)
|
||||
.where((e) => e.cid == cid);
|
||||
}
|
||||
]) =>
|
||||
_client
|
||||
.on(
|
||||
eventType,
|
||||
eventType2,
|
||||
eventType3,
|
||||
eventType4,
|
||||
)
|
||||
.where((e) => e.cid == cid);
|
||||
|
||||
DateTime _lastTypingEvent;
|
||||
|
||||
/// First of the [EventType.typingStart] and [EventType.typingStop] events based on the users keystrokes.
|
||||
/// Call this on every keystroke.
|
||||
/// First of the [EventType.typingStart] and [EventType.typingStop] events
|
||||
/// based on the users keystrokes. Call this on every keystroke.
|
||||
Future<void> keyStroke([String parentId]) async {
|
||||
if (config?.typingEvents == false) {
|
||||
return;
|
||||
@@ -1196,15 +1193,14 @@ class Channel {
|
||||
void _checkInitialized() {
|
||||
if (!_initializedCompleter.isCompleted) {
|
||||
throw Exception(
|
||||
"Channel $cid hasn't been initialized yet. Make sure to call .watch() or to instantiate the client using [Channel.fromState]");
|
||||
"Channel $cid hasn't been initialized yet. Make sure to call .watch()"
|
||||
' or to instantiate the client using [Channel.fromState]');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The class that handles the state of the channel listening to the events
|
||||
class ChannelClientState {
|
||||
final _subscriptions = <StreamSubscription>[];
|
||||
|
||||
/// Creates a new instance listening to events and updating the state
|
||||
ChannelClientState(this._channel, ChannelState channelState) {
|
||||
retryQueue = RetryQueue(
|
||||
@@ -1252,14 +1248,16 @@ class ChannelClientState {
|
||||
_channel._client.chatPersistenceClient
|
||||
?.getChannelStateByCid(_channel.cid)
|
||||
?.then((state) {
|
||||
// Replacing the persistence state members with the latest `channelState.members`
|
||||
// as they may have changes over the time.
|
||||
// Replacing the persistence state members with the latest
|
||||
// `channelState.members` as they may have changes over the time.
|
||||
updateChannelState(state.copyWith(members: channelState.members));
|
||||
retryFailedMessages();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
final _subscriptions = <StreamSubscription>[];
|
||||
|
||||
void _computeInitialUnread() {
|
||||
final userRead = channelState?.read?.firstWhere(
|
||||
(r) => r.user.id == _channel._client.state?.user?.id,
|
||||
@@ -1338,8 +1336,8 @@ class ChannelClientState {
|
||||
|
||||
/// Flag which indicates if [ChannelClientState] contain latest/recent messages or not.
|
||||
/// This flag should be managed by UI sdks.
|
||||
/// When false, any new message (received by WebSocket event - [EventType.messageNew]) will not
|
||||
/// be pushed on to message list.
|
||||
/// When false, any new message (received by WebSocket event
|
||||
/// - [EventType.messageNew]) will not be pushed on to message list.
|
||||
bool get isUpToDate => _isUpToDateController.value;
|
||||
|
||||
set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate);
|
||||
@@ -1357,12 +1355,18 @@ class ChannelClientState {
|
||||
Future<void> retryFailedMessages() async {
|
||||
final failedMessages =
|
||||
<Message>[...messages, ...threads.values.expand((v) => v)]
|
||||
.where((message) =>
|
||||
message.status != null &&
|
||||
message.status != MessageSendingStatus.sent &&
|
||||
message.createdAt.isBefore(DateTime.now().subtract(Duration(
|
||||
seconds: 1,
|
||||
))))
|
||||
.where(
|
||||
(message) =>
|
||||
message.status != null &&
|
||||
message.status != MessageSendingStatus.sent &&
|
||||
message.createdAt.isBefore(
|
||||
DateTime.now().subtract(
|
||||
const Duration(
|
||||
seconds: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
retryQueue.add(failedMessages);
|
||||
@@ -1471,27 +1475,32 @@ class ChannelClientState {
|
||||
return;
|
||||
}
|
||||
|
||||
_subscriptions.add(_channel
|
||||
.on(
|
||||
EventType.messageRead,
|
||||
EventType.notificationMarkRead,
|
||||
)
|
||||
.listen((event) {
|
||||
final readList = List<Read>.from(_channelState?.read ?? []);
|
||||
final userReadIndex = read?.indexWhere((r) => r.user.id == event.user.id);
|
||||
_subscriptions.add(
|
||||
_channel
|
||||
.on(
|
||||
EventType.messageRead,
|
||||
EventType.notificationMarkRead,
|
||||
)
|
||||
.listen(
|
||||
(event) {
|
||||
final readList = List<Read>.from(_channelState?.read ?? []);
|
||||
final userReadIndex =
|
||||
read?.indexWhere((r) => r.user.id == event.user.id);
|
||||
|
||||
if (userReadIndex != null && userReadIndex != -1) {
|
||||
final userRead = readList.removeAt(userReadIndex);
|
||||
if (userRead.user?.id == _channel._client.state.user.id) {
|
||||
_unreadCountController.add(0);
|
||||
}
|
||||
readList.add(Read(
|
||||
user: event.user,
|
||||
lastRead: event.createdAt,
|
||||
));
|
||||
_channelState = _channelState.copyWith(read: readList);
|
||||
}
|
||||
}));
|
||||
if (userReadIndex != null && userReadIndex != -1) {
|
||||
final userRead = readList.removeAt(userReadIndex);
|
||||
if (userRead.user?.id == _channel._client.state.user.id) {
|
||||
_unreadCountController.add(0);
|
||||
}
|
||||
readList.add(Read(
|
||||
user: event.user,
|
||||
lastRead: event.createdAt,
|
||||
));
|
||||
_channelState = _channelState.copyWith(read: readList);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Channel message list
|
||||
@@ -1527,11 +1536,8 @@ class ChannelClientState {
|
||||
List<Member>, Map<String, User>, List<Member>>(
|
||||
channelStateStream.map((cs) => cs.members),
|
||||
_channel.client.state.usersStream,
|
||||
(members, users) {
|
||||
return members
|
||||
.map((e) => e.copyWith(user: users[e.user.id]))
|
||||
.toList();
|
||||
},
|
||||
(members, users) =>
|
||||
members.map((e) => e.copyWith(user: users[e.user.id])).toList(),
|
||||
);
|
||||
|
||||
/// Channel watcher count
|
||||
@@ -1551,9 +1557,7 @@ class ChannelClientState {
|
||||
CombineLatestStream.combine2<List<User>, Map<String, User>, List<User>>(
|
||||
channelStateStream.map((cs) => cs.watchers),
|
||||
_channel.client.state.usersStream,
|
||||
(watchers, users) {
|
||||
return watchers.map((e) => users[e.id] ?? e).toList();
|
||||
},
|
||||
(watchers, users) => watchers.map((e) => users[e.id] ?? e).toList(),
|
||||
);
|
||||
|
||||
/// Channel read list
|
||||
@@ -1625,9 +1629,7 @@ class ChannelClientState {
|
||||
true)
|
||||
?.toList() ??
|
||||
[],
|
||||
];
|
||||
|
||||
newMessages.sort(_sortByCreatedAt);
|
||||
]..sort(_sortByCreatedAt);
|
||||
|
||||
final newWatchers = <User>[
|
||||
...updatedState?.watchers ?? [],
|
||||
@@ -1692,10 +1694,9 @@ class ChannelClientState {
|
||||
|
||||
set _channelState(ChannelState v) {
|
||||
_channelStateController.add(v);
|
||||
|
||||
if (_channel._client.persistenceEnabled) {
|
||||
debounce(
|
||||
timeout: Duration(milliseconds: 500),
|
||||
timeout: const Duration(milliseconds: 500),
|
||||
target: _channel._client.chatPersistenceClient?.updateChannelState,
|
||||
positionalArguments: [v],
|
||||
);
|
||||
@@ -1735,28 +1736,37 @@ class ChannelClientState {
|
||||
return;
|
||||
}
|
||||
|
||||
_subscriptions.add(_channel.on(EventType.typingStart).listen((event) {
|
||||
if (event.user.id != _channel.client.state.user.id) {
|
||||
_typings[event.user] = DateTime.now();
|
||||
_typingEventsController.add(_typings.keys.toList());
|
||||
}
|
||||
}));
|
||||
|
||||
_subscriptions.add(_channel.on(EventType.typingStop).listen((event) {
|
||||
if (event.user.id != _channel.client.state.user.id) {
|
||||
_typings.remove(event.user);
|
||||
_typingEventsController.add(_typings.keys.toList());
|
||||
}
|
||||
}));
|
||||
_subscriptions
|
||||
..add(
|
||||
_channel.on(EventType.typingStart).listen(
|
||||
(event) {
|
||||
if (event.user.id != _channel.client.state.user.id) {
|
||||
_typings[event.user] = DateTime.now();
|
||||
_typingEventsController.add(_typings.keys.toList());
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
..add(
|
||||
_channel.on(EventType.typingStop).listen(
|
||||
(event) {
|
||||
if (event.user.id != _channel.client.state.user.id) {
|
||||
_typings.remove(event.user);
|
||||
_typingEventsController.add(_typings.keys.toList());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Timer _cleaningTimer;
|
||||
|
||||
void _startCleaning() {
|
||||
if (_channel.config?.typingEvents == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
_cleaningTimer = Timer.periodic(Duration(seconds: 1), (_) {
|
||||
_cleaningTimer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
final now = DateTime.now();
|
||||
|
||||
if (_channel._lastTypingEvent != null &&
|
||||
@@ -1769,8 +1779,9 @@ class ChannelClientState {
|
||||
}
|
||||
|
||||
Timer _pinnedMessagesTimer;
|
||||
|
||||
void _startCleaningPinnedMessages() {
|
||||
_pinnedMessagesTimer = Timer.periodic(Duration(seconds: 30), (_) {
|
||||
_pinnedMessagesTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||
final now = DateTime.now();
|
||||
var expiredMessages = channelState.pinnedMessages
|
||||
?.where((m) => m.pinExpires?.isBefore(now) == true)
|
||||
@@ -1781,8 +1792,6 @@ class ChannelClientState {
|
||||
.map((m) => m.copyWith(
|
||||
pinExpires: null,
|
||||
pinned: false,
|
||||
pinnedAt: null,
|
||||
pinnedBy: null,
|
||||
))
|
||||
.toList();
|
||||
|
||||
|
||||
@@ -5,22 +5,6 @@ part 'requests.g.dart';
|
||||
/// Sorting options
|
||||
@JsonSerializable(createFactory: false)
|
||||
class SortOption<T> {
|
||||
/// Ascending order
|
||||
static const ASC = 1;
|
||||
|
||||
/// Descending order
|
||||
static const DESC = -1;
|
||||
|
||||
/// A sorting field name
|
||||
final String field;
|
||||
|
||||
/// A sorting direction
|
||||
final int direction;
|
||||
|
||||
/// Sorting field Comparator required for offline sorting
|
||||
@JsonKey(ignore: true)
|
||||
final Comparator<T> comparator;
|
||||
|
||||
/// Creates a new SortOption instance
|
||||
///
|
||||
/// For example:
|
||||
@@ -34,6 +18,24 @@ class SortOption<T> {
|
||||
this.comparator,
|
||||
});
|
||||
|
||||
/// Ascending order
|
||||
// ignore: constant_identifier_names
|
||||
static const ASC = 1;
|
||||
|
||||
/// Descending order
|
||||
// ignore: constant_identifier_names
|
||||
static const DESC = -1;
|
||||
|
||||
/// A sorting field name
|
||||
final String field;
|
||||
|
||||
/// A sorting direction
|
||||
final int direction;
|
||||
|
||||
/// Sorting field Comparator required for offline sorting
|
||||
@JsonKey(ignore: true)
|
||||
final Comparator<T> comparator;
|
||||
|
||||
/// Serialize model to json
|
||||
Map<String, dynamic> toJson() => _$SortOptionToJson(this);
|
||||
}
|
||||
@@ -41,6 +43,25 @@ class SortOption<T> {
|
||||
/// Pagination options.
|
||||
@JsonSerializable(createFactory: false, includeIfNull: false)
|
||||
class PaginationParams {
|
||||
/// Creates a new PaginationParams instance
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// // limit to 50
|
||||
/// final paginationParams = PaginationParams(limit: 50);
|
||||
///
|
||||
/// // limit to 50 with offset
|
||||
/// final paginationParams = PaginationParams(limit: 50, offset: 50);
|
||||
/// ```
|
||||
const PaginationParams({
|
||||
this.limit = 10,
|
||||
this.offset = 0,
|
||||
this.greaterThan,
|
||||
this.greaterThanOrEqual,
|
||||
this.lessThan,
|
||||
this.lessThanOrEqual,
|
||||
});
|
||||
|
||||
/// The amount of items requested from the APIs.
|
||||
final int limit;
|
||||
|
||||
@@ -63,25 +84,6 @@ class PaginationParams {
|
||||
@JsonKey(name: 'id_lte')
|
||||
final String lessThanOrEqual;
|
||||
|
||||
/// Creates a new PaginationParams instance
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// // limit to 50
|
||||
/// final paginationParams = PaginationParams(limit: 50);
|
||||
///
|
||||
/// // limit to 50 with offset
|
||||
/// final paginationParams = PaginationParams(limit: 50, offset: 50);
|
||||
/// ```
|
||||
const PaginationParams({
|
||||
this.limit = 10,
|
||||
this.offset = 0,
|
||||
this.greaterThan,
|
||||
this.greaterThanOrEqual,
|
||||
this.lessThan,
|
||||
this.lessThanOrEqual,
|
||||
});
|
||||
|
||||
/// Serialize model to json
|
||||
Map<String, dynamic> toJson() => _$PaginationParamsToJson(this);
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/client.dart';
|
||||
import 'package:stream_chat/src/models/channel_model.dart';
|
||||
import 'package:stream_chat/src/models/channel_state.dart';
|
||||
import 'package:stream_chat/src/models/device.dart';
|
||||
import 'package:stream_chat/src/models/event.dart';
|
||||
|
||||
import '../models/channel_model.dart';
|
||||
import '../models/channel_state.dart';
|
||||
import '../models/member.dart';
|
||||
import '../models/message.dart';
|
||||
import '../models/reaction.dart';
|
||||
import '../models/read.dart';
|
||||
import '../models/user.dart';
|
||||
import 'package:stream_chat/src/models/member.dart';
|
||||
import 'package:stream_chat/src/models/message.dart';
|
||||
import 'package:stream_chat/src/models/reaction.dart';
|
||||
import 'package:stream_chat/src/models/read.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
part 'responses.g.dart';
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ class RetryPolicy {
|
||||
final bool Function(StreamChatClient client, int attempt, ApiError apiError)
|
||||
shouldRetry;
|
||||
|
||||
/// In the case that we want to retry a failed request the retryTimeout method is called to determine the timeout
|
||||
/// In the case that we want to retry a failed request the retryTimeout
|
||||
/// method is called to determine the timeout
|
||||
final Duration Function(
|
||||
StreamChatClient client, int attempt, ApiError apiError) retryTimeout;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:stream_chat/src/api/channel.dart';
|
||||
import 'package:stream_chat/src/api/retry_policy.dart';
|
||||
import 'package:stream_chat/src/event_type.dart';
|
||||
@@ -12,12 +12,6 @@ import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
/// The retry queue associated to a channel
|
||||
class RetryQueue {
|
||||
/// The channel of this queue
|
||||
final Channel channel;
|
||||
|
||||
/// The logger associated to this queue
|
||||
final Logger logger;
|
||||
|
||||
/// Instantiate a new RetryQueue object
|
||||
RetryQueue({
|
||||
@required this.channel,
|
||||
@@ -30,6 +24,12 @@ class RetryQueue {
|
||||
_listenFailedEvents();
|
||||
}
|
||||
|
||||
/// The channel of this queue
|
||||
final Channel channel;
|
||||
|
||||
/// The logger associated to this queue
|
||||
final Logger logger;
|
||||
|
||||
final _subscriptions = <StreamSubscription>[];
|
||||
|
||||
void _listenConnectionRecovered() {
|
||||
|
||||
@@ -2,28 +2,29 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
import '../models/event.dart';
|
||||
import '../models/user.dart';
|
||||
import 'connection_status.dart';
|
||||
import 'web_socket_channel_stub.dart'
|
||||
import 'package:stream_chat/src/api/connection_status.dart';
|
||||
import 'package:stream_chat/src/api/web_socket_channel_stub.dart'
|
||||
if (dart.library.html) 'web_socket_channel_html.dart'
|
||||
if (dart.library.io) 'web_socket_channel_io.dart';
|
||||
import 'package:stream_chat/src/models/event.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
/// Typedef which exposes an [Event] as the only parameter.
|
||||
typedef EventHandler = void Function(Event);
|
||||
|
||||
/// Typedef used for connecting to a websocket. Method returns a [WebSocketChannel]
|
||||
/// and accepts a connection [url] and an optional [Iterable] of `protocols`.
|
||||
/// Typedef used for connecting to a websocket. Method returns a
|
||||
/// [WebSocketChannel] and accepts a connection [url] and an optional
|
||||
/// [Iterable] of `protocols`.
|
||||
typedef ConnectWebSocket = WebSocketChannel Function(String url,
|
||||
{Iterable<String> protocols});
|
||||
|
||||
// TODO: parse error even
|
||||
// TODO: if parsing an error into an event fails we should not hide the original error
|
||||
// TODO: if parsing an error into an event fails we should not hide the
|
||||
// TODO: original error
|
||||
/// A WebSocket connection that reconnects upon failure.
|
||||
class WebSocket {
|
||||
/// Creates a new websocket
|
||||
@@ -75,7 +76,8 @@ class WebSocket {
|
||||
/// WS connection payload
|
||||
final Map<String, dynamic> connectPayload;
|
||||
|
||||
/// Functions that will be called every time a new event is received from the connection
|
||||
/// Functions that will be called every time a new event is received from the
|
||||
/// connection
|
||||
final EventHandler handler;
|
||||
|
||||
/// A WS specific logger instance
|
||||
@@ -87,8 +89,9 @@ class WebSocket {
|
||||
final ConnectWebSocket connectFunc;
|
||||
|
||||
/// Interval of the reconnection monitor timer
|
||||
/// This checks that it received a new event in the last [reconnectionMonitorTimeout] seconds,
|
||||
/// otherwise it considers the connection unhealthy and reconnects the WS
|
||||
/// This checks that it received a new event in the last
|
||||
/// [reconnectionMonitorTimeout] seconds, otherwise it considers the
|
||||
/// connection unhealthy and reconnects the WS
|
||||
final int reconnectionMonitorInterval;
|
||||
|
||||
/// Interval of the health event sending timer
|
||||
@@ -96,7 +99,8 @@ class WebSocket {
|
||||
/// make the server aware that the client is still listening
|
||||
final int healthCheckInterval;
|
||||
|
||||
/// The timeout that uses the reconnection monitor timer to consider the connection unhealthy
|
||||
/// The timeout that uses the reconnection monitor timer to consider the
|
||||
/// connection unhealthy
|
||||
final int reconnectionMonitorTimeout;
|
||||
|
||||
final _connectionStatusController =
|
||||
@@ -121,9 +125,7 @@ class WebSocket {
|
||||
_connecting = false,
|
||||
_reconnecting = false;
|
||||
|
||||
Event _decodeEvent(String source) {
|
||||
return Event.fromJson(json.decode(source));
|
||||
}
|
||||
Event _decodeEvent(String source) => Event.fromJson(json.decode(source));
|
||||
|
||||
Completer<Event> _connectionCompleter = Completer<Event>();
|
||||
|
||||
@@ -166,8 +168,8 @@ class WebSocket {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'connection closed | closeCode: ${_channel.closeCode} | closedReason: ${_channel.closeReason}');
|
||||
logger.info('connection closed | closeCode: ${_channel.closeCode} | '
|
||||
'closedReason: ${_channel.closeReason}');
|
||||
|
||||
if (!_reconnecting) {
|
||||
_reconnect();
|
||||
@@ -200,8 +202,7 @@ class WebSocket {
|
||||
}
|
||||
|
||||
Future<void> _onConnectionError(error, [stacktrace]) async {
|
||||
logger.severe('error connecting');
|
||||
logger.severe(error);
|
||||
logger..severe('error connecting')..severe(error);
|
||||
if (stacktrace != null) {
|
||||
logger.severe(stacktrace);
|
||||
}
|
||||
@@ -219,21 +220,21 @@ class WebSocket {
|
||||
}
|
||||
}
|
||||
|
||||
void _startReconnectionMonitor() {
|
||||
final reconnectionTimer = (_) {
|
||||
final now = DateTime.now();
|
||||
if (_lastEventAt != null &&
|
||||
now.difference(_lastEventAt).inSeconds > reconnectionMonitorTimeout) {
|
||||
_channel.sink.close();
|
||||
}
|
||||
};
|
||||
void _reconnectionTimer(_) {
|
||||
final now = DateTime.now();
|
||||
if (_lastEventAt != null &&
|
||||
now.difference(_lastEventAt).inSeconds > reconnectionMonitorTimeout) {
|
||||
_channel.sink.close();
|
||||
}
|
||||
}
|
||||
|
||||
void _startReconnectionMonitor() {
|
||||
_reconnectionMonitor = Timer.periodic(
|
||||
Duration(seconds: reconnectionMonitorInterval),
|
||||
reconnectionTimer,
|
||||
_reconnectionTimer,
|
||||
);
|
||||
|
||||
reconnectionTimer(_reconnectionMonitor);
|
||||
_reconnectionTimer(_reconnectionMonitor);
|
||||
}
|
||||
|
||||
void _reconnectTimer() async {
|
||||
@@ -283,20 +284,20 @@ class WebSocket {
|
||||
}
|
||||
}
|
||||
|
||||
void _healthCheckTimer(_) {
|
||||
logger.info('sending health.check');
|
||||
_channel.sink.add("{'type': 'health.check'}");
|
||||
}
|
||||
|
||||
void _startHealthCheck() {
|
||||
logger.info('start health check monitor');
|
||||
|
||||
final healthCheckTimer = (_) {
|
||||
logger.info('sending health.check');
|
||||
_channel.sink.add("{'type': 'health.check'}");
|
||||
};
|
||||
|
||||
_healthCheck = Timer.periodic(
|
||||
Duration(seconds: healthCheckInterval),
|
||||
healthCheckTimer,
|
||||
_healthCheckTimer,
|
||||
);
|
||||
|
||||
healthCheckTimer(_healthCheck);
|
||||
_healthCheckTimer(_healthCheck);
|
||||
}
|
||||
|
||||
/// Disconnects the WS and releases eventual resources
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:dio/dio.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 'client.dart';
|
||||
import 'extensions/string_extension.dart';
|
||||
import 'package:stream_chat/src/extensions/string_extension.dart';
|
||||
|
||||
/// Class responsible for uploading images and files to a given channel
|
||||
abstract class AttachmentFileUploader {
|
||||
@@ -57,11 +57,11 @@ abstract class AttachmentFileUploader {
|
||||
|
||||
/// Stream's default implementation of [AttachmentFileUploader]
|
||||
class StreamAttachmentFileUploader implements AttachmentFileUploader {
|
||||
final StreamChatClient _client;
|
||||
|
||||
/// Creates a new [StreamAttachmentFileUploader] instance.
|
||||
const StreamAttachmentFileUploader(this._client);
|
||||
|
||||
final StreamChatClient _client;
|
||||
|
||||
@override
|
||||
Future<SendImageResponse> sendImage(
|
||||
AttachmentFile file,
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:stream_chat/src/extensions/map_extension.dart';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:pedantic/pedantic.dart' show unawaited;
|
||||
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/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/exceptions.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_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/user.dart';
|
||||
import 'package:stream_chat/src/platform_detector/platform_detector.dart';
|
||||
import 'package:stream_chat/version.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'attachment_file_uploader.dart';
|
||||
import 'api/channel.dart';
|
||||
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.
|
||||
/// Handler function used for logging records. Function requires a single
|
||||
/// [LogRecord] as the only parameter.
|
||||
typedef LogHandlerFunction = void Function(LogRecord record);
|
||||
|
||||
/// Used for decoding [Map] data to a generic type `T`.
|
||||
@@ -62,7 +61,9 @@ extension on PushProvider {
|
||||
|
||||
/// The official Dart client for Stream Chat,
|
||||
/// a service for building chat applications.
|
||||
/// This library can be used on any Dart project and on both mobile and web apps 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/
|
||||
///
|
||||
/// The Chat client will manage API call, event handling and manage the
|
||||
@@ -73,7 +74,8 @@ extension on PushProvider {
|
||||
/// ```
|
||||
class StreamChatClient {
|
||||
/// 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(
|
||||
this.apiKey, {
|
||||
this.tokenProvider,
|
||||
@@ -83,6 +85,7 @@ class StreamChatClient {
|
||||
Duration connectTimeout = const Duration(seconds: 6),
|
||||
Duration receiveTimeout = const Duration(seconds: 6),
|
||||
Dio httpClient,
|
||||
// ignore: avoid_unused_constructor_parameters
|
||||
RetryPolicy retryPolicy,
|
||||
this.attachmentFileUploader,
|
||||
}) {
|
||||
@@ -122,30 +125,39 @@ class StreamChatClient {
|
||||
/// This client state
|
||||
ClientState state;
|
||||
|
||||
/// By default the Chat client will write all messages with level Warn or Error to stdout.
|
||||
/// During development you might want to enable more logging information, you can change the default log level when constructing the client.
|
||||
/// By default the Chat client will write all messages with level Warn or
|
||||
/// 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
|
||||
/// final client = StreamChatClient("stream-chat-api-key", logLevel: Level.INFO);
|
||||
/// final client = StreamChatClient("stream-chat-api-key",
|
||||
/// logLevel: Level.INFO);
|
||||
/// ```
|
||||
final Level logLevel;
|
||||
|
||||
/// 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('📡');
|
||||
|
||||
/// A function that has a parameter of type [LogRecord].
|
||||
/// This is called on every new log record.
|
||||
/// By default the client will use the handler returned by [_getDefaultLogHandler].
|
||||
/// 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.
|
||||
/// By default the client will use the handler returned by
|
||||
/// [_getDefaultLogHandler].
|
||||
/// 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
|
||||
/// myLogHandlerFunction = (LogRecord record) {
|
||||
/// // 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;
|
||||
|
||||
@@ -156,14 +168,18 @@ class StreamChatClient {
|
||||
/// Your project Stream Chat base url.
|
||||
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.
|
||||
/// 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;
|
||||
|
||||
/// [Dio] httpClient
|
||||
/// It's be chosen because it's easy to use and supports interesting features out of the box
|
||||
/// (Interceptors, Global configuration, FormData, File downloading etc.)
|
||||
/// It's be chosen because it's easy to use and supports interesting features
|
||||
/// out of the box (Interceptors, Global configuration, FormData,
|
||||
/// File downloading etc.)
|
||||
@visibleForTesting
|
||||
Dio httpClient = Dio();
|
||||
|
||||
@@ -234,7 +250,7 @@ class StreamChatClient {
|
||||
(options.data is Map || options.data == null)) {
|
||||
options.data = {
|
||||
'connection_id': _connectionId,
|
||||
...(options.data ?? {}),
|
||||
...options.data ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -278,7 +294,7 @@ class StreamChatClient {
|
||||
await _disconnect();
|
||||
|
||||
final newToken = await tokenProvider(userId);
|
||||
await Future.delayed(Duration(seconds: 4));
|
||||
await Future.delayed(const Duration(seconds: 4));
|
||||
token = newToken;
|
||||
|
||||
httpClient.unlock();
|
||||
@@ -312,7 +328,10 @@ class StreamChatClient {
|
||||
};
|
||||
return (LogRecord record) {
|
||||
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) {
|
||||
print(record.stackTrace);
|
||||
}
|
||||
@@ -321,11 +340,10 @@ class StreamChatClient {
|
||||
|
||||
Logger _detachedLogger(
|
||||
String name,
|
||||
) {
|
||||
return Logger.detached(name)
|
||||
..level = logLevel
|
||||
..onRecord.listen(logHandlerFunction ?? _getDefaultLogHandler());
|
||||
}
|
||||
) =>
|
||||
Logger.detached(name)
|
||||
..level = logLevel
|
||||
..onRecord.listen(logHandlerFunction ?? _getDefaultLogHandler());
|
||||
|
||||
void _setupLogger() {
|
||||
logger.level = logLevel;
|
||||
@@ -386,7 +404,8 @@ class StreamChatClient {
|
||||
/// Set the current user using the [tokenProvider] to fetch the token.
|
||||
/// It returns a [Future] that resolves when the connection is setup.
|
||||
@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);
|
||||
|
||||
/// Connects the current user using the [tokenProvider] to fetch the token.
|
||||
@@ -521,6 +540,7 @@ class StreamChatClient {
|
||||
}).catchError((err, stacktrace) {
|
||||
logger.severe('error connecting ws', err, stacktrace);
|
||||
if (err is Map) {
|
||||
// ignore: only_throw_errors
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
@@ -558,13 +578,12 @@ class StreamChatClient {
|
||||
res.events.sort((a, b) => a.createdAt.compareTo(b.createdAt));
|
||||
|
||||
res.events.forEach((element) {
|
||||
logger.fine('element.type: ${element.type}');
|
||||
logger.fine('element.message.text: ${element.message?.text}');
|
||||
logger
|
||||
..fine('element.type: ${element.type}')
|
||||
..fine('element.message.text: ${element.message?.text}');
|
||||
});
|
||||
|
||||
res.events.forEach((event) {
|
||||
handleEvent(event);
|
||||
});
|
||||
res.events.forEach(handleEvent);
|
||||
|
||||
await chatPersistenceClient?.updateLastSyncAt(DateTime.now());
|
||||
_synced = true;
|
||||
@@ -573,9 +592,7 @@ class StreamChatClient {
|
||||
}
|
||||
}
|
||||
|
||||
String _asMap(sort) {
|
||||
return sort?.map((s) => s.toJson().toString())?.join('');
|
||||
}
|
||||
String _asMap(sort) => sort?.map((s) => s.toJson().toString())?.join('');
|
||||
|
||||
final _queryChannelsStreams = <String, Future<List<Channel>>>{};
|
||||
|
||||
@@ -584,13 +601,14 @@ class StreamChatClient {
|
||||
Map<String, dynamic> filter,
|
||||
List<SortOption<ChannelModel>> sort,
|
||||
Map<String, dynamic> options,
|
||||
PaginationParams paginationParams = const PaginationParams(limit: 10),
|
||||
PaginationParams paginationParams = const PaginationParams(),
|
||||
int messageLimit,
|
||||
bool preferOffline = false,
|
||||
bool waitForConnect = true,
|
||||
}) async* {
|
||||
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)) {
|
||||
@@ -627,7 +645,7 @@ class StreamChatClient {
|
||||
List<SortOption<ChannelModel>> sort,
|
||||
Map<String, dynamic> options,
|
||||
int messageLimit,
|
||||
PaginationParams paginationParams = const PaginationParams(limit: 10),
|
||||
PaginationParams paginationParams = const PaginationParams(),
|
||||
bool waitForConnect = true,
|
||||
}) async {
|
||||
if (waitForConnect) {
|
||||
@@ -650,7 +668,7 @@ class StreamChatClient {
|
||||
'presence': false,
|
||||
};
|
||||
|
||||
var payload = <String, dynamic>{
|
||||
final payload = <String, dynamic>{
|
||||
'filter_conditions': filter,
|
||||
'sort': sort,
|
||||
};
|
||||
@@ -682,9 +700,12 @@ class StreamChatClient {
|
||||
);
|
||||
|
||||
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
|
||||
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>[];
|
||||
}
|
||||
|
||||
@@ -715,7 +736,7 @@ class StreamChatClient {
|
||||
Future<List<Channel>> queryChannelsOffline({
|
||||
@required Map<String, dynamic> filter,
|
||||
@required List<SortOption<ChannelModel>> sort,
|
||||
PaginationParams paginationParams = const PaginationParams(limit: 10),
|
||||
PaginationParams paginationParams = const PaginationParams(),
|
||||
}) async {
|
||||
final offlineChannels = await chatPersistenceClient?.getChannelStates(
|
||||
filter: filter,
|
||||
@@ -771,6 +792,7 @@ class StreamChatClient {
|
||||
);
|
||||
return response;
|
||||
} on DioError catch (error) {
|
||||
// ignore: only_throw_errors
|
||||
throw _parseError(error);
|
||||
}
|
||||
}
|
||||
@@ -791,6 +813,7 @@ class StreamChatClient {
|
||||
);
|
||||
return response;
|
||||
} on DioError catch (error) {
|
||||
// ignore: only_throw_errors
|
||||
throw _parseError(error);
|
||||
}
|
||||
}
|
||||
@@ -809,6 +832,7 @@ class StreamChatClient {
|
||||
);
|
||||
return response;
|
||||
} on DioError catch (error) {
|
||||
// ignore: only_throw_errors
|
||||
throw _parseError(error);
|
||||
}
|
||||
}
|
||||
@@ -827,6 +851,7 @@ class StreamChatClient {
|
||||
);
|
||||
return response;
|
||||
} on DioError catch (error) {
|
||||
// ignore: only_throw_errors
|
||||
throw _parseError(error);
|
||||
}
|
||||
}
|
||||
@@ -845,6 +870,7 @@ class StreamChatClient {
|
||||
);
|
||||
return response;
|
||||
} on DioError catch (error) {
|
||||
// ignore: only_throw_errors
|
||||
throw _parseError(error);
|
||||
}
|
||||
}
|
||||
@@ -864,8 +890,8 @@ class StreamChatClient {
|
||||
|
||||
String get _authType => _anonymous ? 'anonymous' : 'jwt';
|
||||
|
||||
String get _userAgent =>
|
||||
'stream-chat-dart-client-${CurrentPlatform.name}-${PACKAGE_VERSION.split('+')[0]}';
|
||||
String get _userAgent => 'stream-chat-dart-client-${CurrentPlatform.name}-'
|
||||
'${PACKAGE_VERSION.split('+')[0]}';
|
||||
|
||||
Map<String, String> get _commonQueryParams => {
|
||||
'user_id': state.user?.id,
|
||||
@@ -873,14 +899,15 @@ class StreamChatClient {
|
||||
'connection_id': _connectionId,
|
||||
};
|
||||
|
||||
/// Set the current user with an anonymous id, this triggers a connection to the API.
|
||||
/// It returns a [Future] that resolves when the connection is setup.
|
||||
/// Set the current user with an anonymous id, this triggers a connection to
|
||||
/// the API. It returns a [Future] that resolves when the connection is setup.
|
||||
@Deprecated(
|
||||
'Use `connectAnonymousUser` instead. Will be removed in Future releases')
|
||||
Future<Event> setAnonymousUser() => connectAnonymousUser();
|
||||
|
||||
/// Connects the current user with an anonymous id, this triggers a connection to the API.
|
||||
/// It returns a [Future] that resolves when the connection is setup.
|
||||
/// Connects the current user with an anonymous id, this triggers a connection
|
||||
/// to the API. It returns a [Future] that resolves when the connection is
|
||||
/// setup.
|
||||
Future<Event> connectAnonymousUser() async {
|
||||
if (_connectCompleter != null && !_connectCompleter.isCompleted) {
|
||||
logger.warning('Already connecting');
|
||||
@@ -923,14 +950,14 @@ class StreamChatClient {
|
||||
}
|
||||
|
||||
/// Closes the websocket connection and resets the client
|
||||
/// If [flushChatPersistence] is true the client deletes all offline user's data
|
||||
/// If [clearUser] is true the client unsets the current user
|
||||
/// If [flushChatPersistence] is true the client deletes all offline
|
||||
/// user's data. If [clearUser] is true the client unsets the current user
|
||||
Future<void> disconnect({
|
||||
bool flushChatPersistence = false,
|
||||
bool clearUser = false,
|
||||
}) async {
|
||||
logger.info(
|
||||
'Disconnecting flushOfflineStorage: $flushChatPersistence; clearUser: $clearUser');
|
||||
logger.info('Disconnecting flushOfflineStorage: $flushChatPersistence; '
|
||||
'clearUser: $clearUser');
|
||||
|
||||
await chatPersistenceClient?.disconnect(flush: flushChatPersistence);
|
||||
chatPersistenceClient = null;
|
||||
@@ -966,9 +993,7 @@ class StreamChatClient {
|
||||
final payload = <String, dynamic>{
|
||||
'filter_conditions': filter ?? {},
|
||||
'sort': sort,
|
||||
};
|
||||
|
||||
payload.addAll(defaultOptions);
|
||||
}..addAll(defaultOptions);
|
||||
|
||||
if (pagination != null) {
|
||||
payload.addAll(pagination.toJson());
|
||||
@@ -1016,7 +1041,7 @@ class StreamChatClient {
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
}(), 'Check incoming params.');
|
||||
|
||||
final payload = {
|
||||
'filter_conditions': filters,
|
||||
@@ -1041,15 +1066,14 @@ class StreamChatClient {
|
||||
String channelType, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return attachmentFileUploader.sendFile(
|
||||
file,
|
||||
channelId,
|
||||
channelType,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
attachmentFileUploader.sendFile(
|
||||
file,
|
||||
channelId,
|
||||
channelType,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// Send a [image] to the [channelId] of type [channelType]
|
||||
Future<SendImageResponse> sendImage(
|
||||
@@ -1058,15 +1082,14 @@ class StreamChatClient {
|
||||
String channelType, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return attachmentFileUploader.sendImage(
|
||||
image,
|
||||
channelId,
|
||||
channelType,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
attachmentFileUploader.sendImage(
|
||||
image,
|
||||
channelId,
|
||||
channelType,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// Delete a file from this channel
|
||||
Future<EmptyResponse> deleteFile(
|
||||
@@ -1074,14 +1097,13 @@ class StreamChatClient {
|
||||
String channelId,
|
||||
String channelType, {
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return attachmentFileUploader.deleteFile(
|
||||
url,
|
||||
channelId,
|
||||
channelType,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
attachmentFileUploader.deleteFile(
|
||||
url,
|
||||
channelId,
|
||||
channelType,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// Delete an image from this channel
|
||||
Future<EmptyResponse> deleteImage(
|
||||
@@ -1089,14 +1111,13 @@ class StreamChatClient {
|
||||
String channelId,
|
||||
String channelType, {
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return attachmentFileUploader.deleteImage(
|
||||
url,
|
||||
channelId,
|
||||
channelType,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
attachmentFileUploader.deleteImage(
|
||||
url,
|
||||
channelId,
|
||||
channelType,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// Add a device for Push Notifications.
|
||||
Future<EmptyResponse> addDevice(String id, PushProvider pushProvider) async {
|
||||
@@ -1146,9 +1167,8 @@ class StreamChatClient {
|
||||
}
|
||||
|
||||
/// Update or Create the given user object.
|
||||
Future<UpdateUsersResponse> updateUser(User user) async {
|
||||
return updateUsers([user]);
|
||||
}
|
||||
Future<UpdateUsersResponse> updateUser(User user) async =>
|
||||
updateUsers([user]);
|
||||
|
||||
/// Batch update a list of users
|
||||
Future<UpdateUsersResponse> updateUsers(List<User> users) async {
|
||||
@@ -1197,23 +1217,21 @@ class StreamChatClient {
|
||||
Future<EmptyResponse> shadowBan(
|
||||
String targetID, [
|
||||
Map<String, dynamic> options = const {},
|
||||
]) async {
|
||||
return banUser(targetID, {
|
||||
'shadow': true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
]) async =>
|
||||
banUser(targetID, {
|
||||
'shadow': true,
|
||||
...options,
|
||||
});
|
||||
|
||||
/// Removes shadow ban from a user
|
||||
Future<EmptyResponse> removeShadowBan(
|
||||
String targetID, [
|
||||
Map<String, dynamic> options = const {},
|
||||
]) async {
|
||||
return unbanUser(targetID, {
|
||||
'shadow': true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
]) async =>
|
||||
unbanUser(targetID, {
|
||||
'shadow': true,
|
||||
...options,
|
||||
});
|
||||
|
||||
/// Mutes a user
|
||||
Future<EmptyResponse> muteUser(String targetID) async {
|
||||
@@ -1312,7 +1330,7 @@ class StreamChatClient {
|
||||
throw ArgumentError('Invalid timeout or Expiration date');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
}(), 'Check whether time out is valid');
|
||||
|
||||
DateTime pinExpires;
|
||||
if (timeoutOrExpirationDate is DateTime) {
|
||||
@@ -1328,15 +1346,12 @@ class StreamChatClient {
|
||||
}
|
||||
|
||||
/// Unpins provided message
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) {
|
||||
return updateMessage(message.copyWith(pinned: false));
|
||||
}
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) =>
|
||||
updateMessage(message.copyWith(pinned: false));
|
||||
}
|
||||
|
||||
/// The class that handles the state of the channel listening to the events
|
||||
class ClientState {
|
||||
final _subscriptions = <StreamSubscription>[];
|
||||
|
||||
/// Creates a new instance listening to events and updating the state
|
||||
ClientState(this._client) {
|
||||
_subscriptions.addAll([
|
||||
@@ -1358,16 +1373,12 @@ class ClientState {
|
||||
.on()
|
||||
.where((event) => event.unreadChannels != null)
|
||||
.map((e) => e.unreadChannels)
|
||||
.listen((unreadChannels) {
|
||||
_unreadChannelsController.add(unreadChannels);
|
||||
}),
|
||||
.listen(_unreadChannelsController.add),
|
||||
_client
|
||||
.on()
|
||||
.where((event) => event.totalUnreadCount != null)
|
||||
.map((e) => e.totalUnreadCount)
|
||||
.listen((totalUnreadCount) {
|
||||
_totalUnreadCountController.add(totalUnreadCount);
|
||||
}),
|
||||
.listen(_totalUnreadCountController.add),
|
||||
]);
|
||||
|
||||
_listenChannelDeleted();
|
||||
@@ -1377,6 +1388,8 @@ class ClientState {
|
||||
_listenUserUpdated();
|
||||
}
|
||||
|
||||
final _subscriptions = <StreamSubscription>[];
|
||||
|
||||
/// Used internally for optimistic update of unread count
|
||||
set totalUnreadCount(int unreadCount) {
|
||||
_totalUnreadCountController?.add(unreadCount ?? 0);
|
||||
|
||||
@@ -101,18 +101,17 @@ abstract class ChatPersistenceClient {
|
||||
Future<void> updateChannelQueries(
|
||||
Map<String, dynamic> filter,
|
||||
List<String> cids,
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
bool clearQueryCache,
|
||||
);
|
||||
|
||||
/// Remove a message by [messageId]
|
||||
Future<void> deleteMessageById(String messageId) {
|
||||
return deleteMessageByIds([messageId]);
|
||||
}
|
||||
Future<void> deleteMessageById(String messageId) =>
|
||||
deleteMessageByIds([messageId]);
|
||||
|
||||
/// Remove a pinned message by [messageId]
|
||||
Future<void> deletePinnedMessageById(String messageId) {
|
||||
return deletePinnedMessageByIds([messageId]);
|
||||
}
|
||||
Future<void> deletePinnedMessageById(String messageId) =>
|
||||
deletePinnedMessageByIds([messageId]);
|
||||
|
||||
/// Remove a message by [messageIds]
|
||||
Future<void> deleteMessageByIds(List<String> messageIds);
|
||||
@@ -121,14 +120,11 @@ abstract class ChatPersistenceClient {
|
||||
Future<void> deletePinnedMessageByIds(List<String> messageIds);
|
||||
|
||||
/// Remove a message by channel [cid]
|
||||
Future<void> deleteMessageByCid(String cid) {
|
||||
return deleteMessageByCids([cid]);
|
||||
}
|
||||
Future<void> deleteMessageByCid(String cid) => deleteMessageByCids([cid]);
|
||||
|
||||
/// Remove a pinned message by channel [cid]
|
||||
Future<void> deletePinnedMessageByCid(String cid) {
|
||||
return deletePinnedMessageByCids([cid]);
|
||||
}
|
||||
Future<void> deletePinnedMessageByCid(String cid) async =>
|
||||
deletePinnedMessageByCids([cid]);
|
||||
|
||||
/// Remove a message by message [cids]
|
||||
Future<void> deleteMessageByCids(List<String> cids);
|
||||
@@ -175,9 +171,8 @@ abstract class ChatPersistenceClient {
|
||||
Future<void> deleteMembersByCids(List<String> cids);
|
||||
|
||||
/// Update the channel state data using [channelState]
|
||||
Future<void> updateChannelState(ChannelState channelState) {
|
||||
return updateChannelStates([channelState]);
|
||||
}
|
||||
Future<void> updateChannelState(ChannelState channelState) =>
|
||||
updateChannelStates([channelState]);
|
||||
|
||||
/// Update list of channel states
|
||||
Future<void> updateChannelStates(List<ChannelState> channelStates) async {
|
||||
@@ -195,31 +190,31 @@ abstract class ChatPersistenceClient {
|
||||
deleteMembers,
|
||||
]);
|
||||
|
||||
final channels = channelStates.map((it) {
|
||||
return it.channel;
|
||||
}).where((it) => it != null);
|
||||
final channels =
|
||||
channelStates.map((it) => it.channel).where((it) => it != null);
|
||||
|
||||
final reactions = channelStates.expand((it) => it.messages).expand((it) {
|
||||
return [
|
||||
if (it.ownReactions != null)
|
||||
...it.ownReactions.where((r) => r.userId != null),
|
||||
if (it.latestReactions != null)
|
||||
...it.latestReactions.where((r) => r.userId != null)
|
||||
];
|
||||
}).where((it) => it != null);
|
||||
final reactions = channelStates
|
||||
.expand((it) => it.messages)
|
||||
.expand((it) => [
|
||||
if (it.ownReactions != null)
|
||||
...it.ownReactions.where((r) => r.userId != null),
|
||||
if (it.latestReactions != null)
|
||||
...it.latestReactions.where((r) => r.userId != null)
|
||||
])
|
||||
.where((it) => it != null);
|
||||
|
||||
final users = channelStates
|
||||
.map((cs) => [
|
||||
cs.channel?.createdBy,
|
||||
...cs.messages?.map((m) {
|
||||
return [
|
||||
m.user,
|
||||
if (m.latestReactions != null)
|
||||
...m.latestReactions.map((r) => r.user),
|
||||
if (m.ownReactions != null)
|
||||
...m.ownReactions.map((r) => r.user),
|
||||
];
|
||||
})?.expand((v) => v),
|
||||
...cs.messages
|
||||
?.map((m) => [
|
||||
m.user,
|
||||
if (m.latestReactions != null)
|
||||
...m.latestReactions.map((r) => r.user),
|
||||
if (m.ownReactions != null)
|
||||
...m.ownReactions.map((r) => r.user),
|
||||
])
|
||||
?.expand((v) => v),
|
||||
if (cs.read != null) ...cs.read.map((r) => r.user),
|
||||
if (cs.members != null) ...cs.members.map((m) => m.user),
|
||||
])
|
||||
|
||||
@@ -2,6 +2,13 @@ import 'dart:convert';
|
||||
|
||||
/// Exception related to api calls
|
||||
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
|
||||
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
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
@@ -48,7 +48,6 @@ class ApiError extends Error {
|
||||
body.hashCode ^ jsonData.hashCode ^ status.hashCode ^ _code.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ApiError{body: $body, jsonData: $jsonData, status: $status, code: $_code}';
|
||||
}
|
||||
String toString() => 'ApiError{body: $body, jsonData: $jsonData, '
|
||||
'status: $status, code: $_code}';
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/// Useful extension functions for [Map]
|
||||
extension MapX on Map {
|
||||
/// Returns a new map with null keys or values removed
|
||||
Map<String, dynamic> get nullProtected {
|
||||
return {...this}..removeWhere((key, value) => key == null || value == null);
|
||||
}
|
||||
Map<String, dynamic> get nullProtected =>
|
||||
{...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
|
||||
@JsonSerializable()
|
||||
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
|
||||
final String name;
|
||||
|
||||
@@ -20,12 +26,6 @@ class Action {
|
||||
/// The value of the action
|
||||
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
|
||||
Map<String, dynamic> toJson() => _$ActionToJson(this);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,61 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
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/serialization.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'action.dart';
|
||||
import 'serialization.dart';
|
||||
|
||||
part 'attachment.g.dart';
|
||||
|
||||
/// The class that contains the information about an attachment
|
||||
@JsonSerializable(includeIfNull: false)
|
||||
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;
|
||||
|
||||
///The link to which the attachment message points to.
|
||||
@@ -21,10 +64,12 @@ class Attachment {
|
||||
/// The attachment 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;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Optional text that appears above the attachment block
|
||||
@@ -33,7 +78,8 @@ class Attachment {
|
||||
/// The original URL that was used to scrape this attachment.
|
||||
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 footerIcon;
|
||||
final String footer;
|
||||
@@ -100,56 +146,11 @@ class Attachment {
|
||||
'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
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
_$AttachmentToJson(this), topLevelFields)
|
||||
..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
|
||||
Map<String, dynamic> toData() => Serialization.moveFromExtraDataToRoot(
|
||||
_$AttachmentToJson(this), topLevelFields + dbSpecificTopLevelFields);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
part 'attachment_file.freezed.dart';
|
||||
|
||||
part 'attachment_file.g.dart';
|
||||
|
||||
/// Union class to hold various [UploadState] of a attachment.
|
||||
@@ -57,8 +56,12 @@ class AttachmentFile {
|
||||
this.size,
|
||||
});
|
||||
|
||||
/// 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.
|
||||
/// Create a new instance from a json
|
||||
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);
|
||||
/// ```
|
||||
@@ -67,8 +70,8 @@ class AttachmentFile {
|
||||
/// File name including its extension.
|
||||
final String name;
|
||||
|
||||
/// Byte data for this file. Particularly useful if you want to manipulate its data
|
||||
/// or easily upload to somewhere else.
|
||||
/// Byte data for this file. Particularly useful if you want to manipulate
|
||||
/// its data or easily upload to somewhere else.
|
||||
@JsonKey(toJson: _toString, fromJson: _fromString)
|
||||
final Uint8List bytes;
|
||||
|
||||
@@ -78,11 +81,6 @@ class AttachmentFile {
|
||||
/// File extension for this file.
|
||||
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
|
||||
Map<String, dynamic> toJson() => _$AttachmentFileToJson(this);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'command.dart';
|
||||
|
||||
import 'package:stream_chat/src/models/command.dart';
|
||||
part 'channel_config.g.dart';
|
||||
|
||||
/// The class that contains the information about the configuration of a channel
|
||||
@JsonSerializable()
|
||||
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
|
||||
final String automod;
|
||||
|
||||
@@ -55,30 +77,6 @@ class ChannelConfig {
|
||||
/// True if urls appears as attachments
|
||||
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
|
||||
Map<String, dynamic> toJson() => _$ChannelConfigToJson(this);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'channel_config.dart';
|
||||
import 'serialization.dart';
|
||||
import 'user.dart';
|
||||
import 'package:stream_chat/src/models/channel_config.dart';
|
||||
import 'package:stream_chat/src/models/serialization.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
part 'channel_model.g.dart';
|
||||
|
||||
/// The class that contains the information about a channel
|
||||
@JsonSerializable()
|
||||
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
|
||||
final String id;
|
||||
|
||||
@@ -76,40 +97,15 @@ class ChannelModel {
|
||||
'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
|
||||
String get name =>
|
||||
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
|
||||
Map<String, dynamic> toJson() {
|
||||
return Serialization.moveFromExtraDataToRoot(
|
||||
_$ChannelModelToJson(this),
|
||||
topLevelFields,
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
_$ChannelModelToJson(this),
|
||||
topLevelFields,
|
||||
);
|
||||
|
||||
/// Creates a copy of [ChannelModel] with specified attributes overridden.
|
||||
ChannelModel copyWith({
|
||||
@@ -143,8 +139,8 @@ class ChannelModel {
|
||||
team: team ?? this.team,
|
||||
);
|
||||
|
||||
/// Returns a new [ChannelModel] that is a combination of this channelModel and the given
|
||||
/// [other] channelModel.
|
||||
/// Returns a new [ChannelModel] that is a combination of this channelModel
|
||||
/// and the given [other] channelModel.
|
||||
ChannelModel merge(ChannelModel other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../models/read.dart';
|
||||
import '../models/user.dart';
|
||||
import 'channel_model.dart';
|
||||
import 'member.dart';
|
||||
import 'message.dart';
|
||||
import 'package:stream_chat/src/models/channel_model.dart';
|
||||
import 'package:stream_chat/src/models/member.dart';
|
||||
import 'package:stream_chat/src/models/message.dart';
|
||||
import 'package:stream_chat/src/models/read.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
part 'channel_state.g.dart';
|
||||
|
||||
/// The class that contains the information about a channel
|
||||
@JsonSerializable()
|
||||
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
|
||||
final ChannelModel channel;
|
||||
|
||||
@@ -32,17 +42,6 @@ class ChannelState {
|
||||
/// The list of channel reads
|
||||
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
|
||||
static ChannelState fromJson(Map<String, dynamic> json) =>
|
||||
_$ChannelStateFromJson(json);
|
||||
|
||||
@@ -5,15 +5,6 @@ part 'command.g.dart';
|
||||
/// The class that contains the information about a command
|
||||
@JsonSerializable()
|
||||
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
|
||||
Command({
|
||||
this.name,
|
||||
@@ -25,6 +16,15 @@ class Command {
|
||||
factory Command.fromJson(Map<String, dynamic> 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
|
||||
Map<String, dynamic> toJson() => _$CommandToJson(this);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,6 @@ part 'device.g.dart';
|
||||
/// The class that contains the information about a device
|
||||
@JsonSerializable()
|
||||
class Device {
|
||||
/// The id of the device
|
||||
final String id;
|
||||
|
||||
/// The notification push provider
|
||||
final String pushProvider;
|
||||
|
||||
/// Constructor used for json serialization
|
||||
Device({
|
||||
this.id,
|
||||
@@ -20,6 +14,12 @@ class Device {
|
||||
/// Create a new instance from a 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
|
||||
Map<String, dynamic> toJson() => _$DeviceToJson(this);
|
||||
}
|
||||
|
||||
@@ -4,17 +4,40 @@ import 'package:stream_chat/src/models/message.dart';
|
||||
import 'package:stream_chat/src/models/serialization.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';
|
||||
|
||||
/// The class that contains the information about an event
|
||||
@JsonSerializable()
|
||||
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
|
||||
/// [EventType] contains some predefined constant types
|
||||
final String type;
|
||||
@@ -71,27 +94,6 @@ class Event {
|
||||
@JsonKey(includeIfNull: false)
|
||||
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.
|
||||
/// Useful for [Serialization] methods.
|
||||
static final topLevelFields = [
|
||||
@@ -114,15 +116,6 @@ class Event {
|
||||
'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
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
_$EventToJson(this),
|
||||
@@ -133,16 +126,6 @@ class Event {
|
||||
/// The channel embedded in the event object
|
||||
@JsonSerializable()
|
||||
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
|
||||
EventChannel({
|
||||
this.members,
|
||||
@@ -174,12 +157,21 @@ class EventChannel extends ChannelModel {
|
||||
);
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory EventChannel.fromJson(Map<String, dynamic> json) {
|
||||
return _$EventChannelFromJson(Serialization.moveToExtraDataFromRoot(
|
||||
json,
|
||||
topLevelFields,
|
||||
));
|
||||
}
|
||||
factory EventChannel.fromJson(Map<String, dynamic> json) =>
|
||||
_$EventChannelFromJson(Serialization.moveToExtraDataFromRoot(
|
||||
json,
|
||||
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
|
||||
@override
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../models/user.dart';
|
||||
import 'package:stream_chat/src/models/user.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()
|
||||
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
|
||||
final User user;
|
||||
|
||||
@@ -40,29 +63,6 @@ class Member {
|
||||
/// The last date of update
|
||||
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.
|
||||
Member copyWith({
|
||||
User user,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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 'attachment.dart';
|
||||
import 'reaction.dart';
|
||||
import 'serialization.dart';
|
||||
import 'user.dart';
|
||||
|
||||
part 'message.g.dart';
|
||||
|
||||
class _PinExpires {
|
||||
@@ -29,9 +28,11 @@ enum MessageSendingStatus {
|
||||
failed,
|
||||
|
||||
/// Message failed to updated
|
||||
// ignore: constant_identifier_names
|
||||
failed_update,
|
||||
|
||||
/// Message failed to delete
|
||||
// ignore: constant_identifier_names
|
||||
failed_delete,
|
||||
|
||||
/// Message correctly sent
|
||||
@@ -41,7 +42,45 @@ enum MessageSendingStatus {
|
||||
/// The class that contains the information about a message
|
||||
@JsonSerializable()
|
||||
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;
|
||||
|
||||
/// The text of this message
|
||||
@@ -55,7 +94,8 @@ class Message {
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
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)
|
||||
final List<Attachment> attachments;
|
||||
|
||||
@@ -188,43 +228,6 @@ class Message {
|
||||
'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
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
_$MessageToJson(this), topLevelFields);
|
||||
@@ -267,7 +270,7 @@ class Message {
|
||||
throw ArgumentError('`pinExpires` can only be set as DateTime or null');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
}(), 'Validate type for pinExpires');
|
||||
return Message(
|
||||
id: id ?? this.id,
|
||||
text: text ?? this.text,
|
||||
@@ -300,8 +303,8 @@ class Message {
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a new [Message] that is a combination of this message and the given
|
||||
/// [other] message.
|
||||
/// Returns a new [Message] that is a combination of this message and the
|
||||
/// given [other] message.
|
||||
Message merge(Message other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
@@ -344,6 +347,12 @@ class TranslatedMessage extends Message {
|
||||
/// Constructor used for json serialization
|
||||
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
|
||||
final Map<String, String> i18n;
|
||||
|
||||
@@ -354,13 +363,6 @@ class TranslatedMessage extends Message {
|
||||
...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
|
||||
@override
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/models/channel_model.dart';
|
||||
|
||||
import 'serialization.dart';
|
||||
import 'user.dart';
|
||||
import 'package:stream_chat/src/models/serialization.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
part 'mute.g.dart';
|
||||
|
||||
/// The class that contains the information about a muted user
|
||||
@JsonSerializable()
|
||||
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
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final User user;
|
||||
@@ -25,12 +30,6 @@ class Mute {
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
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
|
||||
Map<String, dynamic> toJson() => _$MuteToJson(this);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'device.dart';
|
||||
import 'mute.dart';
|
||||
import 'serialization.dart';
|
||||
import 'user.dart';
|
||||
import 'package:stream_chat/src/models/device.dart';
|
||||
import 'package:stream_chat/src/models/mute.dart';
|
||||
import 'package:stream_chat/src/models/serialization.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
part 'own_user.g.dart';
|
||||
|
||||
@@ -11,6 +10,36 @@ part 'own_user.g.dart';
|
||||
/// This object can be found in [Event]
|
||||
@JsonSerializable()
|
||||
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
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final List<Device> devices;
|
||||
@@ -42,42 +71,8 @@ class OwnUser extends User {
|
||||
...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
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return Serialization.moveFromExtraDataToRoot(
|
||||
_$OwnUserToJson(this), topLevelFields);
|
||||
}
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
_$OwnUserToJson(this), topLevelFields);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'serialization.dart';
|
||||
import 'user.dart';
|
||||
import 'package:stream_chat/src/models/serialization.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
part 'reaction.g.dart';
|
||||
|
||||
/// The class that defines a reaction
|
||||
@JsonSerializable()
|
||||
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
|
||||
final String messageId;
|
||||
|
||||
@@ -43,28 +57,9 @@ class Reaction {
|
||||
'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
|
||||
Map<String, dynamic> toJson() {
|
||||
return Serialization.moveFromExtraDataToRoot(
|
||||
_$ReactionToJson(this), topLevelFields);
|
||||
}
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
_$ReactionToJson(this), topLevelFields);
|
||||
|
||||
/// Creates a copy of [Reaction] with specified attributes overridden.
|
||||
Reaction copyWith({
|
||||
@@ -75,20 +70,19 @@ class Reaction {
|
||||
String userId,
|
||||
int score,
|
||||
Map<String, dynamic> extraData,
|
||||
}) {
|
||||
return Reaction(
|
||||
messageId: messageId ?? this.messageId,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
type: type ?? this.type,
|
||||
user: user ?? this.user,
|
||||
userId: userId ?? this.userId,
|
||||
score: score ?? this.score,
|
||||
extraData: extraData ?? this.extraData,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
Reaction(
|
||||
messageId: messageId ?? this.messageId,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
type: type ?? this.type,
|
||||
user: user ?? this.user,
|
||||
userId: userId ?? this.userId,
|
||||
score: score ?? this.score,
|
||||
extraData: extraData ?? this.extraData,
|
||||
);
|
||||
|
||||
/// Returns a new [Reaction] that is a combination of this reaction and the given
|
||||
/// [other] reaction.
|
||||
/// Returns a new [Reaction] that is a combination of this reaction and the
|
||||
/// given [other] reaction.
|
||||
Reaction merge(Reaction other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'user.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
part 'read.g.dart';
|
||||
|
||||
/// The class that defines a read event
|
||||
@JsonSerializable()
|
||||
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
|
||||
Read({
|
||||
this.lastRead,
|
||||
@@ -26,6 +16,15 @@ class Read {
|
||||
/// Create a new instance from a 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
|
||||
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
|
||||
// ignore: prefer_void_to_null
|
||||
Null readonly(_) => null;
|
||||
|
||||
/// Helper class for serialization to and from json
|
||||
@@ -9,9 +10,8 @@ class Serialization {
|
||||
static const Function readOnly = readonly;
|
||||
|
||||
/// List of users to list of userIds
|
||||
static List<String> userIds(List<User> users) {
|
||||
return users?.map((u) => u.id)?.toList();
|
||||
}
|
||||
static List<String> userIds(List<User> users) =>
|
||||
users?.map((u) => u.id)?.toList();
|
||||
|
||||
/// Takes unknown json keys and puts them in the `extra_data` key
|
||||
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(
|
||||
Map<String, dynamic> json,
|
||||
List<String> topLevelFields,
|
||||
|
||||
@@ -1,12 +1,53 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'serialization.dart';
|
||||
import 'package:stream_chat/src/models/serialization.dart';
|
||||
|
||||
part 'user.g.dart';
|
||||
|
||||
/// The class that defines the user model
|
||||
@JsonSerializable()
|
||||
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
|
||||
final String id;
|
||||
|
||||
@@ -42,43 +83,8 @@ class User {
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, dynamic> extraData;
|
||||
|
||||
/// Known top level fields.
|
||||
/// Useful for [Serialization] methods.
|
||||
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,
|
||||
});
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
/// Shortcut for user name
|
||||
String get name =>
|
||||
@@ -86,23 +92,12 @@ class User {
|
||||
? extraData['name']
|
||||
: 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
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is User && runtimeType == other.runtimeType && id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
/// Serialize to json
|
||||
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.io) 'platform_detector_io.dart';
|
||||
|
||||
/// Possible platforms
|
||||
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
|
||||
@@ -31,42 +31,42 @@ class CurrentPlatform {
|
||||
CurrentPlatform._();
|
||||
|
||||
/// 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
|
||||
static bool get isIos => type == PlatformType.Ios;
|
||||
static bool get isIos => type == PlatformType.ios;
|
||||
|
||||
/// 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
|
||||
static bool get isMacOS => type == PlatformType.MacOS;
|
||||
static bool get isMacOS => type == PlatformType.macOS;
|
||||
|
||||
/// 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
|
||||
static bool get isLinux => type == PlatformType.Linux;
|
||||
static bool get isLinux => type == PlatformType.linux;
|
||||
|
||||
/// 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
|
||||
static String get name {
|
||||
switch (type) {
|
||||
case PlatformType.Android:
|
||||
case PlatformType.android:
|
||||
return 'android';
|
||||
case PlatformType.Ios:
|
||||
case PlatformType.ios:
|
||||
return 'ios';
|
||||
case PlatformType.Web:
|
||||
case PlatformType.web:
|
||||
return 'web';
|
||||
case PlatformType.MacOS:
|
||||
case PlatformType.macOS:
|
||||
return 'macos';
|
||||
case PlatformType.Windows:
|
||||
case PlatformType.windows:
|
||||
return 'windows';
|
||||
case PlatformType.Linux:
|
||||
case PlatformType.linux:
|
||||
return 'linux';
|
||||
case PlatformType.Fuchsia:
|
||||
case PlatformType.fuchsia:
|
||||
return 'fuchsia';
|
||||
default:
|
||||
return '';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:io';
|
||||
import 'platform_detector.dart';
|
||||
import 'package:stream_chat/src/platform_detector/platform_detector.dart';
|
||||
|
||||
/// Version running on native systems
|
||||
PlatformType get currentPlatform {
|
||||
if (Platform.isWindows) return PlatformType.Windows;
|
||||
if (Platform.isFuchsia) return PlatformType.Fuchsia;
|
||||
if (Platform.isMacOS) return PlatformType.MacOS;
|
||||
if (Platform.isLinux) return PlatformType.Linux;
|
||||
if (Platform.isIOS) return PlatformType.Ios;
|
||||
return PlatformType.Android;
|
||||
if (Platform.isWindows) return PlatformType.windows;
|
||||
if (Platform.isFuchsia) return PlatformType.fuchsia;
|
||||
if (Platform.isMacOS) return PlatformType.macOS;
|
||||
if (Platform.isLinux) return PlatformType.linux;
|
||||
if (Platform.isIOS) return PlatformType.ios;
|
||||
return PlatformType.android;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'platform_detector.dart';
|
||||
import 'package:stream_chat/src/platform_detector/platform_detector.dart';
|
||||
|
||||
/// Stub implementation
|
||||
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
|
||||
PlatformType get currentPlatform => PlatformType.Web;
|
||||
PlatformType get currentPlatform => PlatformType.web;
|
||||
|
||||
@@ -12,7 +12,9 @@ export './src/api/requests.dart';
|
||||
export './src/api/responses.dart';
|
||||
export './src/attachment_file_uploader.dart' show AttachmentFileUploader;
|
||||
export './src/client.dart';
|
||||
export './src/db/chat_persistence_client.dart';
|
||||
export './src/event_type.dart';
|
||||
export './src/extensions/string_extension.dart';
|
||||
export './src/models/action.dart';
|
||||
export './src/models/attachment.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/read.dart';
|
||||
export './src/models/user.dart';
|
||||
export './src/extensions/string_extension.dart';
|
||||
export './src/db/chat_persistence_client.dart';
|
||||
|
||||
@@ -2,4 +2,5 @@ import 'package:stream_chat/src/client.dart';
|
||||
|
||||
/// Current package version
|
||||
/// Used in [StreamChatClient] to build the `x-stream-client` header
|
||||
// ignore: constant_identifier_names
|
||||
const PACKAGE_VERSION = '1.3.2+1-beta';
|
||||
|
||||
@@ -9,23 +9,24 @@ environment:
|
||||
sdk: ">=2.7.0 <3.0.0"
|
||||
|
||||
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
|
||||
rxdart: ^0.25.0
|
||||
collection: ^1.14.13
|
||||
pedantic: ^1.9.2
|
||||
meta: ^1.2.4
|
||||
mime: ^0.9.7
|
||||
dio: ^3.0.10
|
||||
freezed_annotation: ^0.12.0
|
||||
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:
|
||||
build_runner: ^1.10.0
|
||||
json_serializable: ^3.3.0
|
||||
test: ^1.15.7
|
||||
mockito: ^4.1.1
|
||||
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 = {
|
||||
'payload': json.encode({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// It shows a date divider depending on the date difference
|
||||
|
||||
Reference in New Issue
Block a user