test: add tests for stream chat flutter core (#354)
* test(core): add tests for channels bloc Signed-off-by: Sahil Kumar <[email protected]> * test(core): refactor some tests due to changes in channels bloc Signed-off-by: Sahil Kumar <[email protected]> * test(core): add tests for channel list core Signed-off-by: Sahil Kumar <[email protected]> * test(core): add tests for users bloc, minor improvements Signed-off-by: Sahil Kumar <[email protected]> * test(core): add tests for user list core, minor improvements Signed-off-by: Sahil Kumar <[email protected]> * test(core): add tests for message search bloc, refactor Signed-off-by: Sahil Kumar <[email protected]> * test(core): add tests for message search list core, refactor Signed-off-by: Sahil Kumar <[email protected]> * test(core): add tests for lazy load scroll view Signed-off-by: Sahil Kumar <[email protected]> * test(core): add tests for stream chat core Signed-off-by: Sahil Kumar <[email protected]> * test(core): add tests for message list core Signed-off-by: Sahil Kumar <[email protected]> * test(core): add tests for stream channel Signed-off-by: Sahil Kumar <[email protected]> * Fix merge conflicts Signed-off-by: Sahil Kumar <[email protected]> * flutter format Signed-off-by: Sahil Kumar <[email protected]> * fixes Signed-off-by: Sahil Kumar <[email protected]> * update workflow Signed-off-by: Sahil Kumar <[email protected]> * chore(core): fix analyzer issues Signed-off-by: Sahil Kumar <[email protected]> * chore(ui-kit): fix analyzer issues Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
@@ -130,10 +130,11 @@ class ChannelListCore extends StatefulWidget {
|
||||
final PaginationParams pagination;
|
||||
|
||||
@override
|
||||
_ChannelListCoreState createState() => _ChannelListCoreState();
|
||||
ChannelListCoreState createState() => ChannelListCoreState();
|
||||
}
|
||||
|
||||
class _ChannelListCoreState extends State<ChannelListCore> {
|
||||
/// The current state of the [ChannelListCore].
|
||||
class ChannelListCoreState extends State<ChannelListCore> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final channelsBloc = ChannelsBloc.of(context);
|
||||
@@ -148,7 +149,7 @@ class _ChannelListCoreState extends State<ChannelListCore> {
|
||||
stream: channelsBlocState.channelsStream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return _buildErrorWidget(snapshot, context, channelsBlocState);
|
||||
return widget.errorBuilder(context, snapshot.error);
|
||||
}
|
||||
if (!snapshot.hasData) {
|
||||
return widget.loadingBuilder(context);
|
||||
@@ -161,18 +162,7 @@ class _ChannelListCoreState extends State<ChannelListCore> {
|
||||
},
|
||||
);
|
||||
|
||||
Widget _buildErrorWidget(
|
||||
AsyncSnapshot<List<Channel>> snapshot,
|
||||
BuildContext context,
|
||||
ChannelsBlocState channelsBlocState,
|
||||
) {
|
||||
if (snapshot.error is Error) {
|
||||
print((snapshot.error as Error).stackTrace);
|
||||
}
|
||||
|
||||
return widget.errorBuilder(context, snapshot.error);
|
||||
}
|
||||
|
||||
/// Fetches initial channels and updates the widget
|
||||
Future<void> loadData() {
|
||||
final channelsBloc = ChannelsBloc.of(context);
|
||||
return channelsBloc.queryChannels(
|
||||
@@ -183,6 +173,7 @@ class _ChannelListCoreState extends State<ChannelListCore> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Fetches more channels with updated pagination and updates the widget
|
||||
Future<void> paginateData() {
|
||||
final channelsBloc = ChannelsBloc.of(context);
|
||||
return channelsBloc.queryChannels(
|
||||
@@ -223,9 +214,9 @@ class _ChannelListCoreState extends State<ChannelListCore> {
|
||||
|
||||
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
|
||||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
||||
widget.options?.toString() != oldWidget.options?.toString() ||
|
||||
widget.pagination?.toJson()?.toString() !=
|
||||
oldWidget.pagination?.toJson()?.toString() ||
|
||||
widget.options?.toString() != oldWidget.options?.toString()) {
|
||||
oldWidget.pagination?.toJson()?.toString()) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +74,9 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
||||
/// The current channel list as a stream
|
||||
Stream<List<Channel>> get channelsStream => _channelsController.stream;
|
||||
|
||||
final BehaviorSubject<bool> _queryChannelsLoadingController =
|
||||
BehaviorSubject.seeded(false);
|
||||
final _queryChannelsLoadingController = BehaviorSubject.seeded(false);
|
||||
|
||||
final BehaviorSubject<List<Channel>> _channelsController = BehaviorSubject();
|
||||
final _channelsController = BehaviorSubject<List<Channel>>();
|
||||
|
||||
/// The stream notifying the state of queryChannel call
|
||||
Stream<bool> get queryChannelsLoading =>
|
||||
@@ -91,15 +90,14 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
||||
List<SortOption<ChannelModel>> sortOptions,
|
||||
PaginationParams paginationParams,
|
||||
Map<String, dynamic> options,
|
||||
bool onlyOffline = false,
|
||||
}) async {
|
||||
final client = StreamChatCore.of(context).client;
|
||||
|
||||
if (client.state?.user == null ||
|
||||
_queryChannelsLoadingController.value == true) {
|
||||
return;
|
||||
if (_queryChannelsLoadingController.value == true) return;
|
||||
|
||||
if (_channelsController.hasValue) {
|
||||
_queryChannelsLoadingController.add(true);
|
||||
}
|
||||
_queryChannelsLoadingController.sink.add(true);
|
||||
|
||||
try {
|
||||
final clear = paginationParams == null ||
|
||||
@@ -115,15 +113,20 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
||||
if (clear) {
|
||||
_channelsController.add(channels);
|
||||
} else {
|
||||
final l = oldChannels + channels;
|
||||
_channelsController.add(l);
|
||||
final temp = oldChannels + channels;
|
||||
_channelsController.add(temp);
|
||||
}
|
||||
if (_channelsController.hasValue &&
|
||||
_queryChannelsLoadingController.value) {
|
||||
_queryChannelsLoadingController.sink.add(false);
|
||||
}
|
||||
_queryChannelsLoadingController.sink.add(false);
|
||||
}
|
||||
} catch (err, stackTrace) {
|
||||
print(err);
|
||||
print(stackTrace);
|
||||
_queryChannelsLoadingController.addError(err, stackTrace);
|
||||
} catch (e, stk) {
|
||||
if (_channelsController.hasValue) {
|
||||
_queryChannelsLoadingController.addError(e, stk);
|
||||
} else {
|
||||
_channelsController.addError(e, stk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,15 +142,14 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
||||
_subscriptions.add(client.on(EventType.messageNew).listen((e) {
|
||||
final newChannels = List<Channel>.from(channels ?? []);
|
||||
final index = newChannels.indexWhere((c) => c.cid == e.cid);
|
||||
if (index > -1) {
|
||||
if (index != -1) {
|
||||
if (index > 0) {
|
||||
final channel = newChannels.removeAt(index);
|
||||
newChannels.insert(0, channel);
|
||||
}
|
||||
} else if (widget.shouldAddChannel != null &&
|
||||
widget.shouldAddChannel(e)) {
|
||||
} else if (widget.shouldAddChannel?.call(e) == true) {
|
||||
final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid);
|
||||
if (hiddenIndex > -1) {
|
||||
if (hiddenIndex != -1) {
|
||||
newChannels.insert(0, _hiddenChannels[hiddenIndex]);
|
||||
_hiddenChannels.removeAt(hiddenIndex);
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:flutter/foundation.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
|
||||
/// reaches to the start or end of the view extent.
|
||||
@@ -47,16 +46,16 @@ class LazyLoadScrollView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
|
||||
_LoadingStatus _loadMoreStatus = _LoadingStatus.STABLE;
|
||||
double _scrollPosition = 0;
|
||||
_LoadingStatus _loadMoreStatus = _LoadingStatus.stable;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => NotificationListener(
|
||||
Widget build(BuildContext context) =>
|
||||
NotificationListener<ScrollNotification>(
|
||||
onNotification: _onNotification,
|
||||
child: widget.child,
|
||||
);
|
||||
|
||||
bool _onNotification(Notification notification) {
|
||||
bool _onNotification(ScrollNotification notification) {
|
||||
if (notification is ScrollStartNotification) {
|
||||
if (widget.onPageScrollStart != null) {
|
||||
widget.onPageScrollStart();
|
||||
@@ -73,7 +72,7 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
|
||||
final pixels = notification.metrics.pixels;
|
||||
final maxScrollExtent = notification.metrics.maxScrollExtent;
|
||||
final minScrollExtent = notification.metrics.minScrollExtent;
|
||||
final scrollOffset = widget.scrollOffset;
|
||||
final scrollOffset = widget.scrollOffset ?? 0;
|
||||
|
||||
if (pixels > (minScrollExtent + scrollOffset) &&
|
||||
pixels < (maxScrollExtent - scrollOffset)) {
|
||||
@@ -85,28 +84,15 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
|
||||
|
||||
final extentBefore = notification.metrics.extentBefore;
|
||||
final extentAfter = notification.metrics.extentAfter;
|
||||
final scrollingDown = _scrollPosition < pixels;
|
||||
|
||||
if (scrollOffset == null || scrollOffset == 0) {
|
||||
if (extentAfter == 0) {
|
||||
_onEndOfPage();
|
||||
}
|
||||
if (extentBefore == 0) {
|
||||
_onStartOfPage();
|
||||
}
|
||||
} else {
|
||||
if (scrollingDown) {
|
||||
if (extentAfter <= scrollOffset) {
|
||||
_onEndOfPage();
|
||||
}
|
||||
} else {
|
||||
if (extentBefore <= scrollOffset) {
|
||||
_onStartOfPage();
|
||||
}
|
||||
}
|
||||
if (extentAfter <= scrollOffset) {
|
||||
_onEndOfPage();
|
||||
return true;
|
||||
}
|
||||
if (extentBefore <= scrollOffset) {
|
||||
_onStartOfPage();
|
||||
return true;
|
||||
}
|
||||
_scrollPosition = pixels;
|
||||
return true;
|
||||
}
|
||||
if (notification is OverscrollNotification) {
|
||||
if (notification.overscroll > 0) {
|
||||
@@ -121,22 +107,22 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
|
||||
}
|
||||
|
||||
void _onEndOfPage() {
|
||||
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) {
|
||||
_loadMoreStatus = _LoadingStatus.LOADING;
|
||||
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) {
|
||||
_loadMoreStatus = _LoadingStatus.loading;
|
||||
if (widget.onEndOfPage != null) {
|
||||
widget.onEndOfPage().whenComplete(() {
|
||||
_loadMoreStatus = _LoadingStatus.STABLE;
|
||||
_loadMoreStatus = _LoadingStatus.stable;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onStartOfPage() {
|
||||
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) {
|
||||
_loadMoreStatus = _LoadingStatus.LOADING;
|
||||
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) {
|
||||
_loadMoreStatus = _LoadingStatus.loading;
|
||||
if (widget.onStartOfPage != null) {
|
||||
widget.onStartOfPage().whenComplete(() {
|
||||
_loadMoreStatus = _LoadingStatus.STABLE;
|
||||
_loadMoreStatus = _LoadingStatus.stable;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,28 +114,28 @@ class MessageListCore extends StatefulWidget {
|
||||
final bool Function(Message) messageFilter;
|
||||
|
||||
@override
|
||||
_MessageListCoreState createState() => _MessageListCoreState();
|
||||
MessageListCoreState createState() => MessageListCoreState();
|
||||
}
|
||||
|
||||
class _MessageListCoreState extends State<MessageListCore> {
|
||||
StreamChannelState streamChannel;
|
||||
/// The current state of the [MessageListCore].
|
||||
class MessageListCoreState extends State<MessageListCore> {
|
||||
StreamChannelState _streamChannel;
|
||||
|
||||
bool get _upToDate => streamChannel.channel.state.isUpToDate;
|
||||
bool get _upToDate => _streamChannel.channel.state.isUpToDate;
|
||||
|
||||
bool get _isThreadConversation => widget.parentMessage != null;
|
||||
|
||||
OwnUser get _currentUser => streamChannel.channel.client.state.user;
|
||||
OwnUser get _currentUser => _streamChannel.channel.client.state.user;
|
||||
|
||||
List<Message> messages = <Message>[];
|
||||
bool initialMessageHighlightComplete = false;
|
||||
var _messages = <Message>[];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final messagesStream = _isThreadConversation
|
||||
? streamChannel.channel.state.threadsStream
|
||||
? _streamChannel.channel.state.threadsStream
|
||||
.where((threads) => threads.containsKey(widget.parentMessage.id))
|
||||
.map((threads) => threads[widget.parentMessage.id])
|
||||
: streamChannel.channel.state?.messagesStream;
|
||||
: _streamChannel.channel.state?.messagesStream;
|
||||
|
||||
bool defaultFilter(Message m) {
|
||||
final isMyMessage = m.user.id == _currentUser.id;
|
||||
@@ -148,10 +148,10 @@ class _MessageListCoreState extends State<MessageListCore> {
|
||||
stream: messagesStream?.map((messages) =>
|
||||
messages?.where(widget.messageFilter ?? defaultFilter)?.toList()),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return widget.loadingBuilder(context);
|
||||
} else if (snapshot.hasError) {
|
||||
if (snapshot.hasError) {
|
||||
return widget.errorWidgetBuilder(context, snapshot.error);
|
||||
} else if (!snapshot.hasData) {
|
||||
return widget.loadingBuilder(context);
|
||||
} else {
|
||||
final messageList = snapshot.data?.reversed?.toList() ?? [];
|
||||
if (messageList.isEmpty && !_isThreadConversation) {
|
||||
@@ -159,29 +159,32 @@ class _MessageListCoreState extends State<MessageListCore> {
|
||||
return widget.emptyBuilder(context);
|
||||
}
|
||||
} else {
|
||||
messages = messageList;
|
||||
_messages = messageList;
|
||||
}
|
||||
return widget.messageListBuilder(context, messages);
|
||||
return widget.messageListBuilder(context, _messages);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Fetches more messages with updated pagination and updates the widget.
|
||||
///
|
||||
/// Optionally pass the fetch direction, defaults to [QueryDirection.bottom]
|
||||
Future<void> paginateData(
|
||||
{QueryDirection direction = QueryDirection.bottom}) {
|
||||
if (!_isThreadConversation) {
|
||||
return streamChannel.queryMessages(direction: direction);
|
||||
return _streamChannel.queryMessages(direction: direction);
|
||||
} else {
|
||||
return streamChannel.getReplies(widget.parentMessage.id);
|
||||
return _streamChannel.getReplies(widget.parentMessage.id);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
streamChannel = StreamChannel.of(context);
|
||||
_streamChannel = StreamChannel.of(context);
|
||||
|
||||
if (_isThreadConversation) {
|
||||
streamChannel.getReplies(widget.parentMessage.id);
|
||||
_streamChannel.getReplies(widget.parentMessage.id);
|
||||
}
|
||||
|
||||
if (widget.messageListController != null) {
|
||||
@@ -194,7 +197,7 @@ class _MessageListCoreState extends State<MessageListCore> {
|
||||
@override
|
||||
void dispose() {
|
||||
if (!_upToDate) {
|
||||
streamChannel.reloadChannel();
|
||||
_streamChannel.reloadChannel();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
@@ -203,5 +206,5 @@ class _MessageListCoreState extends State<MessageListCore> {
|
||||
/// Controller used for paginating data in [ChannelListView]
|
||||
class MessageListController {
|
||||
/// Call this function to load further data
|
||||
Function({QueryDirection direction}) paginateData;
|
||||
Future<void> Function({QueryDirection direction}) paginateData;
|
||||
}
|
||||
|
||||
@@ -48,17 +48,16 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
|
||||
Stream<List<GetMessageResponse>> get messagesStream =>
|
||||
_messageResponses.stream;
|
||||
|
||||
final BehaviorSubject<List<GetMessageResponse>> _messageResponses =
|
||||
BehaviorSubject();
|
||||
final _messageResponses = BehaviorSubject<List<GetMessageResponse>>();
|
||||
|
||||
final BehaviorSubject<bool> _queryMessagesLoadingController =
|
||||
BehaviorSubject.seeded(false);
|
||||
final _queryMessagesLoadingController = BehaviorSubject.seeded(false);
|
||||
|
||||
/// The stream notifying the state of queryUsers call
|
||||
Stream<bool> get queryMessagesLoading =>
|
||||
_queryMessagesLoadingController.stream;
|
||||
|
||||
/// Calls [StreamChatClient.search] updating [messageResponses] stream
|
||||
/// Calls [StreamChatClient.search] updating
|
||||
/// [messagesStream] and [queryMessagesLoading] stream
|
||||
Future<void> search({
|
||||
Map<String, dynamic> filter,
|
||||
Map<String, dynamic> messageFilter,
|
||||
@@ -66,33 +65,13 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
|
||||
String query,
|
||||
PaginationParams pagination,
|
||||
}) async {
|
||||
_messageResponses.add(null);
|
||||
try {
|
||||
final messages = await _search(
|
||||
filter: filter,
|
||||
messageFilter: messageFilter,
|
||||
sort: sort,
|
||||
query: query,
|
||||
pagination: pagination,
|
||||
);
|
||||
_messageResponses.add(messages.results);
|
||||
} catch (err, stk) {
|
||||
_messageResponses.addError(err, stk);
|
||||
}
|
||||
}
|
||||
final client = StreamChatCore.of(context).client;
|
||||
|
||||
/// Calls [StreamChatClient.search] updating [queryMessagesLoading] stream
|
||||
Future<void> loadMore({
|
||||
Map<String, dynamic> filter,
|
||||
Map<String, dynamic> messageFilter,
|
||||
List<SortOption> sort,
|
||||
String query,
|
||||
PaginationParams pagination,
|
||||
}) async {
|
||||
if (_queryMessagesLoadingController.value == true) {
|
||||
return;
|
||||
if (_queryMessagesLoadingController.value == true) return;
|
||||
|
||||
if (_messageResponses.hasValue) {
|
||||
_queryMessagesLoadingController.add(true);
|
||||
}
|
||||
_queryMessagesLoadingController.add(true);
|
||||
try {
|
||||
final clear = pagination == null ||
|
||||
pagination.offset == null ||
|
||||
@@ -100,12 +79,12 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
|
||||
|
||||
final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []);
|
||||
|
||||
final messages = await _search(
|
||||
filter: filter,
|
||||
messageFilter: messageFilter,
|
||||
final messages = await client.search(
|
||||
filter,
|
||||
sort: sort,
|
||||
query: query,
|
||||
pagination: pagination,
|
||||
paginationParams: pagination,
|
||||
messageFilters: messageFilter,
|
||||
);
|
||||
|
||||
if (clear) {
|
||||
@@ -114,30 +93,18 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
|
||||
final temp = oldMessages + messages.results;
|
||||
_messageResponses.add(temp);
|
||||
}
|
||||
|
||||
_queryMessagesLoadingController.add(false);
|
||||
} catch (err, stackTrace) {
|
||||
_queryMessagesLoadingController.addError(err, stackTrace);
|
||||
if (_messageResponses.hasValue && _queryMessagesLoadingController.value) {
|
||||
_queryMessagesLoadingController.add(false);
|
||||
}
|
||||
} catch (e, stk) {
|
||||
if (_messageResponses.hasValue) {
|
||||
_queryMessagesLoadingController.addError(e, stk);
|
||||
} else {
|
||||
_messageResponses.addError(e, stk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<SearchMessagesResponse> _search({
|
||||
Map<String, dynamic> filter,
|
||||
Map<String, dynamic> messageFilter,
|
||||
List<SortOption> sort,
|
||||
String query,
|
||||
PaginationParams pagination,
|
||||
}) {
|
||||
final client = StreamChatCore.of(context).client;
|
||||
return client.search(
|
||||
filter,
|
||||
sort: sort,
|
||||
query: query,
|
||||
paginationParams: pagination,
|
||||
messageFilters: messageFilter,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
@@ -104,21 +104,15 @@ class MessageSearchListCore extends StatefulWidget {
|
||||
final WidgetBuilder loadingBuilder;
|
||||
|
||||
@override
|
||||
_MessageSearchListCoreState createState() => _MessageSearchListCoreState();
|
||||
MessageSearchListCoreState createState() => MessageSearchListCoreState();
|
||||
}
|
||||
|
||||
class _MessageSearchListCoreState extends State<MessageSearchListCore> {
|
||||
/// The current state of the [MessageSearchListCore].
|
||||
class MessageSearchListCoreState extends State<MessageSearchListCore> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
MessageSearchBloc.of(context).search(
|
||||
filter: widget.filters,
|
||||
sort: widget.sortOptions,
|
||||
query: widget.messageQuery,
|
||||
pagination: widget.paginationParams,
|
||||
messageFilter: widget.messageFilters,
|
||||
);
|
||||
|
||||
loadData();
|
||||
if (widget.messageSearchListController != null) {
|
||||
widget.messageSearchListController.loadData = loadData;
|
||||
widget.messageSearchListController.paginateData = paginateData;
|
||||
@@ -136,29 +130,23 @@ class _MessageSearchListCoreState extends State<MessageSearchListCore> {
|
||||
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);
|
||||
}
|
||||
|
||||
if (!snapshot.hasData) {
|
||||
return widget.loadingBuilder(context);
|
||||
}
|
||||
|
||||
final items = snapshot.data;
|
||||
|
||||
if (items.isEmpty) {
|
||||
return widget.emptyBuilder(context);
|
||||
}
|
||||
|
||||
return widget.childBuilder(snapshot.data);
|
||||
},
|
||||
);
|
||||
|
||||
void loadData() {
|
||||
MessageSearchBloc.of(context).search(
|
||||
/// Fetches initial messages and updates the widget
|
||||
Future<void> loadData() {
|
||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||
return messageSearchBloc.search(
|
||||
filter: widget.filters,
|
||||
sort: widget.sortOptions,
|
||||
query: widget.messageQuery,
|
||||
@@ -167,10 +155,10 @@ class _MessageSearchListCoreState extends State<MessageSearchListCore> {
|
||||
);
|
||||
}
|
||||
|
||||
void paginateData() {
|
||||
/// Fetches more messages with updated pagination and updates the widget
|
||||
Future<void> paginateData() {
|
||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||
|
||||
messageSearchBloc.loadMore(
|
||||
return messageSearchBloc.search(
|
||||
filter: widget.filters,
|
||||
sort: widget.sortOptions,
|
||||
pagination: widget.paginationParams.copyWith(
|
||||
@@ -186,18 +174,12 @@ class _MessageSearchListCoreState extends State<MessageSearchListCore> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.filters?.toString() != oldWidget.filters?.toString() ||
|
||||
jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) ||
|
||||
widget.paginationParams?.toJson()?.toString() !=
|
||||
oldWidget.paginationParams?.toJson()?.toString() ||
|
||||
widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() ||
|
||||
widget.messageFilters?.toString() !=
|
||||
oldWidget.messageFilters?.toString()) {
|
||||
MessageSearchBloc.of(context).search(
|
||||
filter: widget.filters,
|
||||
sort: widget.sortOptions,
|
||||
query: widget.messageQuery,
|
||||
pagination: widget.paginationParams,
|
||||
messageFilter: widget.messageFilters,
|
||||
);
|
||||
oldWidget.messageFilters?.toString() ||
|
||||
widget.paginationParams?.toJson()?.toString() !=
|
||||
oldWidget.paginationParams?.toJson()?.toString()) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,8 +187,8 @@ class _MessageSearchListCoreState extends State<MessageSearchListCore> {
|
||||
/// Controller used for paginating data in [ChannelListView]
|
||||
class MessageSearchListController {
|
||||
/// Call this function to reload data
|
||||
VoidCallback loadData;
|
||||
AsyncCallback loadData;
|
||||
|
||||
/// Call this function to load further data
|
||||
VoidCallback paginateData;
|
||||
AsyncCallback paginateData;
|
||||
}
|
||||
|
||||
@@ -207,15 +207,14 @@ class StreamChannelState extends State<StreamChannel> {
|
||||
int after = 20,
|
||||
bool preferOffline = false,
|
||||
}) =>
|
||||
queryAtMessage(
|
||||
_queryAtMessage(
|
||||
messageId: messageId,
|
||||
before: before,
|
||||
after: after,
|
||||
preferOffline: preferOffline,
|
||||
);
|
||||
|
||||
///
|
||||
Future<void> queryAtMessage({
|
||||
Future<void> _queryAtMessage({
|
||||
String messageId,
|
||||
int before = 20,
|
||||
int after = 20,
|
||||
@@ -297,7 +296,7 @@ class StreamChannelState extends State<StreamChannel> {
|
||||
}
|
||||
|
||||
/// Reloads the channel with latest message
|
||||
Future<void> reloadChannel() => queryAtMessage(before: 30);
|
||||
Future<void> reloadChannel() => _queryAtMessage(before: 30);
|
||||
|
||||
List<Future<bool>> _futures;
|
||||
|
||||
@@ -305,8 +304,7 @@ class StreamChannelState extends State<StreamChannel> {
|
||||
try {
|
||||
await loadChannelAtMessage(initialMessageId);
|
||||
return true;
|
||||
} catch (e, stk) {
|
||||
print('Error: $e\nStack: $stk');
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -349,9 +347,6 @@ class StreamChannelState extends State<StreamChannel> {
|
||||
],
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
if (snapshot.error is Error) {
|
||||
print((snapshot.error as Error).stackTrace);
|
||||
}
|
||||
var message = snapshot.error.toString();
|
||||
if (snapshot.error is DioError) {
|
||||
final dioError = snapshot.error as DioError;
|
||||
@@ -361,9 +356,7 @@ class StreamChannelState extends State<StreamChannel> {
|
||||
message = 'Check your connection and retry';
|
||||
}
|
||||
}
|
||||
return Center(
|
||||
child: Text(message),
|
||||
);
|
||||
return Center(child: Text(message));
|
||||
}
|
||||
final initialized = snapshot.data[0];
|
||||
// ignore: avoid_bool_literals_in_conditional_expressions
|
||||
|
||||
@@ -93,10 +93,10 @@ class StreamChatCoreState extends State<StreamChatCore>
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
|
||||
/// The current user
|
||||
User get user => widget.client.state.user;
|
||||
User get user => client.state?.user;
|
||||
|
||||
/// The current user as a stream
|
||||
Stream<User> get userStream => widget.client.state.userStream;
|
||||
Stream<User> get userStream => client.state?.userStream;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -108,21 +108,25 @@ class StreamChatCoreState extends State<StreamChatCore>
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (client.state?.user != null) {
|
||||
if (user != null) {
|
||||
if (state == AppLifecycleState.paused) {
|
||||
if (widget.onBackgroundEventReceived != null) {
|
||||
_eventSubscription =
|
||||
client.on().listen(widget.onBackgroundEventReceived);
|
||||
_disconnectTimer = Timer(
|
||||
widget.backgroundKeepAlive,
|
||||
client.disconnect,
|
||||
);
|
||||
} else {
|
||||
if (widget.onBackgroundEventReceived == null) {
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
_eventSubscription = client.on().listen(
|
||||
widget.onBackgroundEventReceived,
|
||||
);
|
||||
|
||||
void onTimerComplete() {
|
||||
_eventSubscription.cancel();
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
_disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete);
|
||||
} else if (state == AppLifecycleState.resumed) {
|
||||
_eventSubscription?.cancel();
|
||||
if (_disconnectTimer?.isActive == true) {
|
||||
_eventSubscription.cancel();
|
||||
_disconnectTimer.cancel();
|
||||
} else {
|
||||
if (client.wsConnectionStatus == ConnectionStatus.disconnected) {
|
||||
|
||||
@@ -80,7 +80,7 @@ class UserListCore extends StatefulWidget {
|
||||
final UserListController userListController;
|
||||
|
||||
/// The builder that will be used in case of error
|
||||
final Widget Function(Error error) errorBuilder;
|
||||
final Widget Function(Object error) errorBuilder;
|
||||
|
||||
/// The builder that will be used to build the list
|
||||
final Widget Function(BuildContext context, List<ListItem> users) listBuilder;
|
||||
@@ -120,21 +120,16 @@ class UserListCore extends StatefulWidget {
|
||||
final bool groupAlphabetically;
|
||||
|
||||
@override
|
||||
_UserListCoreState createState() => _UserListCoreState();
|
||||
UserListCoreState createState() => UserListCoreState();
|
||||
}
|
||||
|
||||
class _UserListCoreState extends State<UserListCore>
|
||||
/// The current state of the [UserListCore].
|
||||
class UserListCoreState extends State<UserListCore>
|
||||
with WidgetsBindingObserver {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
UsersBloc.of(context).queryUsers(
|
||||
filter: widget.filter,
|
||||
sort: widget.sort,
|
||||
pagination: widget.pagination,
|
||||
options: widget.options,
|
||||
);
|
||||
|
||||
loadData();
|
||||
if (widget.userListController != null) {
|
||||
widget.userListController.loadData = loadData;
|
||||
widget.userListController.paginateData = paginateData;
|
||||
@@ -144,11 +139,10 @@ class _UserListCoreState extends State<UserListCore>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _usersBloc = UsersBloc.of(context);
|
||||
|
||||
return _buildListView(_usersBloc);
|
||||
}
|
||||
|
||||
bool get isListAlreadySorted =>
|
||||
bool get _isListAlreadySorted =>
|
||||
widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false;
|
||||
|
||||
Stream<List<ListItem>> _buildUserStream(
|
||||
@@ -158,7 +152,7 @@ class _UserListCoreState extends State<UserListCore>
|
||||
(users) {
|
||||
if (widget.groupAlphabetically) {
|
||||
var temp = users;
|
||||
if (!isListAlreadySorted) {
|
||||
if (!_isListAlreadySorted) {
|
||||
temp = users
|
||||
..sort((curr, next) => curr.name.compareTo(next.name));
|
||||
}
|
||||
@@ -186,33 +180,23 @@ class _UserListCoreState extends State<UserListCore>
|
||||
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);
|
||||
}
|
||||
|
||||
if (!snapshot.hasData) {
|
||||
return widget.loadingBuilder(context);
|
||||
}
|
||||
|
||||
final items = snapshot.data;
|
||||
|
||||
if (items.isEmpty) {
|
||||
return widget.emptyBuilder(context);
|
||||
}
|
||||
|
||||
if (items.isEmpty) {
|
||||
return widget.emptyBuilder(context);
|
||||
}
|
||||
|
||||
return widget.listBuilder(context, items);
|
||||
},
|
||||
);
|
||||
|
||||
void loadData() {
|
||||
UsersBloc.of(context).queryUsers(
|
||||
// ignore: public_member_api_docs
|
||||
Future<void> loadData() {
|
||||
final _usersBloc = UsersBloc.of(context);
|
||||
return _usersBloc.queryUsers(
|
||||
filter: widget.filter,
|
||||
sort: widget.sort,
|
||||
pagination: widget.pagination,
|
||||
@@ -220,10 +204,10 @@ class _UserListCoreState extends State<UserListCore>
|
||||
);
|
||||
}
|
||||
|
||||
void paginateData() {
|
||||
// ignore: public_member_api_docs
|
||||
Future<void> paginateData() {
|
||||
final _usersBloc = UsersBloc.of(context);
|
||||
|
||||
_usersBloc.queryUsers(
|
||||
return _usersBloc.queryUsers(
|
||||
filter: widget.filter,
|
||||
sort: widget.sort,
|
||||
pagination: widget.pagination.copyWith(
|
||||
@@ -238,15 +222,10 @@ class _UserListCoreState extends State<UserListCore>
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
|
||||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
||||
widget.options?.toString() != oldWidget.options?.toString() ||
|
||||
widget.pagination?.toJson()?.toString() !=
|
||||
oldWidget.pagination?.toJson()?.toString() ||
|
||||
widget.options?.toString() != oldWidget.options?.toString()) {
|
||||
UsersBloc.of(context).queryUsers(
|
||||
filter: widget.filter,
|
||||
sort: widget.sort,
|
||||
pagination: widget.pagination,
|
||||
options: widget.options,
|
||||
);
|
||||
oldWidget.pagination?.toJson()?.toString()) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,11 +234,11 @@ class _UserListCoreState extends State<UserListCore>
|
||||
/// Header items are prefixed with the key `HEADER` While users are prefixed
|
||||
/// with `USER`.
|
||||
abstract class ListItem {
|
||||
// ignore: public_member_api_docs
|
||||
/// Unique key per list item
|
||||
String get key {
|
||||
if (this is ListHeaderItem) {
|
||||
final header = (this as ListHeaderItem).heading;
|
||||
return 'HEADER-$header';
|
||||
return 'HEADER-${header.toLowerCase()}';
|
||||
}
|
||||
if (this is ListUserItem) {
|
||||
final user = (this as ListUserItem).user;
|
||||
@@ -268,7 +247,8 @@ abstract class ListItem {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
/// Helper function to build widget based on ListItem type
|
||||
// ignore: missing_return
|
||||
Widget when({
|
||||
@required Widget Function(String heading) headerItem,
|
||||
@required Widget Function(User user) userItem,
|
||||
@@ -279,33 +259,32 @@ abstract class ListItem {
|
||||
if (this is ListUserItem) {
|
||||
return userItem((this as ListUserItem).user);
|
||||
}
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
/// Header Item
|
||||
class ListHeaderItem extends ListItem {
|
||||
// ignore: public_member_api_docs
|
||||
/// Constructs a new [ListHeaderItem]
|
||||
ListHeaderItem(this.heading);
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
/// Heading used to build the item.
|
||||
final String heading;
|
||||
}
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
/// User Item
|
||||
class ListUserItem extends ListItem {
|
||||
// ignore: public_member_api_docs
|
||||
/// Constructs a new [ListUserItem]
|
||||
ListUserItem(this.user);
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
/// [User] used to build the item.
|
||||
final User user;
|
||||
}
|
||||
|
||||
/// Controller used for paginating data in [ChannelListView]
|
||||
class UserListController {
|
||||
/// Call this function to reload data
|
||||
VoidCallback loadData;
|
||||
AsyncCallback loadData;
|
||||
|
||||
/// Call this function to load further data
|
||||
VoidCallback paginateData;
|
||||
AsyncCallback paginateData;
|
||||
}
|
||||
|
||||
@@ -51,10 +51,9 @@ class UsersBlocState extends State<UsersBloc>
|
||||
/// The current users list as a stream
|
||||
Stream<List<User>> get usersStream => _usersController.stream;
|
||||
|
||||
final BehaviorSubject<List<User>> _usersController = BehaviorSubject();
|
||||
final _usersController = BehaviorSubject<List<User>>();
|
||||
|
||||
final BehaviorSubject<bool> _queryUsersLoadingController =
|
||||
BehaviorSubject.seeded(false);
|
||||
final _queryUsersLoadingController = BehaviorSubject.seeded(false);
|
||||
|
||||
/// The stream notifying the state of queryUsers call
|
||||
Stream<bool> get queryUsersLoading => _queryUsersLoadingController.stream;
|
||||
@@ -70,11 +69,12 @@ class UsersBlocState extends State<UsersBloc>
|
||||
}) async {
|
||||
final client = StreamChatCore.of(context).client;
|
||||
|
||||
if (client.state?.user == null ||
|
||||
_queryUsersLoadingController.value == true) {
|
||||
return;
|
||||
if (_queryUsersLoadingController.value == true) return;
|
||||
|
||||
if (_usersController.hasValue) {
|
||||
_queryUsersLoadingController.add(true);
|
||||
}
|
||||
_queryUsersLoadingController.add(true);
|
||||
|
||||
try {
|
||||
final clear = pagination == null ||
|
||||
pagination.offset == null ||
|
||||
@@ -95,10 +95,15 @@ class UsersBlocState extends State<UsersBloc>
|
||||
final temp = oldUsers + usersResponse.users;
|
||||
_usersController.add(temp);
|
||||
}
|
||||
|
||||
_queryUsersLoadingController.add(false);
|
||||
} catch (err, stackTrace) {
|
||||
_queryUsersLoadingController.addError(err, stackTrace);
|
||||
if (_usersController.hasValue && _queryUsersLoadingController.value) {
|
||||
_queryUsersLoadingController.add(false);
|
||||
}
|
||||
} catch (e, stk) {
|
||||
if (_usersController.hasValue) {
|
||||
_queryUsersLoadingController.addError(e, stk);
|
||||
} else {
|
||||
_usersController.addError(e, stk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ library stream_chat_flutter_core;
|
||||
|
||||
export 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
export 'src/channel_list_core.dart';
|
||||
export 'src/channel_list_core.dart' hide ChannelListCoreState;
|
||||
export 'src/channels_bloc.dart';
|
||||
export 'src/lazy_load_scroll_view.dart';
|
||||
export 'src/message_list_core.dart';
|
||||
export 'src/message_list_core.dart' hide MessageListCoreState;
|
||||
export 'src/message_search_bloc.dart';
|
||||
export 'src/message_search_list_core.dart';
|
||||
export 'src/message_search_list_core.dart' hide MessageSearchListCoreState;
|
||||
export 'src/stream_channel.dart';
|
||||
export 'src/stream_chat_core.dart';
|
||||
export 'src/typedef.dart';
|
||||
export 'src/user_list_core.dart';
|
||||
export 'src/user_list_core.dart' hide UserListCoreState;
|
||||
export 'src/users_bloc.dart';
|
||||
|
||||
Reference in New Issue
Block a user