feat: Apply team lints to core package (#334)

* Add analyzer file to core package

* Apply team lint rules to core package

* clean up analyzer warnings in stream chat flutter

* remove unused line

* add ignore

* sync analysis options

* [Core -> MessageWidget] Fix variable dynamic types

Signed-off-by: Sahil Kumar <xdsahil@gmail.com>

Co-authored-by: Salvatore Giordano <salvatoregiordanoo@gmail.com>
Co-authored-by: Sahil Kumar <xdsahil@gmail.com>
This commit is contained in:
Neevash Ramdial (Nash)
2021-03-22 04:49:27 -04:00
committed by GitHub
parent 6d259074eb
commit cba0b1a391
17 changed files with 425 additions and 256 deletions
+3 -5
View File
@@ -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
@@ -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}'),
@@ -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
@@ -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<String, dynamic> 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<SortOption<ChannelModel>> sort;
/// Pagination parameters
@@ -128,24 +143,23 @@ class _ChannelListCoreState extends State<ChannelListCore> {
StreamBuilder<List<Channel>> _buildListView(
ChannelsBlocState channelsBlocState,
) {
return StreamBuilder<List<Channel>>(
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<List<Channel>>(
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<List<Channel>> snapshot,
@@ -223,14 +237,15 @@ class _ChannelListCoreState extends State<ChannelListCore> {
}
}
/// 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;
}
@@ -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<Channel> 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<ChannelsBloc>
_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)));
@@ -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<LazyLoadScrollView> {
_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) {
@@ -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<MessageListCore> {
OwnUser get _currentUser => streamChannel.channel.client.state.user;
int initialIndex;
double initialAlignment;
List<Message> messages = <Message>[];
bool initialMessageHighlightComplete = false;
@override
@@ -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
@@ -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<String, dynamic> 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<SortOption> sortOptions;
/// Pagination parameters
@@ -103,10 +109,9 @@ class MessageSearchListCore extends StatefulWidget {
class _MessageSearchListCoreState extends State<MessageSearchListCore> {
@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<MessageSearchListCore> {
return _buildListView(messageSearchBloc);
}
Widget _buildListView(MessageSearchBlocState messageSearchBloc) {
return StreamBuilder<List<GetMessageResponse>>(
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<List<GetMessageResponse>>(
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<MessageSearchListCore> {
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,
@@ -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<StreamChannel> {
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<void> queryAtMessage({
@@ -254,15 +255,14 @@ class StreamChannelState extends State<StreamChannel> {
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<ChannelState> queryAfterMessage(
@@ -366,9 +366,10 @@ class StreamChannelState extends State<StreamChannel> {
);
}
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(),
);
}
@@ -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<StreamChatCore>
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;
@@ -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].
@@ -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<String, dynamic> 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<SortOption> sort;
/// Pagination parameters
@@ -123,10 +126,9 @@ class UserListCore extends StatefulWidget {
class _UserListCoreState extends State<UserListCore>
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<UserListCore>
Stream<List<ListItem>> _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 = <String, List<User>>{};
for (final e in temp) {
final alphabet = e.name[0]?.toUpperCase();
groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e];
}
final items = <ListItem>[];
for (final key in groupedUsers.keys) {
items
..add(ListHeaderItem(key))
..addAll(groupedUsers[key].map((e) => ListUserItem(e)));
}
return items;
}
final groupedUsers = <String, List<User>>{};
for (final e in temp) {
final alphabet = e.name[0]?.toUpperCase();
groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e];
}
final items = <ListItem>[];
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<List<ListItem>> _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<UserListCore>
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<UserListCore>
}
/// 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]
@@ -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
@@ -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';
@@ -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
mockito: ^4.1.4
@@ -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'),