diff --git a/packages/stream_chat/analysis_options.yaml b/packages/stream_chat/analysis_options.yaml index 26001160..ddc691d0 100644 --- a/packages/stream_chat/analysis_options.yaml +++ b/packages/stream_chat/analysis_options.yaml @@ -1,11 +1,11 @@ analyzer: - exclude: + exclude: - lib/**/*.g.dart - lib/**/*.freezed.dart - example/* - test/* -linter: - rules: +linter: + rules: - always_use_package_imports - avoid_empty_else - avoid_relative_lib_imports @@ -14,7 +14,6 @@ linter: - cancel_subscriptions - close_sinks - control_flow_in_finally - - diagnostic_describe_all_properties - empty_statements - hash_and_equals - invariant_booleans @@ -33,7 +32,6 @@ linter: - 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 diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 1701fed8..dcb11316 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1210,9 +1210,10 @@ class ChannelClientState { this._channel, ChannelState channelState, //ignore: unnecessary_parenthesis - ) : _debouncedUpdatePersistenceChannelState = ((ChannelState state) { - _channel?._client?.chatPersistenceClient?.updateChannelState(state); - }).debounced(const Duration(seconds: 1)) { + ) : _debouncedUpdatePersistenceChannelState = ((ChannelState state) => + _channel?._client?.chatPersistenceClient + ?.updateChannelState(state)) + .debounced(const Duration(seconds: 1)) { retryQueue = RetryQueue( channel: _channel, logger: Logger('RETRY QUEUE ${_channel.cid}'), diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml new file mode 100644 index 00000000..545d5492 --- /dev/null +++ b/packages/stream_chat_flutter_core/analysis_options.yaml @@ -0,0 +1,145 @@ +analyzer: + exclude: + - lib/**/*.g.dart + - lib/**/*.freezed.dart + - example/* + - test/* +linter: + rules: + - always_use_package_imports + - avoid_empty_else + - avoid_relative_lib_imports + - avoid_slow_async_io + - avoid_types_as_parameter_names + - cancel_subscriptions + - close_sinks + - control_flow_in_finally + - empty_statements + - hash_and_equals + - 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 + - unnecessary_statements + - unrelated_type_equality_checks + - omit_local_variable_types + - use_key_in_widget_constructors + - valid_regexps + - always_declare_return_types + - 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 \ No newline at end of file diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 816bb052..8a41332d 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -5,11 +5,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/channels_bloc.dart'; +import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; -import 'stream_chat_core.dart'; - -/// [ChannelListCore] is a simplified class that allows fetching a list of channels while exposing UI builders. +/// [ChannelListCore] is a simplified class that allows fetching a list of +/// channels while exposing UI builders. /// A [ChannelListController] is used to reload and paginate data. /// /// @@ -52,10 +52,11 @@ import 'stream_chat_core.dart'; /// } /// ``` /// -/// Make sure to have a [StreamChatCore] ancestor in order to provide the information about the channels. +/// Make sure to have a [StreamChatCore] ancestor in order to provide the +/// information about the channels. class ChannelListCore extends StatefulWidget { /// Instantiate a new ChannelListView - ChannelListCore({ + const ChannelListCore({ Key key, @required this.errorBuilder, @required this.emptyBuilder, @@ -65,18 +66,31 @@ class ChannelListCore extends StatefulWidget { this.options, this.sort, this.pagination = const PaginationParams( - offset: 0, limit: 25, ), this.channelListController, - }) : assert(errorBuilder != null), - assert(emptyBuilder != null), - assert(loadingBuilder != null), - assert(listBuilder != null), + }) : assert( + errorBuilder != null, + 'Parameter errorBuilder should not be null', + ), + assert( + emptyBuilder != null, + 'Parameter emptyBuilder should not be null', + ), + assert( + loadingBuilder != null, + 'Parameter loadingBuilder should not be null', + ), + assert( + listBuilder != null, + 'Parameter listBuilder should not be null', + ), super(key: key); /// A [ChannelListController] allows reloading and pagination. - /// Use [ChannelListController.loadData] and [ChannelListController.paginateData] respectively for reloading and pagination. + /// Use [ChannelListController.loadData] and + /// [ChannelListController.paginateData] respectively for reloading and + /// pagination. final ChannelListController channelListController; /// The builder that will be used in case of error @@ -103,9 +117,10 @@ class ChannelListCore extends StatefulWidget { final Map options; /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. - /// Direction can be ascending or descending. + /// Sorting is based on field and direction, multiple sorting options can be + /// provided. + /// You can sort based on last_updated, last_message_at, updated_at, created + /// _at or member_count. Direction can be ascending or descending. final List> sort; /// Pagination parameters @@ -128,24 +143,23 @@ class _ChannelListCoreState extends State { StreamBuilder> _buildListView( ChannelsBlocState channelsBlocState, - ) { - return StreamBuilder>( - stream: channelsBlocState.channelsStream, - builder: (context, snapshot) { - if (snapshot.hasError) { - return _buildErrorWidget(snapshot, context, channelsBlocState); - } - if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } - final channels = snapshot.data; - if (channels.isEmpty) { - return widget.emptyBuilder(context); - } - return widget.listBuilder(context, channels); - }, - ); - } + ) => + StreamBuilder>( + stream: channelsBlocState.channelsStream, + builder: (context, snapshot) { + if (snapshot.hasError) { + return _buildErrorWidget(snapshot, context, channelsBlocState); + } + if (!snapshot.hasData) { + return widget.loadingBuilder(context); + } + final channels = snapshot.data; + if (channels.isEmpty) { + return widget.emptyBuilder(context); + } + return widget.listBuilder(context, channels); + }, + ); Widget _buildErrorWidget( AsyncSnapshot> snapshot, @@ -223,14 +237,15 @@ class _ChannelListCoreState extends State { } } -/// Controller used for loading more data and controlling pagination in [ChannelListCore]. +/// Controller used for loading more data and controlling pagination in +/// [ChannelListCore]. class ChannelListController { - /// This function calls Stream's servers to load a list of channels. If there is existing data, - /// calling this function causes a reload. + /// This function calls Stream's servers to load a list of channels. + /// If there is existing data, calling this function causes a reload. AsyncCallback loadData; - /// This function is used to load another page of data. Note, [loadData] should be - /// used to populate the initial page of data. Calling [paginateData] performs a query - /// to load subsequent pages. + /// This function is used to load another page of data. Note, [loadData] + /// should be used to populate the initial page of data. Calling + /// [paginateData] performs a query to load subsequent pages. AsyncCallback paginateData; } diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart index 1ab84878..66c2b8ea 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; @@ -16,26 +17,29 @@ import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; /// /// API docs: https://getstream.io/chat/docs/flutter-dart/query_channels/ class ChannelsBloc extends StatefulWidget { - /// Creates a new [ChannelsBloc]. The parameter [child] must be supplied and not null. + /// Creates a new [ChannelsBloc]. The parameter [child] must be supplied and + /// not null. const ChannelsBloc({ Key key, @required this.child, this.lockChannelsOrder = false, this.channelsComparator, this.shouldAddChannel, - }) : assert(child != null), + }) : assert(child != null, 'Parameter child should not be null.'), super(key: key); /// The widget child final Widget child; - /// Set this to true to prevent channels to be brought to the top of the list when a new message arrives + /// Set this to true to prevent channels to be brought to the top of the list + /// when a new message arrives final bool lockChannelsOrder; /// Comparator used to sort the channels when a message.new event is received final Comparator channelsComparator; - /// Function used to evaluate if a channel should be added to the list when a message.new event is received + /// Function used to evaluate if a channel should be added to the list when a + /// message.new event is received final bool Function(Event) shouldAddChannel; @override @@ -170,13 +174,14 @@ class ChannelsBlocState extends State _channelsController.add(newChannels); } })); - + // ignore: cascade_invocations _subscriptions.add(client .on( EventType.channelDeleted, EventType.notificationRemovedFromChannel, ) .listen((e) { + // ignore: cascade_invocations final channel = e.channel; _channelsController.add(List.from( (channels ?? [])..removeWhere((c) => c.cid == channel.cid))); diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index 1820827f..725e19f3 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -1,13 +1,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +// ignore: constant_identifier_names enum _LoadingStatus { LOADING, STABLE } /// Wrapper around a [Scrollable] which triggers [onEndOfPage]/[onStartOfPage] the Scrollable /// reaches to the start or end of the view extent. class LazyLoadScrollView extends StatefulWidget { - /// Creates a new instance of [LazyLoadScrollView]. The parameter [child] must be - /// supplied and not null. + /// Creates a new instance of [LazyLoadScrollView]. The parameter [child] + /// must be supplied and not null. const LazyLoadScrollView({ Key key, @required this.child, @@ -17,7 +18,7 @@ class LazyLoadScrollView extends StatefulWidget { this.onPageScrollEnd, this.onInBetweenOfPage, this.scrollOffset = 100, - }) : assert(child != null), + }) : assert(child != null, 'Parameter child should not be null'), super(key: key); /// The [Widget] that this widget watches for changes on @@ -47,15 +48,13 @@ class LazyLoadScrollView extends StatefulWidget { class _LazyLoadScrollViewState extends State { _LoadingStatus _loadMoreStatus = _LoadingStatus.STABLE; - double _scrollPosition = 0.0; + double _scrollPosition = 0; @override - Widget build(BuildContext context) { - return NotificationListener( - child: widget.child, - onNotification: _onNotification, - ); - } + Widget build(BuildContext context) => NotificationListener( + onNotification: _onNotification, + child: widget.child, + ); bool _onNotification(Notification notification) { if (notification is ScrollStartNotification) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 87f821c1..a5e9587e 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -1,12 +1,15 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/stream_channel.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; -import 'stream_channel.dart'; -/// [MessageListCore] is a simplified class that allows fetching a list of messages while exposing UI builders. +/// [MessageListCore] is a simplified class that allows fetching a list of +/// messages while exposing UI builders. +/// /// A [MessageListController] is used to paginate data. /// /// ```dart @@ -50,12 +53,14 @@ import 'stream_channel.dart'; /// ``` /// /// -/// Make sure to have a [StreamChannel] ancestor in order to provide the information about the channels. +/// Make sure to have a [StreamChannel] ancestor in order to provide the +/// information about the channels. +/// /// The widget uses a [ListView.custom] to render the list of channels. /// class MessageListCore extends StatefulWidget { /// Instantiate a new [MessageListView]. - MessageListCore({ + const MessageListCore({ Key key, @required this.loadingBuilder, @required this.emptyBuilder, @@ -65,10 +70,16 @@ class MessageListCore extends StatefulWidget { this.parentMessage, this.messageListController, this.messageFilter, - }) : assert(loadingBuilder != null), - assert(emptyBuilder != null), - assert(messageListBuilder != null), - assert(errorWidgetBuilder != null), + }) : assert(loadingBuilder != null, 'loadingBuilder should not be null'), + assert(emptyBuilder != null, 'emptyBuilder should not be null'), + assert( + messageListBuilder != null, + 'messageListBuilder should not be null', + ), + assert( + errorWidgetBuilder != null, + 'errorWidgetBuilder should not be null', + ), super(key: key); /// A [MessageListController] allows pagination. @@ -84,12 +95,15 @@ class MessageListCore extends StatefulWidget { /// Function used to build an empty widget final WidgetBuilder emptyBuilder; - /// Callback triggered when an error occurs while performing the given request. - /// This parameter can be used to display an error message to users in the event - /// of a connection failure. + /// Callback triggered when an error occurs while performing the given + /// request. + /// + /// This parameter can be used to display an error message to users in the + /// event of a connection failure. final ErrorBuilder errorWidgetBuilder; - /// If true will show a scroll to bottom message when there are new messages and the scroll offset is not zero + /// If true will show a scroll to bottom message when there are new messages + /// and the scroll offset is not zero. final bool showScrollToBottom; /// If the current message belongs to a `thread`, this property represents the @@ -112,11 +126,7 @@ class _MessageListCoreState extends State { OwnUser get _currentUser => streamChannel.channel.client.state.user; - int initialIndex; - double initialAlignment; - List messages = []; - bool initialMessageHighlightComplete = false; @override diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart index 71488cc8..cf1e8d19 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; - -import 'stream_chat_core.dart'; +import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; /// [MessageSearchBloc] is used to manage a list of messages with pagination. /// This class can be used to load messages, perform queries, etc. @@ -16,7 +15,7 @@ class MessageSearchBloc extends StatefulWidget { const MessageSearchBloc({ Key key, @required this.child, - }) : assert(child != null), + }) : assert(child != null, 'Parameter child should not be null.'), super(key: key); /// The widget child diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index e3d98426..5c4e74fe 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -1,13 +1,15 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/message_search_bloc.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; -import 'message_search_bloc.dart'; /// -/// [MessageSearchListCore] is a simplified class that allows searching for messages across channels while exposing UI builders. -/// A [MessageSearchListController] is used to load and paginate data. +/// [MessageSearchListCore] is a simplified class that allows searching for +/// messages across channels while exposing UI builders. +/// A [MessageSearchListController] is used to load and paginate data. /// /// ```dart /// class MessageSearchPage extends StatelessWidget { @@ -28,7 +30,8 @@ import 'message_search_bloc.dart'; /// } /// ``` /// -/// Make sure to have a [MessageSearchBloc] ancestor in order to provide the information about the messages. +/// Make sure to have a [MessageSearchBloc] ancestor in order to provide the +/// information about the messages. /// The widget uses a [ListView.separated] to render the list of messages. /// class MessageSearchListCore extends StatefulWidget { @@ -50,14 +53,16 @@ class MessageSearchListCore extends StatefulWidget { this.paginationParams, this.messageFilters, this.messageSearchListController, - }) : assert(emptyBuilder != null), - assert(errorBuilder != null), - assert(loadingBuilder != null), - assert(childBuilder != null), + }) : assert(emptyBuilder != null, 'emptyBuilder should not be null'), + assert(errorBuilder != null, 'errorBuilder should not be null'), + assert(loadingBuilder != null, 'loadingBuilder should not be null'), + assert(childBuilder != null, 'childBuilder should not be null'), super(key: key); /// A [MessageSearchListController] allows reloading and pagination. - /// Use [MessageSearchListController.loadData] and [MessageSearchListController.paginateData] respectively for reloading and pagination. + /// Use [MessageSearchListController.loadData] and + /// [MessageSearchListController.paginateData] respectively for reloading and + /// pagination. final MessageSearchListController messageSearchListController; /// Message String to search on @@ -69,9 +74,10 @@ class MessageSearchListCore extends StatefulWidget { final Map filters; /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. - /// Direction can be ascending or descending. + /// Sorting is based on field and direction, multiple sorting options can be + /// provided. + /// You can sort based on last_updated, last_message_at, updated_at, created_ + /// at or member_count. Direction can be ascending or descending. final List sortOptions; /// Pagination parameters @@ -103,10 +109,9 @@ class MessageSearchListCore extends StatefulWidget { class _MessageSearchListCoreState extends State { @override - void initState() { - super.initState(); - final messageSearchBloc = MessageSearchBloc.of(context); - messageSearchBloc.search( + void didChangeDependencies() { + super.didChangeDependencies(); + MessageSearchBloc.of(context).search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, @@ -126,37 +131,34 @@ class _MessageSearchListCoreState extends State { return _buildListView(messageSearchBloc); } - Widget _buildListView(MessageSearchBlocState messageSearchBloc) { - return StreamBuilder>( - stream: messageSearchBloc.messagesStream, - builder: (context, snapshot) { - if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); + Widget _buildListView(MessageSearchBlocState messageSearchBloc) => + StreamBuilder>( + stream: messageSearchBloc.messagesStream, + builder: (context, snapshot) { + if (snapshot.hasError) { + if (snapshot.error is Error) { + print((snapshot.error as Error).stackTrace); + } + + return widget.errorBuilder(context, snapshot.error); } - return widget.errorBuilder(context, snapshot.error); - } + if (!snapshot.hasData) { + return widget.loadingBuilder(context); + } - if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } + final items = snapshot.data; - final items = snapshot.data; + if (items.isEmpty) { + return widget.emptyBuilder(context); + } - if (items.isEmpty) { - return widget.emptyBuilder(context); - } - - return widget.childBuilder(snapshot.data); - }, - ); - } + return widget.childBuilder(snapshot.data); + }, + ); void loadData() { - final messageSearchBloc = MessageSearchBloc.of(context); - - messageSearchBloc.search( + MessageSearchBloc.of(context).search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, @@ -189,8 +191,7 @@ class _MessageSearchListCoreState extends State { widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() || widget.messageFilters?.toString() != oldWidget.messageFilters?.toString()) { - final messageSearchBloc = MessageSearchBloc.of(context); - messageSearchBloc.search( + MessageSearchBloc.of(context).search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart index c2333b05..05158c94 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; @@ -25,8 +26,8 @@ class StreamChannel extends StatefulWidget { @required this.channel, this.showLoading = true, this.initialMessageId, - }) : assert(child != null), - assert(channel != null), + }) : assert(child != null, 'Child should not be null'), + assert(channel != null, 'Channel should not be null'), super(key: key); // ignore: public_member_api_docs @@ -49,7 +50,8 @@ class StreamChannel extends StatefulWidget { if (streamChannelState == null) { throw Exception( - 'You must have a StreamChannel widget at the top of your widget tree'); + 'You must have a StreamChannel widget at the top of your widget tree', + ); } return streamChannelState; @@ -204,14 +206,13 @@ class StreamChannelState extends State { int before = 20, int after = 20, bool preferOffline = false, - }) { - return queryAtMessage( - messageId: messageId, - before: before, - after: after, - preferOffline: preferOffline, - ); - } + }) => + queryAtMessage( + messageId: messageId, + before: before, + after: after, + preferOffline: preferOffline, + ); /// Future queryAtMessage({ @@ -254,15 +255,14 @@ class StreamChannelState extends State { String messageId, { int limit = 20, bool preferOffline = false, - }) { - return channel.query( - messagesPagination: PaginationParams( - lessThan: messageId, - limit: limit, - ), - preferOffline: preferOffline, - ); - } + }) => + channel.query( + messagesPagination: PaginationParams( + lessThan: messageId, + limit: limit, + ), + preferOffline: preferOffline, + ); /// Future queryAfterMessage( @@ -366,9 +366,10 @@ class StreamChannelState extends State { ); } final initialized = snapshot.data[0]; + // ignore: avoid_bool_literals_in_conditional_expressions final dataLoaded = initialMessageId == null ? true : snapshot.data[1]; if (widget.showLoading && (!initialized || !dataLoaded)) { - return Center( + return const Center( child: CircularProgressIndicator(), ); } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 8a8d0185..83699cc1 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -3,13 +3,12 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; - -import 'typedef.dart'; +import 'package:stream_chat_flutter_core/src/typedef.dart'; /// Widget used to provide information about the chat to the widget tree. /// This Widget is used to react to life cycle changes and system updates. -/// When the app goes into the background, the websocket connection is kept alive -/// for two minutes before being terminated. +/// When the app goes into the background, the websocket connection is kept +/// alive for two minutes before being terminated. /// /// Conversely, when app is resumed or restarted, a new connection is initiated. /// @@ -36,16 +35,16 @@ import 'typedef.dart'; class StreamChatCore extends StatefulWidget { /// Constructor used for creating a new instance of [StreamChatCore]. /// - /// [StreamChatCore] is a stateful widget which reacts to system events and updates - /// Stream's connection status accordingly. - StreamChatCore({ + /// [StreamChatCore] is a stateful widget which reacts to system events and + /// updates Stream's connection status accordingly. + const StreamChatCore({ Key key, @required this.client, @required this.child, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), - }) : assert(client != null), - assert(child != null), + }) : assert(client != null, 'Stream Chat Client should not be null'), + assert(child != null, 'Child should not be null'), super(key: key); /// Instance of Stream Chat Client containing information about the current @@ -55,7 +54,8 @@ class StreamChatCore extends StatefulWidget { /// Widget descendant. final Widget child; - /// The amount of time that will pass before disconnecting the client in the background + /// The amount of time that will pass before disconnecting the client in + /// the background final Duration backgroundKeepAlive; /// Handler called whenever the [client] receives a new [Event] while the app @@ -90,9 +90,7 @@ class StreamChatCoreState extends State Timer _disconnectTimer; @override - Widget build(BuildContext context) { - return widget.child; - } + Widget build(BuildContext context) => widget.child; /// The current user User get user => widget.client.state.user; diff --git a/packages/stream_chat_flutter_core/lib/src/typedef.dart b/packages/stream_chat_flutter_core/lib/src/typedef.dart index 29fb2d31..ace8d7e6 100644 --- a/packages/stream_chat_flutter_core/lib/src/typedef.dart +++ b/packages/stream_chat_flutter_core/lib/src/typedef.dart @@ -2,8 +2,8 @@ import 'package:flutter/widgets.dart'; import 'package:stream_chat/stream_chat.dart'; /// A signature for a callback which exposes an error and returns a function. -/// This Callback can be used in cases where an API failure occurs and the widget -/// is unable to render data. +/// This Callback can be used in cases where an API failure occurs and the +/// widget is unable to render data. typedef ErrorBuilder = Widget Function(BuildContext context, Object error); /// A Signature for a handler function which will expose a [event]. diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 23140258..61169483 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -1,11 +1,13 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/users_bloc.dart'; /// -/// [UserListCore] is a simplified class that allows fetching users while exposing UI builders. +/// [UserListCore] is a simplified class that allows fetching users while +/// exposing UI builders. /// A [UserListController] is used to load and paginate data. /// /// ```dart @@ -50,30 +52,31 @@ import 'package:stream_chat_flutter_core/src/users_bloc.dart'; /// [UsersBloc] must be the ancestor of this widget. This is necessary since /// [UserListCore] depends on functionality contained within [UsersBloc]. /// -/// The parameters [listBuilder], [loadingBuilder], [emptyBuilder] and [errorBuilder] must all be supplied -/// and not null. +/// The parameters [listBuilder], [loadingBuilder], [emptyBuilder] and +/// [errorBuilder] must all be supplied and not null. class UserListCore extends StatefulWidget { /// Instantiate a new [UserListCore] const UserListCore({ - Key key, @required this.errorBuilder, @required this.emptyBuilder, @required this.loadingBuilder, @required this.listBuilder, + Key key, this.filter, this.options, this.sort, this.pagination, this.groupAlphabetically = false, this.userListController, - }) : assert(errorBuilder != null), - assert(emptyBuilder != null), - assert(loadingBuilder != null), - assert(listBuilder != null), + }) : assert(errorBuilder != null, ''), + assert(emptyBuilder != null, ''), + assert(loadingBuilder != null, ''), + assert(listBuilder != null, ''), super(key: key); /// A [UserListController] allows reloading and pagination. - /// Use [UserListController.loadData] and [UserListController.paginateData] respectively for reloading and pagination. + /// Use [UserListController.loadData] and [UserListController.paginateData] + /// respectively for reloading and pagination. final UserListController userListController; /// The builder that will be used in case of error @@ -100,9 +103,9 @@ class UserListCore extends StatefulWidget { final Map options; /// The sorting used for the channels matching the filters. - /// Sorting is based on field and direction, multiple sorting options can be provided. - /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. - /// Direction can be ascending or descending. + /// Sorting is based on field and direction, multiple sorting options can be + /// provided. You can sort based on last_updated, last_message_at, updated_at, + /// created_at or member_count. Direction can be ascending or descending. final List sort; /// Pagination parameters @@ -123,10 +126,9 @@ class UserListCore extends StatefulWidget { class _UserListCoreState extends State with WidgetsBindingObserver { @override - void initState() { - super.initState(); - final usersBloc = UsersBloc.of(context); - usersBloc.queryUsers( + void didChangeDependencies() { + super.didChangeDependencies(); + UsersBloc.of(context).queryUsers( filter: widget.filter, sort: widget.sort, pagination: widget.pagination, @@ -151,68 +153,66 @@ class _UserListCoreState extends State Stream> _buildUserStream( UsersBlocState usersBlocState, - ) { - return usersBlocState.usersStream.map( - (users) { - if (widget.groupAlphabetically) { - var temp = users; - if (!isListAlreadySorted) { - temp = users..sort((curr, next) => curr.name.compareTo(next.name)); + ) => + usersBlocState.usersStream.map( + (users) { + if (widget.groupAlphabetically) { + var temp = users; + if (!isListAlreadySorted) { + temp = users + ..sort((curr, next) => curr.name.compareTo(next.name)); + } + final groupedUsers = >{}; + for (final e in temp) { + final alphabet = e.name[0]?.toUpperCase(); + groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; + } + final items = []; + for (final key in groupedUsers.keys) { + items + ..add(ListHeaderItem(key)) + ..addAll(groupedUsers[key].map((e) => ListUserItem(e))); + } + return items; } - final groupedUsers = >{}; - for (final e in temp) { - final alphabet = e.name[0]?.toUpperCase(); - groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; - } - final items = []; - for (final key in groupedUsers.keys) { - items.add(ListHeaderItem(key)); - items.addAll(groupedUsers[key].map((e) => ListUserItem(e))); - } - return items; - } - return users.map((e) => ListUserItem(e)).toList(); - }, - ); - } + return users.map((e) => ListUserItem(e)).toList(); + }, + ); StreamBuilder> _buildListView( UsersBlocState usersBlocState, - ) { - return StreamBuilder( - stream: _buildUserStream(usersBlocState), - builder: (context, snapshot) { - if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); + ) => + StreamBuilder( + stream: _buildUserStream(usersBlocState), + builder: (context, snapshot) { + if (snapshot.hasError) { + if (snapshot.error is Error) { + print((snapshot.error as Error).stackTrace); + } + + return widget.errorBuilder(snapshot.error); } - return widget.errorBuilder(snapshot.error); - } + if (!snapshot.hasData) { + return widget.loadingBuilder(context); + } - if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } + final items = snapshot.data; - final items = snapshot.data; + if (items.isEmpty) { + return widget.emptyBuilder(context); + } - if (items.isEmpty) { - return widget.emptyBuilder(context); - } + if (items.isEmpty) { + return widget.emptyBuilder(context); + } - if (items.isEmpty) { - return widget.emptyBuilder(context); - } - - return widget.listBuilder(context, items); - }, - ); - } + return widget.listBuilder(context, items); + }, + ); void loadData() { - final _usersBloc = UsersBloc.of(context); - - _usersBloc.queryUsers( + UsersBloc.of(context).queryUsers( filter: widget.filter, sort: widget.sort, pagination: widget.pagination, @@ -241,8 +241,7 @@ class _UserListCoreState extends State widget.pagination?.toJson()?.toString() != oldWidget.pagination?.toJson()?.toString() || widget.options?.toString() != oldWidget.options?.toString()) { - final usersBloc = UsersBloc.of(context); - usersBloc.queryUsers( + UsersBloc.of(context).queryUsers( filter: widget.filter, sort: widget.sort, pagination: widget.pagination, @@ -253,8 +252,8 @@ class _UserListCoreState extends State } /// Represents an item in a the user stream list. -/// Header items are prefixed with the key `HEADER` While users are prefixed with -/// `USER`. +/// Header items are prefixed with the key `HEADER` While users are prefixed +/// with `USER`. abstract class ListItem { // ignore: public_member_api_docs String get key { @@ -280,26 +279,26 @@ abstract class ListItem { if (this is ListUserItem) { return userItem((this as ListUserItem).user); } - return SizedBox(); + return const SizedBox(); } } // ignore: public_member_api_docs class ListHeaderItem extends ListItem { // ignore: public_member_api_docs - final String heading; + ListHeaderItem(this.heading); // ignore: public_member_api_docs - ListHeaderItem(this.heading); + final String heading; } // ignore: public_member_api_docs class ListUserItem extends ListItem { // ignore: public_member_api_docs - final User user; + ListUserItem(this.user); // ignore: public_member_api_docs - ListUserItem(this.user); + final User user; } /// Controller used for paginating data in [ChannelListView] diff --git a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart index 19c4a026..f10a5aae 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -1,8 +1,8 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; - -import 'stream_chat_core.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget dedicated to the management of a users list with pagination. /// @@ -14,9 +14,12 @@ class UsersBloc extends StatefulWidget { /// Instantiate a new [UsersBloc]. The parameter [child] must be supplied and /// not null. const UsersBloc({ - Key key, @required this.child, - }) : assert(child != null), + Key key, + }) : assert( + child != null, + 'When constructing a UsersBloc, the parameter ' + 'child should not be null.'), super(key: key); /// The widget child diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 48d31b45..b62101d1 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -1,5 +1,7 @@ library stream_chat_flutter_core; +export 'package:stream_chat/stream_chat.dart'; + export 'src/channel_list_core.dart'; export 'src/channels_bloc.dart'; export 'src/lazy_load_scroll_view.dart'; @@ -8,7 +10,6 @@ export 'src/message_search_bloc.dart'; export 'src/message_search_list_core.dart'; export 'src/stream_channel.dart'; export 'src/stream_chat_core.dart'; +export 'src/typedef.dart'; export 'src/user_list_core.dart'; export 'src/users_bloc.dart'; -export 'src/typedef.dart'; -export 'package:stream_chat/stream_chat.dart'; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index cc36f149..09643899 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -10,14 +10,13 @@ environment: flutter: ">=1.17.0" dependencies: - stream_chat: ^1.5.0 flutter: sdk: flutter rxdart: ^0.25.0 + stream_chat: ^1.5.0 dev_dependencies: - mockito: ^4.1.4 + fake_async: ^1.1.0 flutter_test: sdk: flutter - fake_async: ^1.1.0 - \ No newline at end of file + mockito: ^4.1.4 \ No newline at end of file diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 40880dfc..52860c3e 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:fake_async/fake_async.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; @@ -110,17 +110,14 @@ void main() { onBackgroundEventReceived: showLocalNotificationMock, backgroundKeepAlive: const Duration(seconds: 4), child: Builder( - builder: (context) { - return Container(); - }, + builder: (context) => Container(), ), ), ); - final sc = scKey.currentState; - sc.didChangeAppLifecycleState(AppLifecycleState.paused); + scKey.currentState.didChangeAppLifecycleState(AppLifecycleState.paused); - _async.elapse(Duration(seconds: 5)); + _async.elapse(const Duration(seconds: 5)); verify(client.disconnect()).called(1); eventStreamController.close(); @@ -129,7 +126,8 @@ void main() { ); testWidgets( - 'StreamChatCore should handle notifications when on background and connected', + 'StreamChatCore should handle notifications when on background and ' + 'connected', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); @@ -154,15 +152,12 @@ void main() { onBackgroundEventReceived: showLocalNotificationMock, backgroundKeepAlive: const Duration(seconds: 4), child: Builder( - builder: (context) { - return Container(); - }, + builder: (context) => Container(), ), ), ); - final sc = scKey.currentState; - sc.didChangeAppLifecycleState(AppLifecycleState.paused); + scKey.currentState.didChangeAppLifecycleState(AppLifecycleState.paused); final event = Event( type: EventType.messageNew, message: Message(text: 'hey'),