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