From 9392d150c068b44148f13695d2729357db1004e6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 6 May 2021 13:25:41 +0200 Subject: [PATCH 01/16] [core]: move .of calls to didChangeDependencies --- .../lib/src/channel_list_core.dart | 79 +++++++++++-------- .../lib/src/channels_bloc.dart | 76 ++++++++++-------- .../lib/src/message_list_core.dart | 26 ++++-- .../lib/src/message_search_bloc.dart | 19 +++-- .../lib/src/message_search_list_core.dart | 48 +++++------ .../lib/src/stream_channel.dart | 11 ++- .../lib/src/stream_chat_core.dart | 10 +-- .../lib/src/user_list_core.dart | 2 +- .../lib/src/users_bloc.dart | 19 +++-- 9 files changed, 170 insertions(+), 120 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 8ead2637..e327f0ba 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -119,12 +119,11 @@ class ChannelListCore extends StatefulWidget { /// The current state of the [ChannelListCore]. class ChannelListCoreState extends State { - @override - Widget build(BuildContext context) { - final channelsBloc = ChannelsBloc.of(context); + late final ChannelsBlocState _channelsBloc; + late final StreamChatCoreState _streamChatCoreState; - return _buildListView(channelsBloc); - } + @override + Widget build(BuildContext context) => _buildListView(_channelsBloc); StreamBuilder> _buildListView( ChannelsBlocState channelsBlocState, @@ -147,36 +146,42 @@ class ChannelListCoreState extends State { ); /// Fetches initial channels and updates the widget - Future loadData() { - final channelsBloc = ChannelsBloc.of(context); - return channelsBloc.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination, - options: widget.options, - ); - } + Future loadData() => _channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + ); /// Fetches more channels with updated pagination and updates the widget - Future paginateData() { - final channelsBloc = ChannelsBloc.of(context); - return channelsBloc.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination.copyWith( - offset: channelsBloc.channels?.length ?? 0, - ), - options: widget.options, - ); - } + Future paginateData() => _channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination.copyWith( + offset: _channelsBloc.channels?.length ?? 0, + ), + options: widget.options, + ); - late StreamSubscription _subscription; + StreamSubscription? _subscription; @override void initState() { super.initState(); - loadData(); - final client = StreamChatCore.of(context).client; + _setupController(); + } + + @override + void didChangeDependencies() { + _channelsBloc = ChannelsBloc.of(context); + _streamChatCoreState = StreamChatCore.of(context); + + if (_subscription == null) { + loadData(); + } + + final client = _streamChatCoreState.client; + _subscription?.cancel(); _subscription = client .on( EventType.connectionRecovered, @@ -186,10 +191,7 @@ class ChannelListCoreState extends State { ) .listen((event) => loadData()); - if (widget.channelListController != null) { - widget.channelListController!.loadData = loadData; - widget.channelListController!.paginateData = paginateData; - } + super.didChangeDependencies(); } @override @@ -203,11 +205,22 @@ class ChannelListCoreState extends State { oldWidget.pagination.toJson().toString()) { loadData(); } + + if (widget.channelListController != oldWidget.channelListController) { + _setupController(); + } + } + + void _setupController() { + if (widget.channelListController != null) { + widget.channelListController!.loadData = loadData; + widget.channelListController!.paginateData = paginateData; + } } @override void dispose() { - _subscription.cancel(); + _subscription?.cancel(); super.dispose(); } } diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart index ce17235a..966a3fe5 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -50,17 +50,20 @@ class ChannelsBloc extends StatefulWidget { streamChatState = context.findAncestorStateOfType(); - if (streamChatState == null) { - throw Exception('You must have a ChannelsBloc widget as ancestor'); - } + assert( + streamChatState != null, + 'You must have a ChannelsBloc widget as ancestor', + ); - return streamChatState; + return streamChatState!; } } /// The current state of the [ChannelsBloc]. class ChannelsBlocState extends State with AutomaticKeepAliveClientMixin { + late final StreamChatCoreState _streamChatCoreState; + @override Widget build(BuildContext context) { super.build(context); @@ -86,6 +89,8 @@ class ChannelsBlocState extends State bool _paginationEnded = false; + final List _subscriptions = []; + /// Calls [client.queryChannels] updating [queryChannelsLoading] stream Future queryChannels({ Filter? filter, @@ -93,7 +98,7 @@ class ChannelsBlocState extends State PaginationParams paginationParams = const PaginationParams(limit: 30), Map? options, }) async { - final client = StreamChatCore.of(context).client; + final client = _streamChatCoreState.client; final clear = paginationParams.offset == 0; @@ -139,14 +144,12 @@ class ChannelsBlocState extends State } } - final List _subscriptions = []; - @override - void initState() { - super.initState(); - - final client = StreamChatCore.of(context).client; + void didChangeDependencies() { + _streamChatCoreState = StreamChatCore.of(context); + final client = _streamChatCoreState.client; + _cancelSubscriptions(); if (!widget.lockChannelsOrder) { _subscriptions.add(client .on( @@ -179,37 +182,44 @@ class ChannelsBlocState extends State })); } - _subscriptions.add(client.on(EventType.channelHidden).listen((event) async { - final newChannels = List.from(channels ?? []); - final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid); - if (channelIndex > -1) { - final channel = newChannels.removeAt(channelIndex); - _hiddenChannels.add(channel); - _channelsController.add(newChannels); - } - })); - // ignore: cascade_invocations - _subscriptions.add(client - .on( - EventType.channelDeleted, - EventType.notificationRemovedFromChannel, - ) - .listen((e) { - // ignore: cascade_invocations - final channel = e.channel; - _channelsController.add(List.from( - (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); - })); + _subscriptions + ..add(client.on(EventType.channelHidden).listen((event) async { + final newChannels = List.from(channels ?? []); + final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid); + if (channelIndex > -1) { + final channel = newChannels.removeAt(channelIndex); + _hiddenChannels.add(channel); + _channelsController.add(newChannels); + } + })) + ..add(client + .on( + EventType.channelDeleted, + EventType.notificationRemovedFromChannel, + ) + .listen((e) { + final channel = e.channel; + _channelsController.add(List.from( + (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); + })); + + super.didChangeDependencies(); } @override void dispose() { _channelsController.close(); _queryChannelsLoadingController.close(); - _subscriptions.forEach((s) => s.cancel()); + _cancelSubscriptions(); super.dispose(); } + void _cancelSubscriptions() { + _subscriptions + ..forEach((s) => s.cancel()) + ..clear(); + } + @override bool get wantKeepAlive => true; } diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index e1fed859..74c6c1ea 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -109,7 +109,7 @@ class MessageListCore extends StatefulWidget { /// The current state of the [MessageListCore]. class MessageListCoreState extends State { - late StreamChannelState _streamChannel; + late final StreamChannelState _streamChannel; bool get _upToDate => _streamChannel.channel.state?.isUpToDate ?? true; @@ -174,18 +174,34 @@ class MessageListCoreState extends State { } @override - void initState() { + void didChangeDependencies() { _streamChannel = StreamChannel.of(context); - if (_isThreadConversation) { _streamChannel.getReplies(widget.parentMessage!.id); } + super.didChangeDependencies(); + } + @override + void didUpdateWidget(covariant MessageListCore oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.messageListController != oldWidget.messageListController) { + _setupController(); + } + } + + @override + void initState() { + _setupController(); + + super.initState(); + } + + void _setupController() { if (widget.messageListController != null) { widget.messageListController!.paginateData = paginateData; } - - super.initState(); } @override diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart index 6c9be4f0..c98c470e 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart @@ -29,17 +29,20 @@ class MessageSearchBloc extends StatefulWidget { state = context.findAncestorStateOfType(); - if (state == null) { - throw Exception('You must have a MessageSearchBloc widget as ancestor'); - } + assert( + state != null, + 'You must have a MessageSearchBloc widget as ancestor', + ); - return state; + return state!; } } /// The current state of the [MessageSearchBloc] class MessageSearchBlocState extends State with AutomaticKeepAliveClientMixin { + late final StreamChatCoreState _streamChatCoreState; + /// The current messages list List? get messageResponses => _messageResponses.value; @@ -64,7 +67,7 @@ class MessageSearchBlocState extends State String? query, PaginationParams? pagination, }) async { - final client = StreamChatCore.of(context).client; + final client = _streamChatCoreState.client; if (_queryMessagesLoadingController.value == true) return; @@ -109,6 +112,12 @@ class MessageSearchBlocState extends State return widget.child; } + @override + void didChangeDependencies() { + _streamChatCoreState = StreamChatCore.of(context); + super.didChangeDependencies(); + } + @override void dispose() { _messageResponses.close(); diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index cb8c9387..5c661ca1 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -105,21 +105,21 @@ class MessageSearchListCore extends StatefulWidget { /// The current state of the [MessageSearchListCore]. class MessageSearchListCoreState extends State { + late final MessageSearchBlocState messageSearchBloc; + @override void didChangeDependencies() { - super.didChangeDependencies(); + messageSearchBloc = MessageSearchBloc.of(context); loadData(); if (widget.messageSearchListController != null) { widget.messageSearchListController!.loadData = loadData; widget.messageSearchListController!.paginateData = paginateData; } + super.didChangeDependencies(); } @override - Widget build(BuildContext context) { - final messageSearchBloc = MessageSearchBloc.of(context); - return _buildListView(messageSearchBloc); - } + Widget build(BuildContext context) => _buildListView(messageSearchBloc); Widget _buildListView(MessageSearchBlocState messageSearchBloc) => StreamBuilder>( @@ -140,30 +140,24 @@ class MessageSearchListCoreState extends State { ); /// Fetches initial messages and updates the widget - Future loadData() { - final messageSearchBloc = MessageSearchBloc.of(context); - return messageSearchBloc.search( - filter: widget.filters, - sort: widget.sortOptions, - query: widget.messageQuery, - pagination: widget.paginationParams, - messageFilter: widget.messageFilters, - ); - } + Future loadData() => messageSearchBloc.search( + filter: widget.filters, + sort: widget.sortOptions, + query: widget.messageQuery, + pagination: widget.paginationParams, + messageFilter: widget.messageFilters, + ); /// Fetches more messages with updated pagination and updates the widget - Future paginateData() { - final messageSearchBloc = MessageSearchBloc.of(context); - return messageSearchBloc.search( - filter: widget.filters, - sort: widget.sortOptions, - pagination: widget.paginationParams!.copyWith( - offset: messageSearchBloc.messageResponses?.length ?? 0, - ), - query: widget.messageQuery, - messageFilter: widget.messageFilters, - ); - } + Future paginateData() => messageSearchBloc.search( + filter: widget.filters, + sort: widget.sortOptions, + pagination: widget.paginationParams!.copyWith( + offset: messageSearchBloc.messageResponses?.length ?? 0, + ), + query: widget.messageQuery, + messageFilter: widget.messageFilters, + ); @override void didUpdateWidget(MessageSearchListCore oldWidget) { diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart index a6fd83b2..2760d9cc 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -47,13 +47,12 @@ class StreamChannel extends StatefulWidget { streamChannelState = context.findAncestorStateOfType(); - if (streamChannelState == null) { - throw Exception( - 'You must have a StreamChannel widget at the top of your widget tree', - ); - } + assert( + streamChannelState != null, + 'You must have a StreamChannel widget at the top of your widget tree', + ); - return streamChannelState; + return streamChannelState!; } @override diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index a72425ed..89d4853a 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -70,12 +70,12 @@ class StreamChatCore extends StatefulWidget { streamChatState = context.findAncestorStateOfType(); - if (streamChatState == null) { - throw Exception( - 'You must have a StreamChat widget at the top of your widget tree'); - } + assert( + streamChatState != null, + 'You must have a StreamChat widget at the top of your widget tree', + ); - return streamChatState; + return streamChatState!; } } diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 694c52bc..d608ddc4 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -124,12 +124,12 @@ class UserListCoreState extends State with WidgetsBindingObserver { @override void didChangeDependencies() { - super.didChangeDependencies(); loadData(); if (widget.userListController != null) { widget.userListController!.loadData = loadData; widget.userListController!.paginateData = paginateData; } + super.didChangeDependencies(); } @override diff --git a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart index 9b33f75c..f19efa8d 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -30,11 +30,12 @@ class UsersBloc extends StatefulWidget { state = context.findAncestorStateOfType(); - if (state == null) { - throw Exception('You must have a UsersBloc widget as ancestor'); - } + assert( + state != null, + 'You must have a UsersBloc widget as ancestor', + ); - return state; + return state!; } } @@ -54,6 +55,8 @@ class UsersBlocState extends State /// The stream notifying the state of queryUsers call Stream get queryUsersLoading => _queryUsersLoadingController.stream; + late final StreamChatCoreState _streamChatCore; + /// The Query Users method allows you to search for users and see if they are /// online/offline. /// [API Reference](https://getstream.io/chat/docs/flutter-dart/query_users/?language=dart) @@ -63,7 +66,7 @@ class UsersBlocState extends State Map? options, PaginationParams? pagination, }) async { - final client = StreamChatCore.of(context).client; + final client = _streamChatCore.client; if (_queryUsersLoadingController.value == true) return; @@ -101,6 +104,12 @@ class UsersBlocState extends State } } + @override + void didChangeDependencies() { + _streamChatCore = StreamChatCore.of(context); + super.didChangeDependencies(); + } + @override Widget build(BuildContext context) { super.build(context); From 63c447c7a5cd723c7031eecff2ece5bb70085e1a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 6 May 2021 13:33:12 +0200 Subject: [PATCH 02/16] fix analysis --- .../lib/src/message_search_list_core.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 5c661ca1..95761365 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -105,11 +105,11 @@ class MessageSearchListCore extends StatefulWidget { /// The current state of the [MessageSearchListCore]. class MessageSearchListCoreState extends State { - late final MessageSearchBlocState messageSearchBloc; + late final MessageSearchBlocState _messageSearchBloc; @override void didChangeDependencies() { - messageSearchBloc = MessageSearchBloc.of(context); + _messageSearchBloc = MessageSearchBloc.of(context); loadData(); if (widget.messageSearchListController != null) { widget.messageSearchListController!.loadData = loadData; @@ -119,7 +119,7 @@ class MessageSearchListCoreState extends State { } @override - Widget build(BuildContext context) => _buildListView(messageSearchBloc); + Widget build(BuildContext context) => _buildListView(_messageSearchBloc); Widget _buildListView(MessageSearchBlocState messageSearchBloc) => StreamBuilder>( @@ -140,7 +140,7 @@ class MessageSearchListCoreState extends State { ); /// Fetches initial messages and updates the widget - Future loadData() => messageSearchBloc.search( + Future loadData() => _messageSearchBloc.search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, @@ -149,11 +149,11 @@ class MessageSearchListCoreState extends State { ); /// Fetches more messages with updated pagination and updates the widget - Future paginateData() => messageSearchBloc.search( + Future paginateData() => _messageSearchBloc.search( filter: widget.filters, sort: widget.sortOptions, pagination: widget.paginationParams!.copyWith( - offset: messageSearchBloc.messageResponses?.length ?? 0, + offset: _messageSearchBloc.messageResponses?.length ?? 0, ), query: widget.messageQuery, messageFilter: widget.messageFilters, From 777eaa23fc3eb43916770573c7da48f073ed7cd4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 6 May 2021 13:43:02 +0200 Subject: [PATCH 03/16] remove final --- .../stream_chat_flutter_core/lib/src/channel_list_core.dart | 4 ++-- packages/stream_chat_flutter_core/lib/src/channels_bloc.dart | 2 +- .../stream_chat_flutter_core/lib/src/message_list_core.dart | 2 +- .../stream_chat_flutter_core/lib/src/message_search_bloc.dart | 2 +- .../lib/src/message_search_list_core.dart | 2 +- packages/stream_chat_flutter_core/lib/src/users_bloc.dart | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index e327f0ba..0460cf7e 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -119,8 +119,8 @@ class ChannelListCore extends StatefulWidget { /// The current state of the [ChannelListCore]. class ChannelListCoreState extends State { - late final ChannelsBlocState _channelsBloc; - late final StreamChatCoreState _streamChatCoreState; + late ChannelsBlocState _channelsBloc; + late StreamChatCoreState _streamChatCoreState; @override Widget build(BuildContext context) => _buildListView(_channelsBloc); diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart index 966a3fe5..f29c76f9 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -62,7 +62,7 @@ class ChannelsBloc extends StatefulWidget { /// The current state of the [ChannelsBloc]. class ChannelsBlocState extends State with AutomaticKeepAliveClientMixin { - late final StreamChatCoreState _streamChatCoreState; + late StreamChatCoreState _streamChatCoreState; @override Widget build(BuildContext context) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 74c6c1ea..3e0ac1c7 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -109,7 +109,7 @@ class MessageListCore extends StatefulWidget { /// The current state of the [MessageListCore]. class MessageListCoreState extends State { - late final StreamChannelState _streamChannel; + late StreamChannelState _streamChannel; bool get _upToDate => _streamChannel.channel.state?.isUpToDate ?? true; diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart index c98c470e..386ba388 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart @@ -41,7 +41,7 @@ class MessageSearchBloc extends StatefulWidget { /// The current state of the [MessageSearchBloc] class MessageSearchBlocState extends State with AutomaticKeepAliveClientMixin { - late final StreamChatCoreState _streamChatCoreState; + late StreamChatCoreState _streamChatCoreState; /// The current messages list List? get messageResponses => _messageResponses.value; diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 95761365..e2f0ae2e 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -105,7 +105,7 @@ class MessageSearchListCore extends StatefulWidget { /// The current state of the [MessageSearchListCore]. class MessageSearchListCoreState extends State { - late final MessageSearchBlocState _messageSearchBloc; + late MessageSearchBlocState _messageSearchBloc; @override void didChangeDependencies() { diff --git a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart index f19efa8d..57edfecb 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -55,7 +55,7 @@ class UsersBlocState extends State /// The stream notifying the state of queryUsers call Stream get queryUsersLoading => _queryUsersLoadingController.stream; - late final StreamChatCoreState _streamChatCore; + late StreamChatCoreState _streamChatCore; /// The Query Users method allows you to search for users and see if they are /// online/offline. From fff82c8455b91a22cbc0e0ded554c675f3e07c6c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 6 May 2021 14:52:38 +0200 Subject: [PATCH 04/16] fix tests --- .../lib/src/channel_bottom_sheet.dart | 59 +++++++++++-------- .../lib/src/video_thumbnail_image.dart | 16 +++-- .../test/channel_list_core_test.dart | 2 +- .../test/channels_bloc_test.dart | 2 +- .../test/message_list_core_test.dart | 2 +- .../test/message_search_bloc_test.dart | 19 +----- .../test/message_search_list_core_test.dart | 2 +- .../test/user_list_core_test.dart | 2 +- .../test/users_bloc_test.dart | 19 +----- 9 files changed, 52 insertions(+), 71 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart index 89f19466..eaabb7e2 100644 --- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart @@ -17,18 +17,22 @@ class ChannelBottomSheet extends StatefulWidget { class _ChannelBottomSheetState extends State { bool _showActions = true; + late StreamChannelState _streamChannelState; + late StreamChatThemeData _streamChatThemeData; + late StreamChatState _streamChatState; + @override Widget build(BuildContext context) { - final channel = StreamChannel.of(context).channel; + final channel = _streamChannelState.channel; final members = channel.state?.members ?? []; - final userAsMember = members - .firstWhere((e) => e.user?.id == StreamChat.of(context).user?.id); + final userAsMember = + members.firstWhere((e) => e.user?.id == _streamChatState.user?.id); final isOwner = userAsMember.role == 'owner'; return Material( - color: StreamChatTheme.of(context).colorTheme.white, + color: _streamChatThemeData.colorTheme.white, clipBehavior: Clip.antiAlias, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -48,8 +52,7 @@ class _ChannelBottomSheetState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: ChannelName( - textStyle: - StreamChatTheme.of(context).textTheme.headlineBold, + textStyle: _streamChatThemeData.textTheme.headlineBold, ), ), ), @@ -59,10 +62,9 @@ class _ChannelBottomSheetState extends State { Center( child: ChannelInfo( showTypingIndicator: false, - channel: StreamChannel.of(context).channel, - textStyle: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle, + channel: _streamChannelState.channel, + textStyle: + _streamChatThemeData.channelPreviewTheme.subtitle, ), ), const SizedBox( @@ -94,8 +96,7 @@ class _ChannelBottomSheetState extends State { .user ?.name ?? '', - style: - StreamChatTheme.of(context).textTheme.footnoteBold, + style: _streamChatThemeData.textTheme.footnoteBold, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -128,9 +129,8 @@ class _ChannelBottomSheetState extends State { ), Text( members[index].user?.name ?? '', - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold, + style: + _streamChatThemeData.textTheme.footnoteBold, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -146,7 +146,7 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.user( - color: StreamChatTheme.of(context).colorTheme.grey, + color: _streamChatThemeData.colorTheme.grey, ), ), title: 'View Info', @@ -157,7 +157,7 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.userRemove( - color: StreamChatTheme.of(context).colorTheme.grey, + color: _streamChatThemeData.colorTheme.grey, ), ), title: 'Leave Group', @@ -176,12 +176,11 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: _streamChatThemeData.colorTheme.accentRed, ), ), title: 'Delete Conversation', - titleColor: - StreamChatTheme.of(context).colorTheme.accentRed, + titleColor: _streamChatThemeData.colorTheme.accentRed, onTap: () async { setState(() { _showActions = false; @@ -196,7 +195,7 @@ class _ChannelBottomSheetState extends State { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StreamSvgIcon.closeSmall( - color: StreamChatTheme.of(context).colorTheme.grey, + color: _streamChatThemeData.colorTheme.grey, ), ), title: 'Cancel', @@ -209,6 +208,14 @@ class _ChannelBottomSheetState extends State { ); } + @override + void didChangeDependencies() { + _streamChannelState = StreamChannel.of(context); + _streamChatThemeData = StreamChatTheme.of(context); + _streamChatState = StreamChat.of(context); + super.didChangeDependencies(); + } + Future _showDeleteDialog() async { final res = await showConfirmationDialog( context, @@ -217,10 +224,10 @@ class _ChannelBottomSheetState extends State { question: 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: _streamChatThemeData.colorTheme.accentRed, ), ); - final channel = StreamChannel.of(context).channel; + final channel = _streamChannelState.channel; if (res == true) { await channel.delete(); Navigator.pop(context); @@ -235,12 +242,12 @@ class _ChannelBottomSheetState extends State { question: 'Are you sure you want to leave this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.userRemove( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: _streamChatThemeData.colorTheme.accentRed, ), ); if (res == true) { - final channel = StreamChannel.of(context).channel; - final user = StreamChat.of(context).user; + final channel = _streamChannelState.channel; + final user = _streamChatState.user; if (user != null) { await channel.removeMembers([user.id]); } diff --git a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart index 8cafbbff..23cc0e35 100644 --- a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart @@ -2,9 +2,9 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; -import 'package:stream_chat_flutter/src/video_service.dart'; /// Widget for creating video thumbnail image class VideoThumbnailImage extends StatefulWidget { @@ -47,6 +47,7 @@ class VideoThumbnailImage extends StatefulWidget { class _VideoThumbnailImageState extends State { late Future thumbnailFuture; + late StreamChatThemeData _streamChatTheme; @override void initState() { @@ -57,6 +58,12 @@ class _VideoThumbnailImageState extends State { super.initState(); } + @override + void didChangeDependencies() { + _streamChatTheme = StreamChatTheme.of(context); + super.didChangeDependencies(); + } + @override void didUpdateWidget(covariant VideoThumbnailImage oldWidget) { if (oldWidget.video != widget.video || oldWidget.format != widget.format) { @@ -87,11 +94,8 @@ class _VideoThumbnailImageState extends State { constraints: const BoxConstraints.expand(), child: widget.placeholderBuilder?.call(context) ?? Shimmer.fromColors( - baseColor: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, + baseColor: _streamChatTheme.colorTheme.greyGainsboro, + highlightColor: _streamChatTheme.colorTheme.whiteSmoke, child: Image.asset( 'images/placeholder.png', fit: BoxFit.cover, diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart index 53303231..14cbc232 100644 --- a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -46,7 +46,7 @@ void main() { await tester.pumpWidget(channelListCore); expect(find.byKey(channelListCoreKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart index 19d327bd..ff373878 100644 --- a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart @@ -46,7 +46,7 @@ void main() { expect(find.byKey(channelsBlocKey), findsNothing); expect(find.byKey(childKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index 9aea9489..f6ea335f 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -78,7 +78,7 @@ void main() { await tester.pumpWidget(messageListCore); expect(find.byKey(messageListCoreKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart index 91c07435..4d67ebd9 100644 --- a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart @@ -35,27 +35,12 @@ void main() { 'messageSearchBlocState.search() should throw if used where ' 'StreamChat is not present in the widget tree', (tester) async { - const messageSearchBlocKey = Key('messageSearchBloc'); - const childKey = Key('child'); final messageSearchBloc = MessageSearchBloc( - key: messageSearchBlocKey, - child: Offstage(key: childKey), + child: Offstage(), ); await tester.pumpWidget(messageSearchBloc); - - expect(find.byKey(messageSearchBlocKey), findsOneWidget); - expect(find.byKey(childKey), findsOneWidget); - - final usersBlocState = tester.state( - find.byKey(messageSearchBlocKey), - ); - - try { - await usersBlocState.search(filter: testFilter); - } catch (e) { - expect(e, isInstanceOf()); - } + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart index 1f7414d0..f704c9fd 100644 --- a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -45,7 +45,7 @@ void main() { await tester.pumpWidget(messageSearchListCore); expect(find.byKey(messageSearchListCoreKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/user_list_core_test.dart b/packages/stream_chat_flutter_core/test/user_list_core_test.dart index a0f8bd51..820e479f 100644 --- a/packages/stream_chat_flutter_core/test/user_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/user_list_core_test.dart @@ -49,7 +49,7 @@ void main() { await tester.pumpWidget(userListCore); expect(find.byKey(userListCoreKey), findsNothing); - expect(tester.takeException(), isInstanceOf()); + expect(tester.takeException(), isInstanceOf()); }, ); diff --git a/packages/stream_chat_flutter_core/test/users_bloc_test.dart b/packages/stream_chat_flutter_core/test/users_bloc_test.dart index 8c348211..2722d08a 100644 --- a/packages/stream_chat_flutter_core/test/users_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/users_bloc_test.dart @@ -35,27 +35,12 @@ void main() { 'usersBlocState.queryUsers() should throw if used where ' 'StreamChat is not present in the widget tree', (tester) async { - const usersBlocKey = Key('usersBloc'); - const childKey = Key('child'); final usersBloc = UsersBloc( - key: usersBlocKey, - child: Offstage(key: childKey), + child: Offstage(), ); await tester.pumpWidget(usersBloc); - - expect(find.byKey(usersBlocKey), findsOneWidget); - expect(find.byKey(childKey), findsOneWidget); - - final usersBlocState = tester.state( - find.byKey(usersBlocKey), - ); - - try { - await usersBlocState.queryUsers(); - } catch (e) { - expect(e, isInstanceOf()); - } + expect(tester.takeException(), isInstanceOf()); }, ); From 43d5e9063ebec7e91b2e5fe8e6d7c9dbbafdb43f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 10 May 2021 09:21:31 +0200 Subject: [PATCH 05/16] refactoring --- .../lib/src/channel_header.dart | 33 ++-- .../lib/src/channel_image.dart | 60 +++---- .../lib/src/channel_info.dart | 7 +- .../lib/src/channel_list_header.dart | 91 +++++----- .../lib/src/channel_list_view.dart | 158 +++++++++--------- .../lib/src/channel_preview.dart | 46 +++-- 6 files changed, 172 insertions(+), 223 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 8b0eb8fb..d06c128c 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -104,6 +104,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { final channel = StreamChannel.of(context).channel; + final chatThemeData = StreamChatTheme.of(context); final leadingWidget = leading ?? (showBackButton @@ -139,26 +140,18 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { brightness: Theme.of(context).brightness, elevation: 1, leading: leadingWidget, - backgroundColor: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .color, + backgroundColor: + chatThemeData.channelTheme.channelHeaderTheme.color, actions: actions ?? [ Padding( padding: const EdgeInsets.only(right: 10), child: Center( child: ChannelImage( - borderRadius: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .avatarTheme - ?.borderRadius, - constraints: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .avatarTheme - ?.constraints, + borderRadius: chatThemeData.channelTheme + .channelHeaderTheme.avatarTheme?.borderRadius, + constraints: chatThemeData.channelTheme + .channelHeaderTheme.avatarTheme?.constraints, onTap: onImageTap, ), ), @@ -175,20 +168,16 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { children: [ title ?? ChannelName( - textStyle: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .title, + textStyle: chatThemeData + .channelTheme.channelHeaderTheme.title, ), const SizedBox(height: 2), subtitle ?? ChannelInfo( showTypingIndicator: showTypingIndicator, channel: channel, - textStyle: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, + textStyle: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 80e32f07..19495809 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -88,6 +88,7 @@ class ChannelImage extends StatelessWidget { initialData: channel.extraData, builder: (context, snapshot) { String? image; + final chatThemeData = StreamChatTheme.of(context); if (snapshot.data!.containsKey('image') == true) { image = snapshot.data!['image']; } else if (channel.state?.members.length == 2) { @@ -99,20 +100,16 @@ class ChannelImage extends StatelessWidget { initialData: otherMember!.user, builder: (context, snapshot) => UserAvatar( borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.borderRadius, + chatThemeData + .channelPreviewTheme.avatarTheme?.borderRadius, user: snapshot.data ?? otherMember.user!, constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.constraints, + chatThemeData + .channelPreviewTheme.avatarTheme?.constraints, onTap: onTap != null ? (_) => onTap!() : null, selected: selected, - selectionColor: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + selectionColor: + selectionColor ?? chatThemeData.colorTheme.accentBlue, selectionThickness: selectionThickness, )); } else { @@ -127,37 +124,25 @@ class ChannelImage extends StatelessWidget { return GroupImage( images: images ?? [], borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.borderRadius, + chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius, constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.constraints, + chatThemeData.channelPreviewTheme.avatarTheme?.constraints, onTap: onTap, selected: selected, - selectionColor: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + selectionColor: + selectionColor ?? chatThemeData.colorTheme.accentBlue, selectionThickness: selectionThickness, ); } Widget child = ClipRRect( borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.borderRadius, + chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius, child: Container( constraints: constraints ?? - StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - ?.constraints, + chatThemeData.channelPreviewTheme.avatarTheme?.constraints, decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentBlue, ), child: Stack( alignment: Alignment.center, @@ -172,7 +157,7 @@ class ChannelImage extends StatelessWidget { ? snapshot.data!['name'][0] : '', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, fontWeight: FontWeight.bold, ), ), @@ -180,7 +165,7 @@ class ChannelImage extends StatelessWidget { fit: BoxFit.cover, ) else - StreamChatTheme.of(context).defaultChannelImage( + chatThemeData.defaultChannelImage( context, channel, ), @@ -198,20 +183,13 @@ class ChannelImage extends StatelessWidget { child = ClipRRect( key: const Key('selectedImage'), borderRadius: (borderRadius ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.borderRadius ?? + chatThemeData.ownMessageTheme.avatarTheme?.borderRadius ?? BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( constraints: constraints ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.constraints, - color: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + chatThemeData.ownMessageTheme.avatarTheme?.constraints, + color: selectionColor ?? chatThemeData.colorTheme.accentBlue, child: Padding( padding: EdgeInsets.all(selectionThickness), child: child, diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 5fa6f704..f59ac2e0 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -46,7 +46,9 @@ class ChannelInfo extends StatelessWidget { } Widget _buildConnectedTitleState( - BuildContext context, List? members) { + BuildContext context, + List? members, + ) { Widget? alternativeWidget; if (channel.memberCount != null && channel.memberCount! > 2) { @@ -61,8 +63,9 @@ class ChannelInfo extends StatelessWidget { .subtitle, ); } else { + final userId = StreamChat.of(context).user?.id; final otherMember = members?.firstWhereOrNull( - (element) => element.userId != StreamChat.of(context).user?.id, + (element) => element.userId != userId, ); if (otherMember != null) { diff --git a/packages/stream_chat_flutter/lib/src/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel_list_header.dart index afc1884c..325736f1 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -114,6 +114,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { break; } + final chatThemeData = StreamChatTheme.of(context); return InfoTile( // ignore: avoid_bool_literals_in_conditional_expressions showMessage: showConnectionStateTile ? showStatus : false, @@ -121,8 +122,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { child: AppBar( brightness: Theme.of(context).brightness, elevation: 1, - backgroundColor: - StreamChatTheme.of(context).channelListHeaderTheme.color, + backgroundColor: chatThemeData.channelListHeaderTheme.color, centerTitle: true, leading: leading ?? Center( @@ -137,14 +137,10 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { } Scaffold.of(context).openDrawer(); }, - borderRadius: StreamChatTheme.of(context) - .channelListHeaderTheme - .avatarTheme - ?.borderRadius, - constraints: StreamChatTheme.of(context) - .channelListHeaderTheme - .avatarTheme - ?.constraints, + borderRadius: chatThemeData + .channelListHeaderTheme.avatarTheme?.borderRadius, + constraints: chatThemeData + .channelListHeaderTheme.avatarTheme?.constraints, ) : const Offstage(), ), @@ -157,9 +153,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { Color? color; switch (status) { case ConnectionStatus.connected: - color = StreamChatTheme.of(context) - .colorTheme - .accentBlue; + color = chatThemeData.colorTheme.accentBlue; break; case ConnectionStatus.connecting: color = Colors.grey; @@ -209,12 +203,15 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ); } - Widget _buildConnectedTitleState(BuildContext context) => Text( - 'Stream Chat', - style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith( - color: StreamChatTheme.of(context).colorTheme.black, - ), - ); + Widget _buildConnectedTitleState(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return Text( + 'Stream Chat', + style: chatThemeData.textTheme.headlineBold.copyWith( + color: chatThemeData.colorTheme.black, + ), + ); + } Widget _buildConnectingTitleState(BuildContext context) => Row( mainAxisAlignment: MainAxisAlignment.center, @@ -243,39 +240,35 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { Widget _buildDisconnectedTitleState( BuildContext context, StreamChatClient client, - ) => - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Offline...', - style: StreamChatTheme.of(context) - .channelListHeaderTheme - .title - ?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + ) { + final chatThemeData = StreamChatTheme.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Offline...', + style: chatThemeData.channelListHeaderTheme.title?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, ), - TextButton( - onPressed: () async { - await client.disconnect(); - await client.connect(); - }, - child: Text( - 'Try Again', - style: StreamChatTheme.of(context) - .channelListHeaderTheme - .title - ?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), + ), + TextButton( + onPressed: () async { + await client.disconnect(); + await client.connect(); + }, + child: Text( + 'Try Again', + style: chatThemeData.channelListHeaderTheme.title?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + color: chatThemeData.colorTheme.accentBlue, ), ), - ], - ); + ), + ], + ); + } @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index fb713ea8..766fe4ba 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -237,78 +237,70 @@ class _ChannelListViewState extends State { } Widget _buildEmptyWidget(BuildContext context) => LayoutBuilder( - builder: (context, viewportConstraints) => SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: Stack( - children: [ - ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, + builder: (context, viewportConstraints) { + final chatThemeData = StreamChatTheme.of(context); + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Stack( + children: [ + ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: StreamSvgIcon.message( + size: 136, + color: chatThemeData.colorTheme.greyGainsboro, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: Text( + 'Let’s start chatting!', + style: chatThemeData.textTheme.headline, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 52, + ), + child: Text( + 'How about sending your first message to a friend?', + textAlign: TextAlign.center, + style: chatThemeData.textTheme.body.copyWith( + color: chatThemeData.colorTheme.grey, + ), + ), + ), + ], + ), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(8), - child: StreamSvgIcon.message( - size: 136, - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, - ), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Text( - 'Let’s start chatting!', - style: StreamChatTheme.of(context).textTheme.headline, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 52, - ), - child: Text( - 'How about sending your first message to a friend?', - textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith( - color: - StreamChatTheme.of(context).colorTheme.grey, - ), - ), - ), - ], - ), - ), - if (widget.onStartChatPressed != null) - Positioned( - right: 0, - left: 0, - bottom: 32, - child: Center( - child: TextButton( - onPressed: widget.onStartChatPressed, - child: Text( - 'Start a chat', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + if (widget.onStartChatPressed != null) + Positioned( + right: 0, + left: 0, + bottom: 32, + child: Center( + child: TextButton( + onPressed: widget.onStartChatPressed, + child: Text( + 'Start a chat', + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentBlue, + ), + ), ), ), ), - ), - ], - ), - ), + ], + ), + ); + }, ); Widget _buildLoadingWidget(BuildContext context) => ListView( @@ -331,10 +323,11 @@ class _ChannelListViewState extends State { ); Shimmer _buildLoadingItem(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); if (widget.crossAxisCount > 1) { return Shimmer.fromColors( - baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, + baseColor: chatThemeData.colorTheme.greyGainsboro, + highlightColor: chatThemeData.colorTheme.whiteSmoke, child: Column( children: [ const SizedBox(height: 4), @@ -362,12 +355,12 @@ class _ChannelListViewState extends State { ); } else { return Shimmer.fromColors( - baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke, + baseColor: chatThemeData.colorTheme.greyGainsboro, + highlightColor: chatThemeData.colorTheme.whiteSmoke, child: ListTile( leading: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, shape: BoxShape.circle, ), constraints: const BoxConstraints.tightFor( @@ -383,7 +376,7 @@ class _ChannelListViewState extends State { alignment: Alignment.centerLeft, child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: BorderRadius.circular(11), ), constraints: const BoxConstraints.tightFor( @@ -400,7 +393,7 @@ class _ChannelListViewState extends State { alignment: Alignment.centerLeft, child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: BorderRadius.circular(11), ), constraints: const BoxConstraints.expand( @@ -412,7 +405,7 @@ class _ChannelListViewState extends State { Container( margin: const EdgeInsets.only(left: 16), decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: BorderRadius.circular(11), ), constraints: const BoxConstraints.tightFor( @@ -456,12 +449,13 @@ class _ChannelListViewState extends State { ); Widget _listItemBuilder(BuildContext context, int i, List channels) { - final channelsProvider = ChannelsBloc.of(context); + final channelsBloc = ChannelsBloc.of(context); if (i < channels.length) { final channel = channels[i]; final onTap = _getChannelTap(context); - final backgroundColor = StreamChatTheme.of(context).colorTheme.whiteSmoke; + final chatThemeData = StreamChatTheme.of(context); + final backgroundColor = chatThemeData.colorTheme.whiteSmoke; return StreamChannel( key: ValueKey('CHANNEL-${channel.id}'), channel: channel, @@ -506,7 +500,7 @@ class _ChannelListViewState extends State { IconSlideAction( color: backgroundColor, iconWidget: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: chatThemeData.colorTheme.accentRed, ), onTap: () async { final res = await showConfirmationDialog( @@ -517,7 +511,7 @@ class _ChannelListViewState extends State { 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: chatThemeData.colorTheme.accentRed, ), ); if (res == true) { @@ -527,7 +521,7 @@ class _ChannelListViewState extends State { ), ], child: Container( - color: StreamChatTheme.of(context).colorTheme.whiteSnow, + color: chatThemeData.colorTheme.whiteSnow, child: widget.channelPreviewBuilder?.call(context, channel) ?? ChannelPreview( onLongPress: widget.onChannelLongPress, @@ -540,7 +534,7 @@ class _ChannelListViewState extends State { ), ); } else { - return _buildQueryProgressIndicator(context, channelsProvider); + return _buildQueryProgressIndicator(context, channelsBloc); } } diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 70ad0e3f..3189b0cb 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -68,6 +68,8 @@ class ChannelPreview extends StatelessWidget { @override Widget build(BuildContext context) { final channelPreviewTheme = StreamChatTheme.of(context).channelPreviewTheme; + final streamChatState = StreamChat.of(context); + return StreamBuilder( stream: channel.isMutedStream, initialData: channel.isMuted, @@ -130,7 +132,7 @@ class ChannelPreview extends StatelessWidget { (m) => !m.isDeleted && m.shadowed != true, ); if (lastMessage?.user?.id == - StreamChat.of(context).user?.id) { + streamChatState.user?.id) { return Padding( padding: const EdgeInsets.only(right: 4), child: SendingIndicator( @@ -194,6 +196,7 @@ class ChannelPreview extends StatelessWidget { ); Widget _buildSubtitle(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); if (channel.isMuted) { return Row( crossAxisAlignment: CrossAxisAlignment.end, @@ -203,7 +206,7 @@ class ChannelPreview extends StatelessWidget { ), Text( ' Channel is muted', - style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, + style: chatThemeData.channelPreviewTheme.subtitle, ), ], ); @@ -211,7 +214,7 @@ class ChannelPreview extends StatelessWidget { return TypingIndicator( channel: channel, alternativeWidget: _buildLastMessage(context), - style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, + style: chatThemeData.channelPreviewTheme.subtitle, ); } @@ -245,35 +248,24 @@ class ChannelPreview extends StatelessWidget { text = parts.join(' '); + final chatThemeData = StreamChatTheme.of(context); return Text.rich( _getDisplayText( text, lastMessage.mentionedUsers, lastMessage.attachments, - StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.copyWith( - color: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal), - StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.copyWith( - color: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - ?.color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - fontWeight: FontWeight.bold, - ), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + color: chatThemeData.channelPreviewTheme.subtitle?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + color: chatThemeData.channelPreviewTheme.subtitle?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), ), maxLines: 1, overflow: TextOverflow.ellipsis, From 435a88cd1b44b3947f722d8e88e2eb8540fbcfa1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 10 May 2021 16:01:20 +0200 Subject: [PATCH 06/16] migrate missing files --- .../lib/src/date_divider.dart | 9 +- .../lib/src/deleted_message.dart | 67 +- .../lib/src/group_image.dart | 15 +- .../lib/src/image_footer.dart | 30 +- .../lib/src/image_header.dart | 99 +- .../lib/src/info_tile.dart | 43 +- .../lib/src/media_list_view.dart | 16 +- .../lib/src/mention_tile.dart | 122 ++- .../lib/src/message_actions_modal.dart | 255 ++--- .../lib/src/message_input.dart | 869 +++++++++--------- .../lib/src/message_list_view.dart | 165 ++-- .../lib/src/message_reactions_modal.dart | 12 +- .../lib/src/message_search_item.dart | 37 +- .../lib/src/message_search_list_view.dart | 7 +- .../lib/src/message_text.dart | 19 +- .../lib/src/message_widget.dart | 120 +-- .../lib/src/option_list_tile.dart | 93 +- .../lib/src/reaction_bubble.dart | 22 +- .../lib/src/reaction_picker.dart | 12 +- .../lib/src/thread_header.dart | 107 +-- .../lib/src/url_attachment.dart | 151 ++- .../lib/src/user_avatar.dart | 5 +- .../lib/src/user_item.dart | 83 +- .../lib/src/user_list_view.dart | 29 +- .../stream_chat_flutter/lib/src/utils.dart | 241 +++-- 25 files changed, 1292 insertions(+), 1336 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index f409872b..185683be 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -44,18 +44,19 @@ class DateDivider extends StatelessWidget { if (uppercase) dayInfo = dayInfo.toUpperCase(); + final chatThemeData = StreamChatTheme.of(context); return Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.overlayDark, + color: chatThemeData.colorTheme.overlayDark, borderRadius: BorderRadius.circular(8), ), child: Text( dayInfo, - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context).colorTheme.white, - ), + style: chatThemeData.textTheme.footnote.copyWith( + color: chatThemeData.colorTheme.white, + ), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index 96f5606a..93e70b12 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -31,44 +31,41 @@ class DeletedMessage extends StatelessWidget { final bool reverse; @override - Widget build(BuildContext context) => Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Material( - color: messageTheme.messageBackgroundColor, - shape: shape ?? - RoundedRectangleBorder( - borderRadius: borderRadiusGeometry ?? BorderRadius.zero, - side: borderSide ?? - BorderSide( - color: Theme.of(context).brightness == Brightness.dark - ? StreamChatTheme.of(context) - .colorTheme - .white - .withAlpha(24) - : StreamChatTheme.of(context) - .colorTheme - .black - .withAlpha(24), - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Material( + color: messageTheme.messageBackgroundColor, + shape: shape ?? + RoundedRectangleBorder( + borderRadius: borderRadiusGeometry ?? BorderRadius.zero, + side: borderSide ?? + BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? chatThemeData.colorTheme.white.withAlpha(24) + : chatThemeData.colorTheme.black.withAlpha(24), + ), ), - child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: Text( - 'Message deleted', - style: messageTheme.messageText?.copyWith( - fontStyle: FontStyle.italic, - color: messageTheme.createdAt?.color, - ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Text( + 'Message deleted', + style: messageTheme.messageText?.copyWith( + fontStyle: FontStyle.italic, + color: messageTheme.createdAt?.color, ), ), ), ), - ); + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index e5c6ecd2..c6778a91 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -46,18 +46,12 @@ class GroupImage extends StatelessWidget { onTap: onTap, child: ClipRRect( borderRadius: borderRadius ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.borderRadius, + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, child: Container( constraints: constraints ?? - StreamChatTheme.of(context) - .ownMessageTheme - .avatarTheme - ?.constraints, + streamChatTheme.ownMessageTheme.avatarTheme?.constraints, decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: streamChatTheme.colorTheme.accentBlue, ), child: Flex( direction: Axis.vertical, @@ -125,8 +119,7 @@ class GroupImage extends StatelessWidget { BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( - color: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + color: selectionColor ?? streamChatTheme.colorTheme.accentBlue, height: 64, width: 64, child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index 9298e73b..cc79192b 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -77,16 +77,18 @@ class _ImageFooterState extends State { @override Widget build(BuildContext context) { final showShareButton = !kIsWeb; + final mediaQueryData = MediaQuery.of(context); + final chatThemeData = StreamChatTheme.of(context); return SizedBox.fromSize( size: Size( - MediaQuery.of(context).size.width, - MediaQuery.of(context).padding.bottom + widget.preferredSize.height, + mediaQueryData.size.width, + mediaQueryData.padding.bottom + widget.preferredSize.height, ), child: MediaQuery.removePadding( context: context, removeTop: true, child: BottomAppBar( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -98,7 +100,7 @@ class _ImageFooterState extends State { IconButton( icon: StreamSvgIcon.iconShare( size: 24, - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), onPressed: () async { final attachment = @@ -134,8 +136,7 @@ class _ImageFooterState extends State { children: [ Text( '${widget.currentPage + 1} of ${widget.totalPages}', - style: - StreamChatTheme.of(context).textTheme.headlineBold, + style: chatThemeData.textTheme.headlineBold, ), ], ), @@ -143,7 +144,7 @@ class _ImageFooterState extends State { ), IconButton( icon: StreamSvgIcon.iconGrid( - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), onPressed: () => _showPhotosModal(context), ), @@ -155,10 +156,11 @@ class _ImageFooterState extends State { } void _showPhotosModal(context) { + final chatThemeData = StreamChatTheme.of(context); showModalBottomSheet( context: context, - barrierColor: StreamChatTheme.of(context).colorTheme.overlay, - backgroundColor: StreamChatTheme.of(context).colorTheme.white, + barrierColor: chatThemeData.colorTheme.overlay, + backgroundColor: chatThemeData.colorTheme.white, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -189,9 +191,7 @@ class _ImageFooterState extends State { padding: const EdgeInsets.all(16), child: Text( 'Photos', - style: StreamChatTheme.of(context) - .textTheme - .headlineBold, + style: chatThemeData.textTheme.headlineBold, ), ), ), @@ -199,7 +199,7 @@ class _ImageFooterState extends State { alignment: Alignment.centerRight, child: IconButton( icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), onPressed: () => Navigator.maybePop(context), ), @@ -262,9 +262,7 @@ class _ImageFooterState extends State { boxShadow: [ BoxShadow( blurRadius: 8, - color: StreamChatTheme.of(context) - .colorTheme - .black + color: chatThemeData.colorTheme.black .withOpacity(0.3), ), ], diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 0b876aa1..095d7534 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -50,59 +50,58 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final int currentIndex; @override - Widget build(BuildContext context) => AppBar( - brightness: Theme.of(context).brightness, - elevation: 1, - leading: showBackButton - ? IconButton( - icon: StreamSvgIcon.close( - color: StreamChatTheme.of(context).colorTheme.black, - size: 24, - ), - onPressed: onBackPressed, - ) - : const SizedBox(), - backgroundColor: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - actions: [ - if (message.type != 'ephemeral') - IconButton( - icon: StreamSvgIcon.iconMenuPoint( - color: StreamChatTheme.of(context).colorTheme.black, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + leading: showBackButton + ? IconButton( + icon: StreamSvgIcon.close( + color: chatThemeData.colorTheme.black, + size: 24, ), - onPressed: () { - _showMessageActionModalBottomSheet(context); - }, + onPressed: onBackPressed, + ) + : const SizedBox(), + backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color, + actions: [ + if (message.type != 'ephemeral') + IconButton( + icon: StreamSvgIcon.iconMenuPoint( + color: chatThemeData.colorTheme.black, ), - ], - centerTitle: true, - title: message.type != 'ephemeral' - ? InkWell( - onTap: onTitleTap, - child: SizedBox( - height: preferredSize.height, - width: preferredSize.width, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - userName, - style: - StreamChatTheme.of(context).textTheme.headlineBold, - ), - Text( - sentAt, - style: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle, - ), - ], - ), + onPressed: () { + _showMessageActionModalBottomSheet(context); + }, + ), + ], + centerTitle: true, + title: message.type != 'ephemeral' + ? InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + width: preferredSize.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + userName, + style: chatThemeData.textTheme.headlineBold, + ), + Text( + sentAt, + style: chatThemeData.channelPreviewTheme.subtitle, + ), + ], ), - ) - : const SizedBox(), - ); + ), + ) + : const SizedBox(), + ); + } @override final Size preferredSize; diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index 81a701c0..1e935942 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -38,26 +38,29 @@ class InfoTile extends StatelessWidget { final Color? backgroundColor; @override - Widget build(BuildContext context) => PortalEntry( - visible: showMessage, - portalAnchor: tileAnchor ?? Alignment.topCenter, - childAnchor: childAnchor ?? Alignment.bottomCenter, - portal: Container( - height: 25, - color: backgroundColor ?? - StreamChatTheme.of(context).colorTheme.grey.withOpacity(0.9), - child: Center( - child: Text( - message, - style: textStyle ?? - StreamChatTheme.of(context).textTheme.body.copyWith( - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return PortalEntry( + visible: showMessage, + portalAnchor: tileAnchor ?? Alignment.topCenter, + childAnchor: childAnchor ?? Alignment.bottomCenter, + portal: Container( + height: 25, + color: + backgroundColor ?? chatThemeData.colorTheme.grey.withOpacity(0.9), + child: Center( + child: Text( + message, + style: textStyle ?? + chatThemeData.textTheme.body.copyWith( + color: Colors.white, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ), - child: child, - ); + ), + child: child, + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/media_list_view.dart b/packages/stream_chat_flutter/lib/src/media_list_view.dart index e0d0c27e..fe0070f9 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -56,6 +56,7 @@ class _MediaListViewState extends State { position, ) { final media = _media.elementAt(position); + final chatThemeData = StreamChatTheme.of(context); return Padding( padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1), child: InkWell( @@ -89,10 +90,8 @@ class _MediaListViewState extends State { ? 1.0 : 0.0, child: Container( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + color: + chatThemeData.colorTheme.black.withOpacity(0.5), alignment: Alignment.topRight, padding: const EdgeInsets.only( top: 8, @@ -100,13 +99,10 @@ class _MediaListViewState extends State { ), child: CircleAvatar( radius: 12, - backgroundColor: - StreamChatTheme.of(context).colorTheme.white, + backgroundColor: chatThemeData.colorTheme.white, child: StreamSvgIcon.check( size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .black, + color: chatThemeData.colorTheme.black, ), ), ), @@ -128,7 +124,7 @@ class _MediaListViewState extends State { child: Text( media.videoDuration.format(), style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index f3bed355..99b4b962 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; - import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// This widget is used for showing user tiles for mentions @@ -32,71 +31,70 @@ class MentionTile extends StatelessWidget { final Widget? trailing; @override - Widget build(BuildContext context) => SizedBox( - height: 56, - child: Row( - children: [ - const SizedBox( - width: 16, - ), - leading ?? - UserAvatar( - constraints: BoxConstraints.tight( - const Size( - 40, - 40, - ), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return SizedBox( + height: 56, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + leading ?? + UserAvatar( + constraints: BoxConstraints.tight( + const Size( + 40, + 40, ), - user: member.user!, ), - const SizedBox( - width: 8, - ), - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - title ?? - Text( - member.user!.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context).textTheme.bodyBold, + user: member.user!, + ), + const SizedBox( + width: 8, + ), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title ?? + Text( + member.user!.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.bodyBold, + ), + const SizedBox( + height: 2, + ), + subtitle ?? + Text( + '@${member.userId}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.footnoteBold.copyWith( + color: chatThemeData.colorTheme.grey, ), - const SizedBox( - height: 2, - ), - subtitle ?? - Text( - '@${member.userId}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold - .copyWith( - color: - StreamChatTheme.of(context).colorTheme.grey, - ), - ), - ], - ), + ), + ], ), ), - trailing ?? - Padding( - padding: const EdgeInsets.only( - right: 18, - left: 8, - ), - child: StreamSvgIcon.mentions( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), + ), + trailing ?? + Padding( + padding: const EdgeInsets.only( + right: 18, + left: 8, ), - ], - ), - ); + child: StreamSvgIcon.mentions( + color: chatThemeData.colorTheme.accentBlue, + ), + ), + ], + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart index 767cdc76..82ce94e2 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -3,12 +3,12 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/message_action.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Constructs a modal with actions for a message class MessageActionsModal extends StatefulWidget { @@ -108,7 +108,8 @@ class _MessageActionsModalState extends State { Widget build(BuildContext context) => _showMessageOptionsModal(); Widget _showMessageOptionsModal() { - final size = MediaQuery.of(context).size; + final mediaQueryData = MediaQuery.of(context); + final size = mediaQueryData.size; final user = StreamChat.of(context).user; final roughMaxSize = 2 * size.width / 3; @@ -133,6 +134,7 @@ class _MessageActionsModalState extends State { final hasFileAttachment = widget.message.attachments.any((it) => it.type == 'file') == true; + final streamChatThemeData = StreamChatTheme.of(context); return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => Navigator.maybePop(context), @@ -145,7 +147,7 @@ class _MessageActionsModalState extends State { sigmaY: 10, ), child: Container( - color: StreamChatTheme.of(context).colorTheme.overlay, + color: streamChatThemeData.colorTheme.overlay, ), ), ), @@ -226,11 +228,9 @@ class _MessageActionsModalState extends State { left: widget.reverse ? 0 : 40, ), child: SizedBox( - width: MediaQuery.of(context).size.width * 0.75, + width: mediaQueryData.size.width * 0.75, child: Material( - color: StreamChatTheme.of(context) - .colorTheme - .whiteSnow, + color: streamChatThemeData.colorTheme.whiteSnow, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -266,9 +266,8 @@ class _MessageActionsModalState extends State { ].insertBetween( Container( height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .greyWhisper, + color: streamChatThemeData + .colorTheme.greyWhisper, ), ), ), @@ -310,11 +309,12 @@ class _MessageActionsModalState extends State { void _showFlagDialog() async { final client = StreamChat.of(context).client; + final streamChatThemeData = StreamChatTheme.of(context); final answer = await showConfirmationDialog( context, title: 'Flag Message', icon: StreamSvgIcon.flag( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: streamChatThemeData.colorTheme.accentRed, size: 24, ), question: @@ -324,7 +324,7 @@ class _MessageActionsModalState extends State { cancelText: 'CANCEL', ); - final theme = StreamChatTheme.of(context); + final theme = streamChatThemeData; if (answer == true) { try { await client.flagMessage(widget.message.id); @@ -400,48 +400,54 @@ class _MessageActionsModalState extends State { ); } - Widget _buildReplyButton(BuildContext context) => InkWell( - onTap: () { - Navigator.pop(context); - if (widget.onReplyTap != null) { - widget.onReplyTap!(widget.message); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.reply( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Reply', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildReplyButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () { + Navigator.pop(context); + if (widget.onReplyTap != null) { + widget.onReplyTap!(widget.message); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.reply( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Reply', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } - Widget _buildFlagButton(BuildContext context) => InkWell( - onTap: _showFlagDialog, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.iconFlag( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Flag Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildFlagButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: _showFlagDialog, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.iconFlag( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Flag Message', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } Widget _buildDeleteButton(BuildContext context) { final isDeleteFailed = @@ -469,54 +475,61 @@ class _MessageActionsModalState extends State { ); } - Widget _buildCopyButton(BuildContext context) => InkWell( - onTap: () async { - widget.onCopyTap?.call(widget.message); - Navigator.pop(context); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.copy( - size: 24, - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Copy Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildCopyButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () async { + widget.onCopyTap?.call(widget.message); + Navigator.pop(context); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.copy( + size: 24, + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Copy Message', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } - Widget _buildEditMessage(BuildContext context) => InkWell( - onTap: () async { - Navigator.pop(context); - _showEditBottomSheet(context); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.edit( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Edit Message', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildEditMessage(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () async { + Navigator.pop(context); + _showEditBottomSheet(context); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.edit( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Edit Message', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } Widget _buildResendMessage(BuildContext context) { final isUpdateFailed = widget.message.status == MessageSendingStatus.failed_update; + final streamChatThemeData = StreamChatTheme.of(context); return InkWell( onTap: () { Navigator.pop(context); @@ -532,12 +545,12 @@ class _MessageActionsModalState extends State { child: Row( children: [ StreamSvgIcon.circleUp( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: streamChatThemeData.colorTheme.accentBlue, ), const SizedBox(width: 16), Text( isUpdateFailed ? 'Resend Edited Message' : 'Resend', - style: StreamChatTheme.of(context).textTheme.body, + style: streamChatThemeData.textTheme.body, ), ], ), @@ -547,13 +560,13 @@ class _MessageActionsModalState extends State { void _showEditBottomSheet(BuildContext context) { final channel = StreamChannel.of(context).channel; + final streamChatThemeData = StreamChatTheme.of(context); showModalBottomSheet( context: context, elevation: 2, clipBehavior: Clip.hardEdge, isScrollControlled: true, - backgroundColor: - StreamChatTheme.of(context).messageInputTheme.inputBackground, + backgroundColor: streamChatThemeData.messageInputTheme.inputBackground, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), @@ -575,8 +588,7 @@ class _MessageActionsModalState extends State { Padding( padding: const EdgeInsets.all(8), child: StreamSvgIcon.edit( - color: - StreamChatTheme.of(context).colorTheme.greyGainsboro, + color: streamChatThemeData.colorTheme.greyGainsboro, ), ), const Text( @@ -608,27 +620,30 @@ class _MessageActionsModalState extends State { ); } - Widget _buildThreadReplyButton(BuildContext context) => InkWell( - onTap: () { - Navigator.pop(context); - if (widget.onThreadReplyTap != null) { - widget.onThreadReplyTap!(widget.message); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), - child: Row( - children: [ - StreamSvgIcon.thread( - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - const SizedBox(width: 16), - Text( - 'Thread Reply', - style: StreamChatTheme.of(context).textTheme.body, - ), - ], - ), + Widget _buildThreadReplyButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: () { + Navigator.pop(context); + if (widget.onThreadReplyTap != null) { + widget.onThreadReplyTap!(widget.message); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.thread( + color: streamChatThemeData.primaryIconTheme.color, + ), + const SizedBox(width: 16), + Text( + 'Thread Reply', + style: streamChatThemeData.textTheme.body, + ), + ], ), - ); + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 44b54c24..a5ccdb6f 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -12,6 +12,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; @@ -19,11 +20,10 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; import 'package:video_compress/video_compress.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Builder for attachment thumbnails typedef AttachmentThumbnailBuilder = Widget Function( @@ -299,8 +299,9 @@ class MessageInputState extends State { @override Widget build(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); Widget child = Container( - color: StreamChatTheme.of(context).messageInputTheme.inputBackground, + color: streamChatThemeData.messageInputTheme.inputBackground, child: SafeArea( child: GestureDetector( onPanUpdate: (details) { @@ -325,9 +326,7 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.all(8), child: StreamSvgIcon.reply( - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, + color: streamChatThemeData.colorTheme.greyGainsboro, ), ), const Text( @@ -385,68 +384,65 @@ class MessageInputState extends State { ], ); - Widget _buildDmCheckbox() => Row( - children: [ - Container( - height: 16, - width: 16, - foregroundDecoration: BoxDecoration( - border: _sendAsDm - ? null - : Border.all( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - width: 2, - ), + Widget _buildDmCheckbox() { + final streamChatThemeData = StreamChatTheme.of(context); + return Row( + children: [ + Container( + height: 16, + width: 16, + foregroundDecoration: BoxDecoration( + border: _sendAsDm + ? null + : Border.all( + color: streamChatThemeData.colorTheme.black.withOpacity(.5), + width: 2, + ), + borderRadius: BorderRadius.circular(3), + ), + child: Center( + child: Material( borderRadius: BorderRadius.circular(3), - ), - child: Center( - child: Material( - borderRadius: BorderRadius.circular(3), - color: _sendAsDm - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: () { - setState(() { - _sendAsDm = !_sendAsDm; - }); - }, - child: AnimatedCrossFade( - duration: const Duration(milliseconds: 300), - reverseDuration: const Duration(milliseconds: 300), - crossFadeState: _sendAsDm - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: StreamSvgIcon.check( - size: 16, - color: StreamChatTheme.of(context).colorTheme.white, - ), - secondChild: const SizedBox( - height: 16, - width: 16, - ), + color: _sendAsDm + ? streamChatThemeData.colorTheme.accentBlue + : streamChatThemeData.colorTheme.white, + child: InkWell( + onTap: () { + setState(() { + _sendAsDm = !_sendAsDm; + }); + }, + child: AnimatedCrossFade( + duration: const Duration(milliseconds: 300), + reverseDuration: const Duration(milliseconds: 300), + crossFadeState: _sendAsDm + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: StreamSvgIcon.check( + size: 16, + color: streamChatThemeData.colorTheme.white, + ), + secondChild: const SizedBox( + height: 16, + width: 16, ), ), ), ), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Text( - 'Also send as direct message', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + 'Also send as direct message', + style: streamChatThemeData.textTheme.footnote.copyWith( + color: streamChatThemeData.colorTheme.black.withOpacity(0.5), ), ), - ], - ); + ), + ], + ); + } Widget _animateSendButton(BuildContext context) { final sendButton = widget.activeSendButton != null @@ -636,12 +632,9 @@ class MessageInputState extends State { ), Text( _chosenCommand?.name.toUpperCase() ?? '', - style: StreamChatTheme.of(context) - .textTheme - .footnoteBold - .copyWith( - color: Colors.white, - ), + style: theme.textTheme.footnoteBold.copyWith( + color: Colors.white, + ), ), ], ), @@ -839,116 +832,110 @@ class MessageInputState extends State { tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 300), curve: Curves.easeInOutExpo, - builder: (context, val, wid) => Transform.scale( - scale: val, - child: Padding( - padding: const EdgeInsets.all(8), - child: Card( - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - color: StreamChatTheme.of(context).colorTheme.white, - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose( - const Size.fromHeight(400)), - decoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .white, - borderRadius: BorderRadius.circular(8)), - child: ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - if (commands.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - child: StreamSvgIcon.lightning( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + builder: (context, val, wid) { + final streamChatThemeData = StreamChatTheme.of(context); + return Transform.scale( + scale: val, + child: Padding( + padding: const EdgeInsets.all(8), + child: Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + color: streamChatThemeData.colorTheme.white, + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose( + const Size.fromHeight(400)), + decoration: BoxDecoration( + color: streamChatThemeData.colorTheme.white, + borderRadius: BorderRadius.circular(8)), + child: ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + if (commands.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, ), - Text( - 'Instant Commands', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - ) - ], - ), - ), - const SizedBox( - height: 10, - ), - ...commands - .map( - (c) => InkWell( - onTap: () { - _setCommand(c); - }, - child: SizedBox( - height: 40, - child: Row( - children: [ - const SizedBox( - width: 16, - ), - _buildCommandIcon(c.name), - const SizedBox( - width: 8, - ), - Text.rich( - TextSpan( - text: c.name.capitalize(), - style: const TextStyle( - fontWeight: - FontWeight.bold), - children: [ - TextSpan( - text: - ' /${c.name} ${c.args}', - style: - StreamChatTheme.of( - context) - .textTheme - .body - .copyWith( - // ignore: lines_longer_than_80_chars - color: StreamChatTheme.of( - // ignore: lines_longer_than_80_chars - context) - // ignore: lines_longer_than_80_chars - .colorTheme - .grey, - ), - ), - ], - ), - ), - ], - ), + child: StreamSvgIcon.lightning( + color: streamChatThemeData + .colorTheme.accentBlue, ), ), - ) - .toList(), - ], - ), + Text( + 'Instant Commands', + style: TextStyle( + color: streamChatThemeData + .colorTheme.black + .withOpacity(.5), + ), + ) + ], + ), + ), + const SizedBox( + height: 10, + ), + ...commands + .map( + (c) => InkWell( + onTap: () { + _setCommand(c); + }, + child: SizedBox( + height: 40, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + _buildCommandIcon(c.name), + const SizedBox( + width: 8, + ), + Text.rich( + TextSpan( + text: c.name.capitalize(), + style: const TextStyle( + fontWeight: + FontWeight.bold), + children: [ + TextSpan( + text: + ' /${c.name} ${c.args}', + style: streamChatThemeData + .textTheme.body + .copyWith( + // ignore: lines_longer_than_80_chars + color: streamChatThemeData + // ignore: lines_longer_than_80_chars + .colorTheme + .grey, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ) + .toList(), + ], ), ), ), - )), + ), + ); + }), )); } @@ -956,37 +943,30 @@ class MessageInputState extends State { final _attachmentContainsFile = _attachments.values.any((it) => it.type == 'file'); + final chatThemeData = StreamChatTheme.of(context); Color _getIconColor(int index) { + final streamChatThemeData = chatThemeData; switch (index) { case 0: return _attachments.isEmpty - ? StreamChatTheme.of(context).colorTheme.accentBlue + ? streamChatThemeData.colorTheme.accentBlue : (!_attachmentContainsFile - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.2)); + ? streamChatThemeData.colorTheme.accentBlue + : streamChatThemeData.colorTheme.black.withOpacity(0.2)); case 1: return _attachmentContainsFile - ? StreamChatTheme.of(context).colorTheme.accentBlue + ? streamChatThemeData.colorTheme.accentBlue : (_attachments.isEmpty - ? StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5) - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.2)); + ? streamChatThemeData.colorTheme.black.withOpacity(0.5) + : streamChatThemeData.colorTheme.black.withOpacity(0.2)); case 2: return _attachmentContainsFile && _attachments.isNotEmpty - ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) - : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); + ? streamChatThemeData.colorTheme.black.withOpacity(0.2) + : streamChatThemeData.colorTheme.black.withOpacity(0.5); case 3: return _attachmentContainsFile && _attachments.isNotEmpty - ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) - : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); + ? streamChatThemeData.colorTheme.black.withOpacity(0.2) + : streamChatThemeData.colorTheme.black.withOpacity(0.5); default: return Colors.black; } @@ -997,7 +977,7 @@ class MessageInputState extends State { _animateContainer ? const Duration(milliseconds: 300) : Duration.zero, height: _openFilePickerSection ? _filePickerSize : 0, child: Material( - color: StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1061,7 +1041,7 @@ class MessageInputState extends State { }, child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), @@ -1076,8 +1056,7 @@ class MessageInputState extends State { width: 40, height: 4, decoration: BoxDecoration( - color: - StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, borderRadius: BorderRadius.circular(4), ), ), @@ -1090,7 +1069,7 @@ class MessageInputState extends State { Expanded( child: Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, borderRadius: BorderRadius.circular(8), ), child: _PickerWidget( @@ -1166,6 +1145,7 @@ class MessageInputState extends State { } Widget _buildCommandIcon(String iconType) { + final chatThemeData = StreamChatTheme.of(context); switch (iconType) { case 'giphy': return CircleAvatar( @@ -1176,7 +1156,7 @@ class MessageInputState extends State { ); case 'ban': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.iconUserDelete( size: 16, @@ -1185,7 +1165,7 @@ class MessageInputState extends State { ); case 'flag': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.flag( size: 14, @@ -1194,7 +1174,7 @@ class MessageInputState extends State { ); case 'imgur': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: ClipOval( child: StreamSvgIcon.imgur( @@ -1204,7 +1184,7 @@ class MessageInputState extends State { ); case 'mute': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.mute( size: 16, @@ -1213,7 +1193,7 @@ class MessageInputState extends State { ); case 'unban': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.userAdd( size: 16, @@ -1222,7 +1202,7 @@ class MessageInputState extends State { ); case 'unmute': return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.volumeUp( size: 16, @@ -1231,7 +1211,7 @@ class MessageInputState extends State { ); default: return CircleAvatar( - backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, + backgroundColor: chatThemeData.colorTheme.accentBlue, radius: 12, child: StreamSvgIcon.lightning( size: 16, @@ -1249,17 +1229,14 @@ class MessageInputState extends State { Future>? queryMembers; + final channelState = StreamChannel.of(context); if (query.isNotEmpty) { - queryMembers = StreamChannel.of(context) - .channel + queryMembers = channelState.channel .queryMembers(filter: Filter.autoComplete('name', query)) .then((res) => res.members); } - final members = StreamChannel.of(context) - .channel - .state - ?.members + final members = channelState.channel.state?.members .where((m) => m.user?.name.toLowerCase().contains(query) == true) .toList() ?? []; @@ -1281,76 +1258,78 @@ class MessageInputState extends State { tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 300), curve: Curves.easeInOutExpo, - builder: (context, val, wid) => Transform.scale( - scale: val, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: StreamChatTheme.of(context).colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(240)), - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + builder: (context, val, wid) { + final chatThemeData = StreamChatTheme.of(context); + return Transform.scale( + scale: val, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: chatThemeData.colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), ), - child: FutureBuilder>( - future: queryMembers ?? Future.value(members), - initialData: members, - builder: (context, snapshot) => ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - const SizedBox( - height: 8, - ), - ...snapshot.data! - .where((it) => it.user != null) - .map( - (m) => Material( - color: - StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: () { - if (m.user != null) { - _mentionedUsers.add(m.user!); - } + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(240)), + decoration: BoxDecoration( + color: chatThemeData.colorTheme.white, + ), + child: FutureBuilder>( + future: queryMembers ?? Future.value(members), + initialData: members, + builder: (context, snapshot) => ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + const SizedBox( + height: 8, + ), + ...snapshot.data! + .where((it) => it.user != null) + .map( + (m) => Material( + color: chatThemeData.colorTheme.white, + child: InkWell( + onTap: () { + if (m.user != null) { + _mentionedUsers.add(m.user!); + } - splits[splits.length - 1] = m.user!.name; - final rejoin = splits.join('@'); + splits[splits.length - 1] = m.user!.name; + final rejoin = splits.join('@'); - textEditingController.value = - TextEditingValue( - text: rejoin + - textEditingController.text.substring( - textEditingController - .selection.start), - selection: TextSelection.collapsed( - offset: rejoin.length, - ), - ); - _debounce!.cancel(); - _mentionsOverlay?.remove(); - _mentionsOverlay = null; - }, - child: widget.mentionsTileBuilder != null - ? widget.mentionsTileBuilder!(context, m) - : MentionTile(m), + textEditingController.value = + TextEditingValue( + text: rejoin + + textEditingController.text.substring( + textEditingController + .selection.start), + selection: TextSelection.collapsed( + offset: rejoin.length, + ), + ); + _debounce!.cancel(); + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + }, + child: widget.mentionsTileBuilder != null + ? widget.mentionsTileBuilder!(context, m) + : MentionTile(m), + ), ), - ), - ) - .toList(), - const SizedBox( - height: 8, - ), - ], + ) + .toList(), + const SizedBox( + height: 8, + ), + ], + ), ), ), ), - ), - ), + ); + }, ), ), ); @@ -1379,91 +1358,88 @@ class MessageInputState extends State { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; - return OverlayEntry( - builder: (context) => Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: StreamChatTheme.of(context).colorTheme.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + return OverlayEntry(builder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return Positioned( + bottom: size.height + MediaQuery.of(context).viewInsets.bottom, + left: 0, + right: 0, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: chatThemeData.colorTheme.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.antiAlias, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(200)), + decoration: BoxDecoration( + boxShadow: const [ + BoxShadow( + spreadRadius: -8, + blurRadius: 5, + offset: Offset(0, -4), ), - clipBehavior: Clip.antiAlias, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(200)), - decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - spreadRadius: -8, - blurRadius: 5, - offset: Offset(0, -4), - ), - ], - color: StreamChatTheme.of(context).colorTheme.white, - ), - child: ListView.builder( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - itemCount: emojis.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return Padding( - padding: const EdgeInsets.only(left: 8, top: 8), - child: Row( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8), - child: StreamSvgIcon.smile( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), - ), - Flexible( - child: Text( - 'Emoji matching "$query"', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - ), - ) - ], + ], + color: chatThemeData.colorTheme.white, + ), + child: ListView.builder( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + itemCount: emojis.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.only(left: 8, top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: StreamSvgIcon.smile( + color: chatThemeData.colorTheme.accentBlue, ), - ); - } - - final emoji = emojis.elementAt(i - 1)!; - return ListTile( - title: SubstringHighlight( - text: - // ignore: lines_longer_than_80_chars - "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", - term: query, - textStyleHighlight: - Theme.of(context).textTheme.headline6!.copyWith( - fontSize: 14.5, - fontWeight: FontWeight.bold, - ), - textStyle: - Theme.of(context).textTheme.headline6!.copyWith( - fontSize: 14.5, - ), ), - onTap: () { - _chooseEmoji(splits, emoji); - }, - ); - }), - ), - ), - )); + Flexible( + child: Text( + 'Emoji matching "$query"', + style: TextStyle( + color: chatThemeData.colorTheme.black + .withOpacity(.5), + ), + ), + ) + ], + ), + ); + } + + final emoji = emojis.elementAt(i - 1)!; + final themeData = Theme.of(context); + return ListTile( + title: SubstringHighlight( + text: + // ignore: lines_longer_than_80_chars + "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", + term: query, + textStyleHighlight: + themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + fontWeight: FontWeight.bold, + ), + textStyle: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + ), + ), + onTap: () { + _chooseEmoji(splits, emoji); + }, + ); + }), + ), + ), + ); + }); } void _chooseEmoji(List splits, Emoji emoji) { @@ -1589,30 +1565,32 @@ class MessageInputState extends State { ); } - Widget _buildRemoveButton(Attachment attachment) => SizedBox( - height: 24, - width: 24, - child: RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: () { - setState(() => _attachments.remove(attachment.id)); - }, - fillColor: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.5), - child: Center( - child: StreamSvgIcon.close( - size: 24, - color: StreamChatTheme.of(context).colorTheme.white, - ), + Widget _buildRemoveButton(Attachment attachment) { + final chatThemeData = StreamChatTheme.of(context); + return SizedBox( + height: 24, + width: 24, + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: () { + setState(() => _attachments.remove(attachment.id)); + }, + fillColor: chatThemeData.colorTheme.black.withOpacity(.5), + child: Center( + child: StreamSvgIcon.close( + size: 24, + color: chatThemeData.colorTheme.white, ), ), - ); + ), + ); + } Widget _buildAttachment(Attachment attachment) { if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) == @@ -1642,18 +1620,18 @@ class MessageInputState extends State { fit: BoxFit.cover, errorWidget: (_, obj, trace) => getFileTypeImage(attachment.extraData['other'] as String?), - progressIndicatorBuilder: (context, _, progress) => - Shimmer.fromColors( - baseColor: - StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), - ), + progressIndicatorBuilder: (context, _, progress) { + final chatThemeData = StreamChatTheme.of(context); + return Shimmer.fromColors( + baseColor: chatThemeData.colorTheme.greyGainsboro, + highlightColor: chatThemeData.colorTheme.whiteSmoke, + child: Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ), + ); + }, ); case 'video': return Stack( @@ -1685,17 +1663,14 @@ class MessageInputState extends State { Widget _buildCommandButton() { final s = textEditingController.text.trim(); + final chatThemeData = StreamChatTheme.of(context); return IconButton( icon: StreamSvgIcon.lightning( color: s.isNotEmpty - ? StreamChatTheme.of(context).colorTheme.greyGainsboro + ? chatThemeData.colorTheme.greyGainsboro : (_commandsOverlay != null - ? StreamChatTheme.of(context) - .messageInputTheme - .actionButtonColor - : StreamChatTheme.of(context) - .messageInputTheme - .actionButtonIdleColor), + ? chatThemeData.messageInputTheme.actionButtonColor + : chatThemeData.messageInputTheme.actionButtonIdleColor), ), padding: const EdgeInsets.all(0), constraints: const BoxConstraints.tightFor( @@ -1730,39 +1705,40 @@ class MessageInputState extends State { ); } - Widget _buildAttachmentButton() => IconButton( - icon: StreamSvgIcon.attach( - color: _openFilePickerSection - ? StreamChatTheme.of(context).messageInputTheme.actionButtonColor - : StreamChatTheme.of(context) - .messageInputTheme - .actionButtonIdleColor, - ), - padding: const EdgeInsets.all(0), - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, - onPressed: () async { - _emojiOverlay?.remove(); - _emojiOverlay = null; - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; + Widget _buildAttachmentButton() { + final chatThemeData = StreamChatTheme.of(context); + return IconButton( + icon: StreamSvgIcon.attach( + color: _openFilePickerSection + ? chatThemeData.messageInputTheme.actionButtonColor + : chatThemeData.messageInputTheme.actionButtonIdleColor, + ), + padding: const EdgeInsets.all(0), + constraints: const BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; - if (_openFilePickerSection) { - setState(() { - _animateContainer = true; - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - } else { - showAttachmentModal(); - } - }, - ); + if (_openFilePickerSection) { + setState(() { + _animateContainer = true; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + } else { + showAttachmentModal(); + } + }, + ); + } /// Show the attachment modal, making the user choose where to /// pick a media from @@ -2103,8 +2079,9 @@ class MessageInputState extends State { StreamSubscription? _keyboardListener; void _showErrorAlert(String description) { + final chatThemeData = StreamChatTheme.of(context); showModalBottomSheet( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, + backgroundColor: chatThemeData.colorTheme.white, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -2118,7 +2095,7 @@ class MessageInputState extends State { height: 26, ), StreamSvgIcon.error( - color: StreamChatTheme.of(context).colorTheme.accentRed, + color: chatThemeData.colorTheme.accentRed, size: 24, ), const SizedBox( @@ -2126,7 +2103,7 @@ class MessageInputState extends State { ), Text( 'Something went wrong', - style: StreamChatTheme.of(context).textTheme.headlineBold, + style: chatThemeData.textTheme.headlineBold, ), const SizedBox( height: 7, @@ -2142,8 +2119,7 @@ class MessageInputState extends State { height: 36, ), Container( - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), + color: chatThemeData.colorTheme.black.withOpacity(.08), height: 1, ), Row( @@ -2155,13 +2131,8 @@ class MessageInputState extends State { }, child: Text( 'OK', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue), + style: chatThemeData.textTheme.bodyBold + .copyWith(color: chatThemeData.colorTheme.accentBlue), ), ), ], @@ -2291,6 +2262,7 @@ class __PickerWidgetState extends State<_PickerWidget> { return const Center(child: CircularProgressIndicator()); } + final chatThemeData = StreamChatTheme.of(context); if (snapshot.data!) { if (widget.containsFile) { return GestureDetector( @@ -2299,12 +2271,12 @@ class __PickerWidgetState extends State<_PickerWidget> { }, child: Container( constraints: const BoxConstraints.expand(), - color: StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, alignment: Alignment.center, child: Text( 'Add more files', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentBlue, fontWeight: FontWeight.bold, ), ), @@ -2322,7 +2294,7 @@ class __PickerWidgetState extends State<_PickerWidget> { PhotoManager.openSetting(); }, child: Container( - color: StreamChatTheme.of(context).colorTheme.whiteSmoke, + color: chatThemeData.colorTheme.whiteSmoke, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -2331,27 +2303,22 @@ class __PickerWidgetState extends State<_PickerWidget> { 'svgs/icon_picture_empty_state.svg', package: 'stream_chat_flutter', height: 140, - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + color: chatThemeData.colorTheme.greyGainsboro, ), Text( // ignore: lines_longer_than_80_chars 'Please enable access to your photos \nand videos so you can share them with friends.', - style: StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context).colorTheme.grey), + style: chatThemeData.textTheme.body + .copyWith(color: chatThemeData.colorTheme.grey), textAlign: TextAlign.center, ), const SizedBox(height: 6), Center( child: Text( 'Allow access to your gallery', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentBlue, + ), ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 2800a041..661ce885 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -6,15 +6,15 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:rxdart/rxdart.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_widget.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/src/swipeable.dart'; import 'package:stream_chat_flutter/src/system_message.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:visibility_detector/visibility_detector.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; -import 'package:stream_chat_flutter/src/swipeable.dart'; /// Widget builder for message typedef MessageBuilder = Widget Function( @@ -274,15 +274,15 @@ class _MessageListViewState extends State { bool _showScrollToBottom = false; late final ItemPositionsListener _itemPositionListener; int? _messageListLength; - StreamChannelState? streamChannel; + late StreamChannelState streamChannel; int? get _initialIndex { if (widget.initialScrollIndex != null) return widget.initialScrollIndex; - if (streamChannel!.initialMessageId != null) { - final messages = streamChannel!.channel.state!.messages; + if (streamChannel.initialMessageId != null) { + final messages = streamChannel.channel.state!.messages; final totalMessages = messages.length; final messageIndex = - messages.indexWhere((e) => e.id == streamChannel!.initialMessageId); + messages.indexWhere((e) => e.id == streamChannel.initialMessageId); final index = totalMessages - messageIndex; if (index != 0) return index - 1; return index; @@ -295,9 +295,9 @@ class _MessageListViewState extends State { return 0; } - bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id; + bool _isInitialMessage(String id) => streamChannel.initialMessageId == id; - bool get _upToDate => streamChannel!.channel.state!.isUpToDate; + bool get _upToDate => streamChannel.channel.state!.isUpToDate; bool get _isThreadConversation => widget.parentMessage != null; @@ -316,46 +316,37 @@ class _MessageListViewState extends State { final MessageListController _messageListController = MessageListController(); @override - Widget build(BuildContext context) => MessageListCore( - messageFilter: widget.messageFilter, - loadingBuilder: widget.loadingBuilder ?? - (context) => const Center( - child: CircularProgressIndicator(), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return MessageListCore( + messageFilter: widget.messageFilter, + loadingBuilder: widget.loadingBuilder ?? + (context) => const Center( + child: CircularProgressIndicator(), + ), + emptyBuilder: widget.emptyBuilder ?? + (context) => Center( + child: Text( + 'No chats here yet...', + style: chatThemeData.textTheme.footnote.copyWith( + color: chatThemeData.colorTheme.black.withOpacity(.5)), ), - emptyBuilder: widget.emptyBuilder ?? - (context) => Center( - child: Text( - 'No chats here yet...', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), - ), + ), + messageListBuilder: + widget.messageListBuilder ?? (context, list) => _buildListView(list), + messageListController: _messageListController, + parentMessage: widget.parentMessage, + showScrollToBottom: widget.showScrollToBottom, + errorWidgetBuilder: widget.errorWidgetBuilder ?? + (BuildContext context, Object error) => Center( + child: Text( + 'Something went wrong', + style: chatThemeData.textTheme.footnote.copyWith( + color: chatThemeData.colorTheme.black.withOpacity(.5)), ), - messageListBuilder: widget.messageListBuilder ?? - (context, list) => _buildListView(list), - messageListController: _messageListController, - parentMessage: widget.parentMessage, - showScrollToBottom: widget.showScrollToBottom, - errorWidgetBuilder: widget.errorWidgetBuilder ?? - (BuildContext context, Object error) => Center( - child: Text( - 'Something went wrong', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), - ), - ), - ); + ), + ); + } Widget _buildListView(List data) { messages = data; @@ -448,10 +439,10 @@ class _MessageListViewState extends State { if (i == 0) return const SizedBox(height: 30); if (i == messages.length + 1) { final replyCount = widget.parentMessage!.replyCount; + final chatThemeData = StreamChatTheme.of(context); return Container( decoration: BoxDecoration( - gradient: - StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: chatThemeData.colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.all(8), @@ -459,10 +450,8 @@ class _MessageListViewState extends State { // ignore: lines_longer_than_80_chars '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, + style: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, ), ), ); @@ -616,8 +605,8 @@ class _MessageListViewState extends State { Widget _buildScrollToBottom() => StreamBuilder>( stream: Rx.combineLatest2( - streamChannel!.channel.state!.isUpToDateStream, - streamChannel!.channel.state!.unreadCountStream, + streamChannel.channel.state!.isUpToDateStream, + streamChannel.channel.state!.unreadCountStream, (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ), builder: (_, snapshot) { @@ -633,8 +622,9 @@ class _MessageListViewState extends State { } final unreadCount = snapshot.data!.item2; final showUnreadCount = unreadCount > 0 && - streamChannel!.channel.state!.members.any((e) => - e.userId == streamChannel!.channel.client.state.user!.id); + streamChannel.channel.state!.members.any((e) => + e.userId == streamChannel.channel.client.state.user!.id); + final chatThemeData = StreamChatTheme.of(context); return Positioned( bottom: 8, right: 8, @@ -644,15 +634,15 @@ class _MessageListViewState extends State { clipBehavior: Clip.none, children: [ FloatingActionButton( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, + backgroundColor: chatThemeData.colorTheme.white, onPressed: () { if (unreadCount > 0) { - streamChannel!.channel.markRead(); + streamChannel.channel.markRead(); } if (!_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; - streamChannel!.reloadChannel(); + streamChannel.reloadChannel(); } else { setState(() => _showScrollToBottom = false); _scrollController!.scrollTo( @@ -663,7 +653,7 @@ class _MessageListViewState extends State { } }, child: StreamSvgIcon.down( - color: StreamChatTheme.of(context).colorTheme.black, + color: chatThemeData.colorTheme.black, ), ), if (showUnreadCount) @@ -692,12 +682,12 @@ class _MessageListViewState extends State { ); Widget _buildLoadingIndicator( - StreamChannelState? streamChannel, + StreamChannelState streamChannel, QueryDirection direction, ) { final stream = direction == QueryDirection.top - ? streamChannel!.queryTopMessages - : streamChannel!.queryBottomMessages; + ? streamChannel.queryTopMessages + : streamChannel.queryBottomMessages; return StreamBuilder( key: const Key('LOADING-INDICATOR'), stream: stream, @@ -764,7 +754,7 @@ class _MessageListViewState extends State { BuildContext context, Message message, List messages, - StreamChannelState? streamChannel, + StreamChannelState streamChannel, ) { Widget messageWidget; if (widget.messageBuilder != null) { @@ -790,7 +780,7 @@ class _MessageListViewState extends State { onVisibilityChanged: (visibility) { final isVisible = visibility.visibleBounds != Rect.zero; if (isVisible) { - final channel = streamChannel!.channel; + final channel = streamChannel.channel; if (_upToDate && channel.config?.readEvents == true && channel.state!.unreadCount! > 0) { @@ -811,6 +801,7 @@ class _MessageListViewState extends State { final isMyMessage = message.user!.id == StreamChat.of(context).user!.id; final isOnlyEmoji = message.text!.isOnlyEmoji; + final chatThemeData = StreamChatTheme.of(context); return MessageWidget( showReplyMessage: false, showResendMessage: false, @@ -837,8 +828,8 @@ class _MessageListViewState extends State { borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, messageTheme: isMyMessage - ? StreamChatTheme.of(context).ownMessageTheme - : StreamChatTheme.of(context).otherMessageTheme, + ? chatThemeData.ownMessageTheme + : chatThemeData.otherMessageTheme, onShowMessage: widget.onShowMessage, onReturnAction: (action) { switch (action) { @@ -897,7 +888,7 @@ class _MessageListViewState extends State { ); } - final channel = streamChannel!.channel; + final channel = streamChannel.channel; final readList = channel.state?.read?.where((read) { if (read.user.id == userId) return false; return read.lastRead.isAfter(message.createdAt) || @@ -946,6 +937,7 @@ class _MessageListViewState extends State { ? BorderSide.none : null; + final chatThemeData = StreamChatTheme.of(context); Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), message: message, @@ -970,7 +962,7 @@ class _MessageListViewState extends State { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); } else { - await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) { + await streamChannel.loadChannelAtMessage(quotedMessageId).then((_) { WidgetsBinding.instance!.addPostFrameCallback((_) { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); @@ -1013,8 +1005,8 @@ class _MessageListViewState extends State { horizontal: isOnlyEmoji ? 0 : 16.0, ), messageTheme: isMyMessage - ? StreamChatTheme.of(context).ownMessageTheme - : StreamChatTheme.of(context).otherMessageTheme, + ? chatThemeData.ownMessageTheme + : chatThemeData.otherMessageTheme, readList: readList, allRead: allRead, onShowMessage: widget.onShowMessage, @@ -1054,7 +1046,7 @@ class _MessageListViewState extends State { widget.onMessageSwiped?.call(message); }, backgroundIcon: StreamSvgIcon.reply( - color: StreamChatTheme.of(context).colorTheme.accentBlue, + color: chatThemeData.colorTheme.accentBlue, ), child: child, ), @@ -1064,7 +1056,7 @@ class _MessageListViewState extends State { if (!initialMessageHighlightComplete && widget.highlightInitialMessage && _isInitialMessage(message.id)) { - final colorTheme = StreamChatTheme.of(context).colorTheme; + final colorTheme = chatThemeData.colorTheme; final highlightColor = widget.messageHighlightColor ?? colorTheme.highlight; child = TweenAnimationBuilder( @@ -1100,14 +1092,23 @@ class _MessageListViewState extends State { initialIndex = _initialIndex; initialAlignment = _initialAlignment; + _getOnThreadTap(); + super.initState(); + } + + @override + void didChangeDependencies() { + streamChannel = StreamChannel.of(context); + + _messageNewListener?.cancel(); _messageNewListener = - streamChannel!.channel.on(EventType.messageNew).listen((event) { + streamChannel.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; } if (event.message!.user!.id == - streamChannel!.channel.client.state.user!.id) { + streamChannel.channel.client.state.user!.id) { WidgetsBinding.instance!.addPostFrameCallback((_) { _scrollController?.jumpTo( index: 0, @@ -1117,11 +1118,9 @@ class _MessageListViewState extends State { }); if (_isThreadConversation) { - streamChannel!.getReplies(widget.parentMessage!.id); + streamChannel.getReplies(widget.parentMessage!.id); } - - _getOnThreadTap(); - super.initState(); + super.didChangeDependencies(); } void _getOnThreadTap() { @@ -1139,12 +1138,12 @@ class _MessageListViewState extends State { context, MaterialPageRoute( builder: (_) => StreamBuilder( - stream: streamChannel!.channel.state!.messagesStream.map( + stream: streamChannel.channel.state!.messagesStream.map( (messages) => messages!.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) => StreamChannel( - channel: streamChannel!.channel, + channel: streamChannel.channel, child: widget.threadBuilder!(context, snapshot.data), ), ), @@ -1157,7 +1156,7 @@ class _MessageListViewState extends State { @override void dispose() { if (!_upToDate) { - streamChannel!.reloadChannel(); + streamChannel.reloadChannel(); } _messageNewListener?.cancel(); super.dispose(); diff --git a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart index e2392b50..76ce215c 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -1,13 +1,13 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Modal widget for displaying message reactions class MessageReactionsModal extends StatelessWidget { @@ -176,8 +176,9 @@ class MessageReactionsModal extends StatelessWidget { Widget _buildReactionCard(BuildContext context) { final currentUser = StreamChat.of(context).user; + final chatThemeData = StreamChatTheme.of(context); return Card( - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -190,7 +191,7 @@ class MessageReactionsModal extends StatelessWidget { children: [ Text( 'Message Reactions', - style: StreamChatTheme.of(context).textTheme.headlineBold, + style: chatThemeData.textTheme.headlineBold, ), const SizedBox(height: 16), Flexible( @@ -220,6 +221,7 @@ class MessageReactionsModal extends StatelessWidget { BuildContext context, ) { final isCurrentUser = reaction.user?.id == currentUser.id; + final chatThemeData = StreamChatTheme.of(context); return ConstrainedBox( constraints: BoxConstraints.loose(const Size( 64, @@ -258,7 +260,7 @@ class MessageReactionsModal extends StatelessWidget { messageTheme.reactionsBorderColor ?? Colors.transparent, backgroundColor: messageTheme.reactionsBackgroundColor ?? Colors.transparent, - maskColor: StreamChatTheme.of(context).colorTheme.white, + maskColor: chatThemeData.colorTheme.white, tailCirclesSpacing: 1, highlightOwnReactions: false, ), @@ -269,7 +271,7 @@ class MessageReactionsModal extends StatelessWidget { const SizedBox(height: 8), Text( reaction.user!.name.split(' ')[0], - style: StreamChatTheme.of(context).textTheme.footnoteBold, + style: chatThemeData.textTheme.footnoteBold, textAlign: TextAlign.center, ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_search_item.dart b/packages/stream_chat_flutter/lib/src/message_search_item.dart index dc276cb3..1f8a31ea 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -35,6 +35,7 @@ class MessageSearchItem extends StatelessWidget { final channel = getMessageResponse.channel; final channelName = channel?.extraData['name']; final user = message.user!; + final chatThemeData = StreamChatTheme.of(context); return ListTile( onTap: onTap, leading: UserAvatar( @@ -49,21 +50,18 @@ class MessageSearchItem extends StatelessWidget { children: [ Text( user.id == StreamChat.of(context).user?.id ? 'You' : user.name, - style: StreamChatTheme.of(context).channelPreviewTheme.title, + style: chatThemeData.channelPreviewTheme.title, ), if (channelName != null) ...[ Text( ' in ', - style: StreamChatTheme.of(context) - .channelPreviewTheme - .title - ?.copyWith( - fontWeight: FontWeight.normal, - ), + style: chatThemeData.channelPreviewTheme.title?.copyWith( + fontWeight: FontWeight.normal, + ), ), Text( channelName as String, - style: StreamChatTheme.of(context).channelPreviewTheme.title, + style: chatThemeData.channelPreviewTheme.title, ), ], ], @@ -121,22 +119,23 @@ class MessageSearchItem extends StatelessWidget { text = parts.join(' '); } + final chatThemeData = StreamChatTheme.of(context); return Text.rich( _getDisplayText( text!, message.mentionedUsers, message.attachments, - StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( - fontStyle: (message.isSystem || message.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - ), - StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( - fontStyle: (message.isSystem || message.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - fontWeight: FontWeight.bold, - ), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + fontStyle: (message.isSystem || message.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + ), + chatThemeData.channelPreviewTheme.subtitle?.copyWith( + fontStyle: (message.isSystem || message.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), ), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart index 1d9ef272..8b8a8dec 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Callback called when tapping on a user typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); @@ -270,12 +270,13 @@ class _MessageSearchListViewState extends State { ); if (widget.showResultCount) { + final chatThemeData = StreamChatTheme.of(context); child = Column( children: [ Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: chatThemeData.colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -285,7 +286,7 @@ class _MessageSearchListViewState extends State { child: Text( '${items.length} results', style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.grey, + color: chatThemeData.colorTheme.grey, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 24a32a09..e5abede0 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -1,8 +1,8 @@ import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Text widget to display in message class MessageText extends StatelessWidget { @@ -31,6 +31,7 @@ class MessageText extends StatelessWidget { Widget build(BuildContext context) { final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n'); + final themeData = Theme.of(context); return MarkdownBody( data: text, onTapLink: ( @@ -60,14 +61,14 @@ class MessageText extends StatelessWidget { } }, styleSheet: MarkdownStyleSheet.fromTheme( - Theme.of(context).copyWith( - textTheme: Theme.of(context).textTheme.apply( - bodyColor: messageTheme.messageText?.color, - decoration: messageTheme.messageText?.decoration, - decorationColor: messageTheme.messageText?.decorationColor, - decorationStyle: messageTheme.messageText?.decorationStyle, - fontFamily: messageTheme.messageText?.fontFamily, - ), + themeData.copyWith( + textTheme: themeData.textTheme.apply( + bodyColor: messageTheme.messageText?.color, + decoration: messageTheme.messageText?.decoration, + decorationColor: messageTheme.messageText?.decorationColor, + decorationStyle: messageTheme.messageText?.decorationStyle, + fontFamily: messageTheme.messageText?.fontFamily, + ), ), ).copyWith( a: messageTheme.messageLinks, diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 1866da0b..85caf4a6 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -8,6 +8,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/image_group.dart'; import 'package:stream_chat_flutter/src/message_action.dart'; import 'package:stream_chat_flutter/src/message_actions_modal.dart'; import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; @@ -15,8 +17,6 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/image_group.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget builder for building attachments typedef AttachmentBuilder = Widget Function( @@ -109,6 +109,7 @@ class MessageWidget extends StatefulWidget { borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); + final mediaQueryData = MediaQuery.of(context); if (attachments.length > 1) { return Padding( padding: attachmentPadding, @@ -118,8 +119,8 @@ class MessageWidget extends StatefulWidget { color: messageTheme.messageBackgroundColor, child: ImageGroup( size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, ), images: attachments, message: message, @@ -142,8 +143,8 @@ class MessageWidget extends StatefulWidget { message: message, messageTheme: messageTheme, size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, ), onShowMessage: onShowMessage, onReturnAction: onReturnAction, @@ -167,24 +168,25 @@ class MessageWidget extends StatefulWidget { return wrapAttachmentWidget( context, Column( - children: attachments - .map((attachment) => VideoAttachment( - attachment: attachment, - messageTheme: messageTheme, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - message: message, - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - onAttachmentTap: onAttachmentTap != null - ? () { - onAttachmentTap(message, attachment); - } - : null, - )) - .toList(), + children: attachments.map((attachment) { + final mediaQueryData = MediaQuery.of(context); + return VideoAttachment( + attachment: attachment, + messageTheme: messageTheme, + size: Size( + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, + ), + message: message, + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + onAttachmentTap: onAttachmentTap != null + ? () { + onAttachmentTap(message, attachment); + } + : null, + ); + }).toList(), ), border, reverse, @@ -200,18 +202,19 @@ class MessageWidget extends StatefulWidget { return wrapAttachmentWidget( context, Column( - children: attachments - .map((attachment) => GiphyAttachment( - attachment: attachment, - message: message, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - )) - .toList(), + children: attachments.map((attachment) { + final mediaQueryData = MediaQuery.of(context); + return GiphyAttachment( + attachment: attachment, + message: message, + size: Size( + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, + ), + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + ); + }).toList(), ), border, reverse, @@ -230,21 +233,24 @@ class MessageWidget extends StatefulWidget { return Column( children: attachments - .map((attachment) => wrapAttachmentWidget( - context, - FileAttachment( - message: message, - attachment: attachment, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), + .map((attachment) { + final mediaQueryData = MediaQuery.of(context); + return wrapAttachmentWidget( + context, + FileAttachment( + message: message, + attachment: attachment, + size: Size( + mediaQueryData.size.width * 0.8, + mediaQueryData.size.height * 0.3, ), - border, - reverse, - attachmentBorderRadiusGeometry as BorderRadius? ?? - BorderRadius.zero, - )) + ), + border, + reverse, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, + ); + }) .insertBetween(SizedBox( height: attachmentPadding.vertical / 2, )) @@ -658,12 +664,13 @@ class _MessageWidgetState extends State widget.onQuotedMessageTap != null ? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId) : null; + final chatThemeData = StreamChatTheme.of(context); return QuotedMessageWidget( onTap: onTap, message: widget.message.quotedMessage!, messageTheme: isMyMessage - ? StreamChatTheme.of(context).otherMessageTheme - : StreamChatTheme.of(context).ownMessageTheme, + ? chatThemeData.otherMessageTheme + : chatThemeData.ownMessageTheme, reverse: widget.reverse, padding: EdgeInsets.only( right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0), @@ -672,6 +679,7 @@ class _MessageWidgetState extends State Widget get _bottomRow { if (isDeleted) { + final chatThemeData = StreamChatTheme.of(context); return Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, @@ -679,16 +687,14 @@ class _MessageWidgetState extends State mainAxisSize: MainAxisSize.min, children: [ StreamSvgIcon.eye( - color: StreamChatTheme.of(context).colorTheme.grey, + color: chatThemeData.colorTheme.grey, size: 16, ), const SizedBox(width: 8), Text( 'Only visible to you', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith(color: StreamChatTheme.of(context).colorTheme.grey), + style: chatThemeData.textTheme.footnote + .copyWith(color: chatThemeData.colorTheme.grey), ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/option_list_tile.dart index 9a11fd7a..10ae1b5f 100644 --- a/packages/stream_chat_flutter/lib/src/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/option_list_tile.dart @@ -41,56 +41,55 @@ class OptionListTile extends StatelessWidget { final TextStyle? titleTextStyle; @override - Widget build(BuildContext context) => Column( - children: [ - Container( - color: separatorColor ?? - StreamChatTheme.of(context).colorTheme.greyGainsboro, - height: 1, - ), - Material( - color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, - child: SizedBox( - height: 63, - child: InkWell( - onTap: onTap, - child: Row( - children: [ - if (leading != null) Center(child: leading), - if (leading == null) - const SizedBox( - width: 16, - ), - Expanded( - flex: 4, - child: Text( - title!, - style: titleTextStyle ?? - (titleColor == null - ? StreamChatTheme.of(context).textTheme.bodyBold - : StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: titleColor, - )), + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return Column( + children: [ + Container( + color: separatorColor ?? chatThemeData.colorTheme.greyGainsboro, + height: 1, + ), + Material( + color: tileColor ?? chatThemeData.colorTheme.white, + child: SizedBox( + height: 63, + child: InkWell( + onTap: onTap, + child: Row( + children: [ + if (leading != null) Center(child: leading), + if (leading == null) + const SizedBox( + width: 16, + ), + Expanded( + flex: 4, + child: Text( + title!, + style: titleTextStyle ?? + (titleColor == null + ? chatThemeData.textTheme.bodyBold + : chatThemeData.textTheme.bodyBold.copyWith( + color: titleColor, + )), + ), + ), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.only(right: 16), + child: Align( + alignment: Alignment.centerRight, + child: trailing ?? Container(), ), ), - Expanded( - flex: 2, - child: Padding( - padding: const EdgeInsets.only(right: 16), - child: Align( - alignment: Alignment.centerRight, - child: trailing ?? Container(), - ), - ), - ), - ], - ), + ), + ], ), ), ), - ], - ); + ), + ], + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 9b45c248..82ebc6a9 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -126,6 +126,8 @@ class ReactionBubble extends StatelessWidget { (r) => r.type == reaction.type, ); + final chatThemeData = StreamChatTheme.of(context); + final userId = StreamChat.of(context).user?.id; return Padding( padding: const EdgeInsets.symmetric( horizontal: 4, @@ -135,24 +137,16 @@ class ReactionBubble extends StatelessWidget { assetName: reactionIcon.assetName, width: 16, height: 16, - color: (!highlightOwnReactions || - reaction.user?.id == StreamChat.of(context).user?.id) - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), + color: (!highlightOwnReactions || reaction.user?.id == userId) + ? chatThemeData.colorTheme.accentBlue + : chatThemeData.colorTheme.black.withOpacity(.5), ) : Icon( Icons.help_outline_rounded, size: 16, - color: (!highlightOwnReactions || - reaction.user?.id == StreamChat.of(context).user?.id) - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), + color: (!highlightOwnReactions || reaction.user?.id == userId) + ? chatThemeData.colorTheme.accentBlue + : chatThemeData.colorTheme.black.withOpacity(.5), ), ); } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index 1308876d..8a89b8c3 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:ezanimation/ezanimation.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) @@ -33,7 +33,8 @@ class _ReactionPickerState extends State @override Widget build(BuildContext context) { - final reactionIcons = StreamChatTheme.of(context).reactionIcons; + final chatThemeData = StreamChatTheme.of(context); + final reactionIcons = chatThemeData.reactionIcons; if (animations.isEmpty && reactionIcons.isNotEmpty) { reactionIcons.forEach((element) { @@ -57,7 +58,7 @@ class _ReactionPickerState extends State scale: val, child: Material( borderRadius: BorderRadius.circular(24), - color: StreamChatTheme.of(context).colorTheme.white, + color: chatThemeData.colorTheme.white, clipBehavior: Clip.hardEdge, child: Padding( padding: const EdgeInsets.symmetric( @@ -119,9 +120,8 @@ class _ReactionPickerState extends State animations[index].value * 24.0, ), color: ownReactionIndex != -1 - ? StreamChatTheme.of(context) - .colorTheme - .accentBlue + ? chatThemeData + .colorTheme.accentBlue : Theme.of(context) .iconTheme .color! diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index ba94642f..347fc59d 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -97,65 +97,60 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { final List? actions; @override - Widget build(BuildContext context) => AppBar( - automaticallyImplyLeading: false, - brightness: Theme.of(context).brightness, - elevation: 1, - leading: leading ?? - (showBackButton - ? StreamBackButton( - cid: StreamChannel.of(context).channel.cid, - onPressed: onBackPressed, - showUnreads: true, - ) - : const SizedBox()), - backgroundColor: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - centerTitle: true, - actions: actions, - title: InkWell( - onTap: onTitleTap, - child: SizedBox( - height: preferredSize.height, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - title ?? - Text( - 'Thread Reply', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .title, - ), - const SizedBox(height: 2), - subtitle ?? - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'with ', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return AppBar( + automaticallyImplyLeading: false, + brightness: Theme.of(context).brightness, + elevation: 1, + leading: leading ?? + (showBackButton + ? StreamBackButton( + cid: StreamChannel.of(context).channel.cid, + onPressed: onBackPressed, + showUnreads: true, + ) + : const SizedBox()), + backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color, + centerTitle: true, + actions: actions, + title: InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + title ?? + Text( + 'Thread Reply', + style: chatThemeData.channelTheme.channelHeaderTheme.title, + ), + const SizedBox(height: 2), + subtitle ?? + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'with ', + style: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, + ), + Flexible( + child: ChannelName( + textStyle: chatThemeData + .channelTheme.channelHeaderTheme.subtitle, ), - Flexible( - child: ChannelName( - textStyle: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .subtitle, - ), - ), - ], - ), - ], - ), + ), + ], + ), + ], ), ), - ); + ), + ); + } @override final Size preferredSize; diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index 0b91c2ee..995475a8 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -26,91 +26,84 @@ class UrlAttachment extends StatelessWidget { final EdgeInsets textPadding; @override - Widget build(BuildContext context) => GestureDetector( - onTap: () { - launchURL( - context, - urlAttachment.ogScrapeUrl, - ); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (urlAttachment.imageUrl != null) - Container( - clipBehavior: Clip.antiAliasWithSaveLayer, - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), - child: Stack( - children: [ - CachedNetworkImage( - width: double.infinity, - imageUrl: urlAttachment.imageUrl!, - fit: BoxFit.cover, - ), - Positioned( - left: 0, - bottom: -1, - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(16), - ), - color: - StreamChatTheme.of(context).colorTheme.blueAlice, + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return GestureDetector( + onTap: () { + launchURL( + context, + urlAttachment.ogScrapeUrl, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (urlAttachment.imageUrl != null) + Container( + clipBehavior: Clip.antiAliasWithSaveLayer, + margin: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: Stack( + children: [ + CachedNetworkImage( + width: double.infinity, + imageUrl: urlAttachment.imageUrl!, + fit: BoxFit.cover, + ), + Positioned( + left: 0, + bottom: -1, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(16), ), - child: Padding( - padding: const EdgeInsets.only( - top: 8, - left: 8, - right: 8, - ), - child: Text( - hostDisplayName, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), + color: chatThemeData.colorTheme.blueAlice, + ), + child: Padding( + padding: const EdgeInsets.only( + top: 8, + left: 8, + right: 8, + ), + child: Text( + hostDisplayName, + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentBlue, ), ), ), ), - ], - ), - ), - Padding( - padding: textPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (urlAttachment.title != null) - Text( - urlAttachment.title!.trim(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(fontWeight: FontWeight.w700), - ), - if (urlAttachment.text != null) - Text( - urlAttachment.text!, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(fontWeight: FontWeight.w400), - ), + ), ], ), ), - ], - ), - ); + Padding( + padding: textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (urlAttachment.title != null) + Text( + urlAttachment.title!.trim(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.body + .copyWith(fontWeight: FontWeight.w700), + ), + if (urlAttachment.text != null) + Text( + urlAttachment.text!, + style: chatThemeData.textTheme.body + .copyWith(fontWeight: FontWeight.w400), + ), + ], + ), + ), + ], + ), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index cb968527..ecf1783f 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -1,7 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget that displays a user avatar class UserAvatar extends StatelessWidget { @@ -95,8 +95,7 @@ class UserAvatar extends StatelessWidget { child: Container( constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme?.constraints, - color: selectionColor ?? - StreamChatTheme.of(context).colorTheme.accentBlue, + color: selectionColor ?? streamChatTheme.colorTheme.accentBlue, child: Padding( padding: EdgeInsets.all(selectionThickness), child: avatar, diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index 290612e1..e9d9f7c0 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -48,47 +48,52 @@ class UserItem extends StatelessWidget { final bool showLastOnline; @override - Widget build(BuildContext context) => ListTile( - onTap: () { - if (onTap != null) { - onTap!(user); + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return ListTile( + onTap: () { + if (onTap != null) { + onTap!(user); + } + }, + onLongPress: () { + if (onLongPress != null) { + onLongPress!(user); + } + }, + leading: UserAvatar( + user: user, + onTap: (user) { + if (onImageTap != null) { + onImageTap!(user); } }, - onLongPress: () { - if (onLongPress != null) { - onLongPress!(user); - } - }, - leading: UserAvatar( - user: user, - onTap: (user) { - if (onImageTap != null) { - onImageTap!(user); - } - }, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), + constraints: const BoxConstraints.tightFor( + height: 40, + width: 40, ), - trailing: selected - ? StreamSvgIcon.checkSend( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ) - : null, - title: Text( - user.name, - style: StreamChatTheme.of(context).textTheme.bodyBold, - ), - subtitle: showLastOnline ? _buildLastActive(context) : null, - ); + ), + trailing: selected + ? StreamSvgIcon.checkSend( + color: chatThemeData.colorTheme.accentBlue, + ) + : null, + title: Text( + user.name, + style: chatThemeData.textTheme.bodyBold, + ), + subtitle: showLastOnline ? _buildLastActive(context) : null, + ); + } - Widget _buildLastActive(context) => Text( - user.online == true - ? 'Online' - : 'Last online ${Jiffy(user.lastActive).fromNow()}', - style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)), - ); + Widget _buildLastActive(context) { + final chatTheme = StreamChatTheme.of(context); + return Text( + user.online == true + ? 'Online' + : 'Last online ${Jiffy(user.lastActive).fromNow()}', + style: chatTheme.textTheme.footnote + .copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)), + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/user_list_view.dart b/packages/stream_chat_flutter/lib/src/user_list_view.dart index 4d5d202a..3b1efd04 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -295,21 +295,24 @@ class _UserListViewState extends State if (i < items.length) { final item = items[i]; return item.when( - headerItem: (header) => Container( - key: ValueKey('HEADER-$header'), - color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - child: Text( - header, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14.5, - color: StreamChatTheme.of(context).colorTheme.grey, + headerItem: (header) { + final chatThemeData = StreamChatTheme.of(context); + return Container( + key: ValueKey('HEADER-$header'), + color: chatThemeData.colorTheme.black.withOpacity(0.05), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: Text( + header, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14.5, + color: chatThemeData.colorTheme.grey, + ), ), ), - ), - ), + ); + }, userItem: (user) { final selected = widget.selectedUsers?.contains(user) ?? false; return Container( diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index cdd87e2d..ede3e2bd 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -1,9 +1,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Launch URL Future launchURL(BuildContext context, String? url) async { @@ -27,89 +27,81 @@ Future showConfirmationDialog( Widget? icon, String? question, String? cancelText, -}) => - showModalBottomSheet( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), - builder: (context) { - final effect = StreamChatTheme.of(context).colorTheme.borderTop; - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 26), - if (icon != null) icon, - const SizedBox(height: 26), +}) { + final chatThemeData = StreamChatTheme.of(context); + return showModalBottomSheet( + backgroundColor: chatThemeData.colorTheme.white, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + builder: (context) { + final effect = chatThemeData.colorTheme.borderTop; + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 26), + if (icon != null) icon, + const SizedBox(height: 26), + Text( + title, + style: chatThemeData.textTheme.headlineBold, + ), + const SizedBox(height: 7), + if (question != null) Text( - title, - style: StreamChatTheme.of(context).textTheme.headlineBold, + question, + textAlign: TextAlign.center, ), - const SizedBox(height: 7), - if (question != null) - Text( - question, - textAlign: TextAlign.center, - ), - const SizedBox(height: 36), - Container( - color: effect.color!.withOpacity(effect.alpha ?? 1), - height: 1, - ), - Row( - children: [ - if (cancelText != null) - Flexible( - child: Container( - alignment: Alignment.center, - child: TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text( - cancelText, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5)), - ), - ), - ), - ), + const SizedBox(height: 36), + Container( + color: effect.color!.withOpacity(effect.alpha ?? 1), + height: 1, + ), + Row( + children: [ + if (cancelText != null) Flexible( child: Container( alignment: Alignment.center, child: TextButton( onPressed: () { - Navigator.pop(context, true); + Navigator.of(context).pop(false); }, child: Text( - okText, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed), + cancelText, + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.black + .withOpacity(0.5)), ), ), ), ), - ], - ), - ], - ), - ); - }); + Flexible( + child: Container( + alignment: Alignment.center, + child: TextButton( + onPressed: () { + Navigator.pop(context, true); + }, + child: Text( + okText, + style: chatThemeData.textTheme.bodyBold.copyWith( + color: chatThemeData.colorTheme.accentRed), + ), + ), + ), + ), + ], + ), + ], + ), + ); + }); +} /// Shows info dialog Future showInfoDialog( @@ -119,63 +111,64 @@ Future showInfoDialog( Widget? icon, String? details, StreamChatThemeData? theme, -}) => - showModalBottomSheet( - backgroundColor: theme?.colorTheme.white ?? - StreamChatTheme.of(context).colorTheme.white, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox( - height: 26, - ), - if (icon != null) icon, - const SizedBox( - height: 26, - ), - Text( - title, - style: theme?.textTheme.headlineBold ?? - StreamChatTheme.of(context).textTheme.headlineBold, - ), - const SizedBox( - height: 7, - ), - if (details != null) Text(details), - const SizedBox( - height: 36, - ), - Container( - color: theme?.colorTheme.black.withOpacity(.08) ?? - StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), - height: 1, - ), - Center( - child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - okText, - style: TextStyle( - color: theme?.colorTheme.black.withOpacity(0.5) ?? - StreamChatTheme.of(context).colorTheme.accentBlue, - fontWeight: FontWeight.w400, - ), +}) { + final chatThemeData = StreamChatTheme.of(context); + return showModalBottomSheet( + backgroundColor: theme?.colorTheme.white ?? chatThemeData.colorTheme.white, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 26, + ), + if (icon != null) icon, + const SizedBox( + height: 26, + ), + Text( + title, + style: theme?.textTheme.headlineBold ?? + chatThemeData.textTheme.headlineBold, + ), + const SizedBox( + height: 7, + ), + if (details != null) Text(details), + const SizedBox( + height: 36, + ), + Container( + color: theme?.colorTheme.black.withOpacity(.08) ?? + chatThemeData.colorTheme.black.withOpacity(.08), + height: 1, + ), + Center( + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + okText, + style: TextStyle( + color: theme?.colorTheme.black.withOpacity(0.5) ?? + chatThemeData.colorTheme.accentBlue, + fontWeight: FontWeight.w400, ), ), ), - ], - ), + ), + ], ), - ); + ), + ); +} /// Get random png with initials String getRandomPicUrl(User user) => From 4c11dca79240cabcca83e2c4815bb5e3485783ba Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 11 May 2021 10:11:38 +0530 Subject: [PATCH 07/16] Added a new message list controller --- packages/stream_chat/example/lib/main.dart | 8 ++++---- packages/stream_chat/lib/src/api/channel.dart | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart index 11e9779f..bbcc86b8 100644 --- a/packages/stream_chat/example/lib/main.dart +++ b/packages/stream_chat/example/lib/main.dart @@ -79,21 +79,21 @@ class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final messages = channel.state!.channelStateStream; + final messages = channel.state!.messagesStream; return Scaffold( appBar: AppBar( title: Text('Channel: ${channel.id}'), ), body: SafeArea( - child: StreamBuilder( + child: StreamBuilder?>( stream: messages, builder: ( BuildContext context, - AsyncSnapshot snapshot, + AsyncSnapshot?> snapshot, ) { if (snapshot.hasData && snapshot.data != null) { return MessageView( - messages: snapshot.data!.messages.reversed.toList(), + messages: snapshot.data!.reversed.toList(), channel: channel, ); } else if (snapshot.hasError) { diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index a1eab825..8b744cec 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'package:collection/collection.dart' show IterableExtension; +import 'package:collection/collection.dart' show IterableExtension, ListEquality; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; @@ -1296,6 +1296,8 @@ class ChannelClientState { _channelStateController = BehaviorSubject.seeded(channelState); + _messageListController = BehaviorSubject.seeded(channelState.messages); + _listenTypingEvents(); _listenMessageNew(); @@ -1601,8 +1603,7 @@ class ChannelClientState { List get messages => _channelState.messages; /// Channel message list as a stream - Stream?> get messagesStream => - channelStateStream.map((cs) => cs.messages); + Stream?> get messagesStream => _messageListController.stream; /// Channel pinned message list List? get pinnedMessages => _channelState.pinnedMessages.toList(); @@ -1772,10 +1773,14 @@ class ChannelClientState { /// The channel state related to this client ChannelState get channelState => _channelStateController.value!; late BehaviorSubject _channelStateController; + late BehaviorSubject> _messageListController; final Debounce _debouncedUpdatePersistenceChannelState; set _channelState(ChannelState v) { + if(!const ListEquality().equals(_messageListController.value, v.messages)) { + _messageListController.add(v.messages); + } _channelStateController.add(v); _debouncedUpdatePersistenceChannelState.call([v]); } @@ -1933,6 +1938,7 @@ class ChannelClientState { retryQueue!.dispose(); _subscriptions.forEach((s) => s.cancel()); _channelStateController.close(); + _messageListController.close(); _isUpToDateController.close(); _threadsController.close(); _cleaningTimer.cancel(); From 16ecd0568e2dc96ed899fed7ee6b129345d5cd28 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 11 May 2021 10:19:09 +0530 Subject: [PATCH 08/16] fix: dartfmt --- packages/stream_chat/lib/src/api/channel.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 8b744cec..e51725e3 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -2,7 +2,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'package:collection/collection.dart' show IterableExtension, ListEquality; +import 'package:collection/collection.dart' + show IterableExtension, ListEquality; import 'package:dio/dio.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; @@ -1778,7 +1779,8 @@ class ChannelClientState { final Debounce _debouncedUpdatePersistenceChannelState; set _channelState(ChannelState v) { - if(!const ListEquality().equals(_messageListController.value, v.messages)) { + if (!const ListEquality() + .equals(_messageListController.value, v.messages)) { _messageListController.add(v.messages); } _channelStateController.add(v); From 682fe3f4476d673c514b460ca48d0d3a66e9c3c3 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 11 May 2021 14:55:25 +0530 Subject: [PATCH 09/16] Changed to distinct implementation --- packages/stream_chat/lib/src/api/channel.dart | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index e51725e3..ca3c638a 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -1297,8 +1297,6 @@ class ChannelClientState { _channelStateController = BehaviorSubject.seeded(channelState); - _messageListController = BehaviorSubject.seeded(channelState.messages); - _listenTypingEvents(); _listenMessageNew(); @@ -1604,7 +1602,9 @@ class ChannelClientState { List get messages => _channelState.messages; /// Channel message list as a stream - Stream?> get messagesStream => _messageListController.stream; + Stream?> get messagesStream => channelStateStream + .map((cs) => cs.messages) + .distinct((prev, next) => const ListEquality().equals(prev, next)); /// Channel pinned message list List? get pinnedMessages => _channelState.pinnedMessages.toList(); @@ -1774,15 +1774,10 @@ class ChannelClientState { /// The channel state related to this client ChannelState get channelState => _channelStateController.value!; late BehaviorSubject _channelStateController; - late BehaviorSubject> _messageListController; final Debounce _debouncedUpdatePersistenceChannelState; set _channelState(ChannelState v) { - if (!const ListEquality() - .equals(_messageListController.value, v.messages)) { - _messageListController.add(v.messages); - } _channelStateController.add(v); _debouncedUpdatePersistenceChannelState.call([v]); } @@ -1940,7 +1935,6 @@ class ChannelClientState { retryQueue!.dispose(); _subscriptions.forEach((s) => s.cancel()); _channelStateController.close(); - _messageListController.close(); _isUpToDateController.close(); _threadsController.close(); _cleaningTimer.cancel(); From 5c044921766e8a60f86d00f12556e46afdfd0560 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 11 May 2021 15:21:30 +0200 Subject: [PATCH 10/16] perform heavy calls only first time --- .../lib/src/message_list_view.dart | 40 +++---- .../lib/src/channel_list_core.dart | 20 ++-- .../lib/src/channels_bloc.dart | 101 +++++++++--------- .../lib/src/message_list_core.dart | 5 +- .../lib/src/message_search_list_core.dart | 9 +- .../lib/src/user_list_core.dart | 7 +- 6 files changed, 99 insertions(+), 83 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 661ce885..3a07c117 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1087,8 +1087,6 @@ class _MessageListViewState extends State { _itemPositionListener = widget.itemPositionListener ?? ItemPositionsListener.create(); - streamChannel = StreamChannel.of(context); - initialIndex = _initialIndex; initialAlignment = _initialAlignment; @@ -1100,25 +1098,27 @@ class _MessageListViewState extends State { void didChangeDependencies() { streamChannel = StreamChannel.of(context); - _messageNewListener?.cancel(); - _messageNewListener = - streamChannel.channel.on(EventType.messageNew).listen((event) { - if (_upToDate) { - _bottomPaginationActive = false; - _topPaginationActive = false; - } - if (event.message!.user!.id == - streamChannel.channel.client.state.user!.id) { - WidgetsBinding.instance!.addPostFrameCallback((_) { - _scrollController?.jumpTo( - index: 0, - ); - }); - } - }); + if (_messageNewListener == null) { + _messageNewListener?.cancel(); + _messageNewListener = + streamChannel.channel.on(EventType.messageNew).listen((event) { + if (_upToDate) { + _bottomPaginationActive = false; + _topPaginationActive = false; + } + if (event.message!.user!.id == + streamChannel.channel.client.state.user!.id) { + WidgetsBinding.instance!.addPostFrameCallback((_) { + _scrollController?.jumpTo( + index: 0, + ); + }); + } + }); - if (_isThreadConversation) { - streamChannel.getReplies(widget.parentMessage!.id); + if (_isThreadConversation) { + streamChannel.getReplies(widget.parentMessage!.id); + } } super.didChangeDependencies(); } diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 0460cf7e..084ee20f 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -178,19 +178,17 @@ class ChannelListCoreState extends State { if (_subscription == null) { loadData(); + final client = _streamChatCoreState.client; + _subscription = client + .on( + EventType.connectionRecovered, + EventType.notificationAddedToChannel, + EventType.notificationMessageNew, + EventType.channelVisible, + ) + .listen((event) => loadData()); } - final client = _streamChatCoreState.client; - _subscription?.cancel(); - _subscription = client - .on( - EventType.connectionRecovered, - EventType.notificationAddedToChannel, - EventType.notificationMessageNew, - EventType.channelVisible, - ) - .listen((event) => loadData()); - super.didChangeDependencies(); } diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart index f29c76f9..b23b9fc4 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -149,59 +149,62 @@ class ChannelsBlocState extends State _streamChatCoreState = StreamChatCore.of(context); final client = _streamChatCoreState.client; - _cancelSubscriptions(); - if (!widget.lockChannelsOrder) { - _subscriptions.add(client - .on( - EventType.messageNew, - ) - .listen((e) { - final newChannels = List.from(channels ?? []); - final index = newChannels.indexWhere((c) => c.cid == e.cid); - if (index != -1) { - if (index > 0) { - final channel = newChannels.removeAt(index); - newChannels.insert(0, channel); - } - } else if (widget.shouldAddChannel?.call(e) == true) { - final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid); - if (hiddenIndex != -1) { - newChannels.insert(0, _hiddenChannels[hiddenIndex]); - _hiddenChannels.removeAt(hiddenIndex); - } else { - if (client.state.channels[e.cid] != null) { - newChannels.insert(0, client.state.channels[e.cid]!); + if (_subscriptions.isEmpty) { + if (!widget.lockChannelsOrder) { + _subscriptions.add(client + .on( + EventType.messageNew, + ) + .listen((e) { + final newChannels = List.from(channels ?? []); + final index = newChannels.indexWhere((c) => c.cid == e.cid); + if (index != -1) { + if (index > 0) { + final channel = newChannels.removeAt(index); + newChannels.insert(0, channel); + } + } else if (widget.shouldAddChannel?.call(e) == true) { + final hiddenIndex = + _hiddenChannels.indexWhere((c) => c.cid == e.cid); + if (hiddenIndex != -1) { + newChannels.insert(0, _hiddenChannels[hiddenIndex]); + _hiddenChannels.removeAt(hiddenIndex); + } else { + if (client.state.channels[e.cid] != null) { + newChannels.insert(0, client.state.channels[e.cid]!); + } } } - } - if (widget.channelsComparator != null) { - newChannels.sort(widget.channelsComparator); - } - _channelsController.add(newChannels); - })); - } - - _subscriptions - ..add(client.on(EventType.channelHidden).listen((event) async { - final newChannels = List.from(channels ?? []); - final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid); - if (channelIndex > -1) { - final channel = newChannels.removeAt(channelIndex); - _hiddenChannels.add(channel); + if (widget.channelsComparator != null) { + newChannels.sort(widget.channelsComparator); + } _channelsController.add(newChannels); - } - })) - ..add(client - .on( - EventType.channelDeleted, - EventType.notificationRemovedFromChannel, - ) - .listen((e) { - final channel = e.channel; - _channelsController.add(List.from( - (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); - })); + })); + } + + _subscriptions + ..add(client.on(EventType.channelHidden).listen((event) async { + final newChannels = List.from(channels ?? []); + final channelIndex = + newChannels.indexWhere((c) => c.cid == event.cid); + if (channelIndex > -1) { + final channel = newChannels.removeAt(channelIndex); + _hiddenChannels.add(channel); + _channelsController.add(newChannels); + } + })) + ..add(client + .on( + EventType.channelDeleted, + EventType.notificationRemovedFromChannel, + ) + .listen((e) { + final channel = e.channel; + _channelsController.add(List.from( + (channels ?? [])..removeWhere((c) => c.cid == channel?.cid))); + })); + } super.didChangeDependencies(); } diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 3e0ac1c7..babbfd36 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -173,10 +173,13 @@ class MessageListCoreState extends State { } } + var _initialized = false; + @override void didChangeDependencies() { _streamChannel = StreamChannel.of(context); - if (_isThreadConversation) { + if (!_initialized && _isThreadConversation) { + _initialized = true; _streamChannel.getReplies(widget.parentMessage!.id); } super.didChangeDependencies(); diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index e2f0ae2e..e6cb1aad 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -107,10 +107,17 @@ class MessageSearchListCore extends StatefulWidget { class MessageSearchListCoreState extends State { late MessageSearchBlocState _messageSearchBloc; + var _initialized = false; + @override void didChangeDependencies() { _messageSearchBloc = MessageSearchBloc.of(context); - loadData(); + + if (!_initialized) { + loadData(); + _initialized = true; + } + if (widget.messageSearchListController != null) { widget.messageSearchListController!.loadData = loadData; widget.messageSearchListController!.paginateData = paginateData; diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index d608ddc4..da675706 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -122,9 +122,14 @@ class UserListCore extends StatefulWidget { /// The current state of the [UserListCore]. class UserListCoreState extends State with WidgetsBindingObserver { + var _initialized = false; + @override void didChangeDependencies() { - loadData(); + if (!_initialized) { + loadData(); + _initialized = true; + } if (widget.userListController != null) { widget.userListController!.loadData = loadData; widget.userListController!.paginateData = paginateData; From 19ce7ff756485cf6c568f543c20dad134daba104 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 11 May 2021 15:29:02 +0200 Subject: [PATCH 11/16] fix message list --- .../stream_chat_flutter/lib/src/message_list_view.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 3a07c117..7435a11b 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1087,9 +1087,6 @@ class _MessageListViewState extends State { _itemPositionListener = widget.itemPositionListener ?? ItemPositionsListener.create(); - initialIndex = _initialIndex; - initialAlignment = _initialAlignment; - _getOnThreadTap(); super.initState(); } @@ -1099,7 +1096,9 @@ class _MessageListViewState extends State { streamChannel = StreamChannel.of(context); if (_messageNewListener == null) { - _messageNewListener?.cancel(); + initialIndex = _initialIndex; + initialAlignment = _initialAlignment; + _messageNewListener = streamChannel.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { From a7f00b3d85eb7b222b614653a3e38168354cd9b0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 11:50:06 +0200 Subject: [PATCH 12/16] update didchangedependencies --- .../lib/src/message_list_view.dart | 53 ++++++++-------- .../lib/src/channel_list_core.dart | 10 +-- .../lib/src/channels_bloc.dart | 13 ++-- .../lib/src/message_list_core.dart | 30 ++++----- .../lib/src/message_search_list_core.dart | 22 +++---- .../lib/src/user_list_core.dart | 62 +++++++------------ 6 files changed, 88 insertions(+), 102 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 7435a11b..e8f8cdbc 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -274,15 +274,15 @@ class _MessageListViewState extends State { bool _showScrollToBottom = false; late final ItemPositionsListener _itemPositionListener; int? _messageListLength; - late StreamChannelState streamChannel; + StreamChannelState? streamChannel; int? get _initialIndex { if (widget.initialScrollIndex != null) return widget.initialScrollIndex; - if (streamChannel.initialMessageId != null) { - final messages = streamChannel.channel.state!.messages; + if (streamChannel!.initialMessageId != null) { + final messages = streamChannel!.channel.state!.messages; final totalMessages = messages.length; final messageIndex = - messages.indexWhere((e) => e.id == streamChannel.initialMessageId); + messages.indexWhere((e) => e.id == streamChannel!.initialMessageId); final index = totalMessages - messageIndex; if (index != 0) return index - 1; return index; @@ -295,9 +295,9 @@ class _MessageListViewState extends State { return 0; } - bool _isInitialMessage(String id) => streamChannel.initialMessageId == id; + bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id; - bool get _upToDate => streamChannel.channel.state!.isUpToDate; + bool get _upToDate => streamChannel!.channel.state!.isUpToDate; bool get _isThreadConversation => widget.parentMessage != null; @@ -506,13 +506,13 @@ class _MessageListViewState extends State { } if (i == messages.length + 1) { return _buildLoadingIndicator( - streamChannel, + streamChannel!, QueryDirection.top, ); } if (i == 0) { return _buildLoadingIndicator( - streamChannel, + streamChannel!, QueryDirection.bottom, ); } @@ -525,7 +525,7 @@ class _MessageListViewState extends State { context, message, messages, - streamChannel, + streamChannel!, ); } else if (i == messages.length - 1) { messageWidget = _buildTopMessage( @@ -605,8 +605,8 @@ class _MessageListViewState extends State { Widget _buildScrollToBottom() => StreamBuilder>( stream: Rx.combineLatest2( - streamChannel.channel.state!.isUpToDateStream, - streamChannel.channel.state!.unreadCountStream, + streamChannel!.channel.state!.isUpToDateStream, + streamChannel!.channel.state!.unreadCountStream, (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ), builder: (_, snapshot) { @@ -622,8 +622,8 @@ class _MessageListViewState extends State { } final unreadCount = snapshot.data!.item2; final showUnreadCount = unreadCount > 0 && - streamChannel.channel.state!.members.any((e) => - e.userId == streamChannel.channel.client.state.user!.id); + streamChannel!.channel.state!.members.any((e) => + e.userId == streamChannel!.channel.client.state.user!.id); final chatThemeData = StreamChatTheme.of(context); return Positioned( bottom: 8, @@ -637,12 +637,12 @@ class _MessageListViewState extends State { backgroundColor: chatThemeData.colorTheme.white, onPressed: () { if (unreadCount > 0) { - streamChannel.channel.markRead(); + streamChannel!.channel.markRead(); } if (!_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; - streamChannel.reloadChannel(); + streamChannel!.reloadChannel(); } else { setState(() => _showScrollToBottom = false); _scrollController!.scrollTo( @@ -888,7 +888,7 @@ class _MessageListViewState extends State { ); } - final channel = streamChannel.channel; + final channel = streamChannel!.channel; final readList = channel.state?.read?.where((read) { if (read.user.id == userId) return false; return read.lastRead.isAfter(message.createdAt) || @@ -962,7 +962,7 @@ class _MessageListViewState extends State { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); } else { - await streamChannel.loadChannelAtMessage(quotedMessageId).then((_) { + await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) { WidgetsBinding.instance!.addPostFrameCallback((_) { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); @@ -1093,20 +1093,22 @@ class _MessageListViewState extends State { @override void didChangeDependencies() { - streamChannel = StreamChannel.of(context); + final newStreamChannel = StreamChannel.of(context); - if (_messageNewListener == null) { + if (newStreamChannel != streamChannel) { + streamChannel = newStreamChannel; + _messageNewListener?.cancel(); initialIndex = _initialIndex; initialAlignment = _initialAlignment; _messageNewListener = - streamChannel.channel.on(EventType.messageNew).listen((event) { + streamChannel!.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; } if (event.message!.user!.id == - streamChannel.channel.client.state.user!.id) { + streamChannel!.channel.client.state.user!.id) { WidgetsBinding.instance!.addPostFrameCallback((_) { _scrollController?.jumpTo( index: 0, @@ -1116,9 +1118,10 @@ class _MessageListViewState extends State { }); if (_isThreadConversation) { - streamChannel.getReplies(widget.parentMessage!.id); + streamChannel!.getReplies(widget.parentMessage!.id); } } + super.didChangeDependencies(); } @@ -1137,12 +1140,12 @@ class _MessageListViewState extends State { context, MaterialPageRoute( builder: (_) => StreamBuilder( - stream: streamChannel.channel.state!.messagesStream.map( + stream: streamChannel!.channel.state!.messagesStream.map( (messages) => messages!.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) => StreamChannel( - channel: streamChannel.channel, + channel: streamChannel!.channel, child: widget.threadBuilder!(context, snapshot.data), ), ), @@ -1155,7 +1158,7 @@ class _MessageListViewState extends State { @override void dispose() { if (!_upToDate) { - streamChannel.reloadChannel(); + streamChannel!.reloadChannel(); } _messageNewListener?.cancel(); super.dispose(); diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 084ee20f..a3be273d 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -120,7 +120,7 @@ class ChannelListCore extends StatefulWidget { /// The current state of the [ChannelListCore]. class ChannelListCoreState extends State { late ChannelsBlocState _channelsBloc; - late StreamChatCoreState _streamChatCoreState; + StreamChatCoreState? _streamChatCoreState; @override Widget build(BuildContext context) => _buildListView(_channelsBloc); @@ -174,11 +174,13 @@ class ChannelListCoreState extends State { @override void didChangeDependencies() { _channelsBloc = ChannelsBloc.of(context); - _streamChatCoreState = StreamChatCore.of(context); + final newStreamChatCoreState = StreamChatCore.of(context); - if (_subscription == null) { + if (newStreamChatCoreState != _streamChatCoreState) { + _streamChatCoreState = newStreamChatCoreState; loadData(); - final client = _streamChatCoreState.client; + final client = _streamChatCoreState!.client; + _subscription?.cancel(); _subscription = client .on( EventType.connectionRecovered, diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart index b23b9fc4..d46ee4bd 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -62,7 +62,7 @@ class ChannelsBloc extends StatefulWidget { /// The current state of the [ChannelsBloc]. class ChannelsBlocState extends State with AutomaticKeepAliveClientMixin { - late StreamChatCoreState _streamChatCoreState; + StreamChatCoreState? _streamChatCoreState; @override Widget build(BuildContext context) { @@ -98,7 +98,7 @@ class ChannelsBlocState extends State PaginationParams paginationParams = const PaginationParams(limit: 30), Map? options, }) async { - final client = _streamChatCoreState.client; + final client = _streamChatCoreState!.client; final clear = paginationParams.offset == 0; @@ -146,10 +146,13 @@ class ChannelsBlocState extends State @override void didChangeDependencies() { - _streamChatCoreState = StreamChatCore.of(context); - final client = _streamChatCoreState.client; + final newStreamChatCoreState = StreamChatCore.of(context); - if (_subscriptions.isEmpty) { + if (newStreamChatCoreState != _streamChatCoreState) { + _streamChatCoreState = newStreamChatCoreState; + final client = _streamChatCoreState!.client; + + _cancelSubscriptions(); if (!widget.lockChannelsOrder) { _subscriptions.add(client .on( diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index babbfd36..732ac3e6 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -109,23 +109,23 @@ class MessageListCore extends StatefulWidget { /// The current state of the [MessageListCore]. class MessageListCoreState extends State { - late StreamChannelState _streamChannel; + StreamChannelState? _streamChannel; - bool get _upToDate => _streamChannel.channel.state?.isUpToDate ?? true; + bool get _upToDate => _streamChannel!.channel.state?.isUpToDate ?? true; bool get _isThreadConversation => widget.parentMessage != null; - OwnUser? get _currentUser => _streamChannel.channel.client.state.user; + OwnUser? get _currentUser => _streamChannel!.channel.client.state.user; var _messages = []; @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; @@ -167,21 +167,23 @@ class MessageListCoreState extends State { QueryDirection direction = QueryDirection.top, }) { 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); } } - var _initialized = false; - @override void didChangeDependencies() { - _streamChannel = StreamChannel.of(context); - if (!_initialized && _isThreadConversation) { - _initialized = true; - _streamChannel.getReplies(widget.parentMessage!.id); + final newStreamChannel = StreamChannel.of(context); + + if (newStreamChannel != _streamChannel) { + _streamChannel = newStreamChannel; + if (_isThreadConversation) { + _streamChannel!.getReplies(widget.parentMessage!.id); + } } + super.didChangeDependencies(); } @@ -210,7 +212,7 @@ class MessageListCoreState extends State { @override void dispose() { if (!_upToDate) { - _streamChannel.reloadChannel(); + _streamChannel!.reloadChannel(); } super.dispose(); } diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index e6cb1aad..199852b9 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -105,28 +105,22 @@ class MessageSearchListCore extends StatefulWidget { /// The current state of the [MessageSearchListCore]. class MessageSearchListCoreState extends State { - late MessageSearchBlocState _messageSearchBloc; - - var _initialized = false; + MessageSearchBlocState? _messageSearchBloc; @override void didChangeDependencies() { - _messageSearchBloc = MessageSearchBloc.of(context); + final newMessageSearchBloc = MessageSearchBloc.of(context); - if (!_initialized) { + if (newMessageSearchBloc != _messageSearchBloc) { + _messageSearchBloc = newMessageSearchBloc; loadData(); - _initialized = true; } - if (widget.messageSearchListController != null) { - widget.messageSearchListController!.loadData = loadData; - widget.messageSearchListController!.paginateData = paginateData; - } super.didChangeDependencies(); } @override - Widget build(BuildContext context) => _buildListView(_messageSearchBloc); + Widget build(BuildContext context) => _buildListView(_messageSearchBloc!); Widget _buildListView(MessageSearchBlocState messageSearchBloc) => StreamBuilder>( @@ -147,7 +141,7 @@ class MessageSearchListCoreState extends State { ); /// Fetches initial messages and updates the widget - Future loadData() => _messageSearchBloc.search( + Future loadData() => _messageSearchBloc!.search( filter: widget.filters, sort: widget.sortOptions, query: widget.messageQuery, @@ -156,11 +150,11 @@ class MessageSearchListCoreState extends State { ); /// Fetches more messages with updated pagination and updates the widget - Future paginateData() => _messageSearchBloc.search( + Future paginateData() => _messageSearchBloc!.search( filter: widget.filters, sort: widget.sortOptions, pagination: widget.paginationParams!.copyWith( - offset: _messageSearchBloc.messageResponses?.length ?? 0, + offset: _messageSearchBloc!.messageResponses?.length ?? 0, ), query: widget.messageQuery, messageFilter: widget.messageFilters, diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index da675706..3e4839b0 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -122,34 +122,25 @@ class UserListCore extends StatefulWidget { /// The current state of the [UserListCore]. class UserListCoreState extends State with WidgetsBindingObserver { - var _initialized = false; + UsersBlocState? _usersBloc; @override void didChangeDependencies() { - if (!_initialized) { + final newUsersBloc = UsersBloc.of(context); + if (newUsersBloc != _usersBloc) { + _usersBloc = newUsersBloc; loadData(); - _initialized = true; - } - if (widget.userListController != null) { - widget.userListController!.loadData = loadData; - widget.userListController!.paginateData = paginateData; } super.didChangeDependencies(); } @override - Widget build(BuildContext context) { - final _usersBloc = UsersBloc.of(context); - return _buildListView(_usersBloc); - } + Widget build(BuildContext context) => _buildListView(); bool get _isListAlreadySorted => widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false; - Stream> _buildUserStream( - UsersBlocState usersBlocState, - ) => - usersBlocState.usersStream.map( + Stream> _buildUserStream() => _usersBloc!.usersStream.map( (users) { if (widget.groupAlphabetically) { var temp = users; @@ -174,11 +165,8 @@ class UserListCoreState extends State }, ); - StreamBuilder> _buildListView( - UsersBlocState usersBlocState, - ) => - StreamBuilder( - stream: _buildUserStream(usersBlocState), + StreamBuilder> _buildListView() => StreamBuilder( + stream: _buildUserStream(), builder: (context, snapshot) { if (snapshot.hasError) { return widget.errorBuilder(snapshot.error!); @@ -195,28 +183,22 @@ class UserListCoreState extends State ); // ignore: public_member_api_docs - Future loadData() { - final _usersBloc = UsersBloc.of(context); - return _usersBloc.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination, - options: widget.options, - ); - } + Future loadData() => _usersBloc!.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination, + options: widget.options, + ); // ignore: public_member_api_docs - Future paginateData() { - final _usersBloc = UsersBloc.of(context); - return _usersBloc.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination!.copyWith( - offset: _usersBloc.users?.length ?? 0, - ), - options: widget.options, - ); - } + Future paginateData() => _usersBloc!.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination!.copyWith( + offset: _usersBloc!.users?.length ?? 0, + ), + options: widget.options, + ); @override void didUpdateWidget(UserListCore oldWidget) { From 5f010ce82d4545e204b4bde463d5ba785e03b248 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 13:52:02 +0200 Subject: [PATCH 13/16] fix tests --- .../lib/src/message_search_list_core.dart | 4 ++++ packages/stream_chat_flutter_core/lib/src/user_list_core.dart | 4 ++++ .../test/message_search_list_core_test.dart | 1 + 3 files changed, 9 insertions(+) diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 199852b9..a1eb5d68 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -114,6 +114,10 @@ class MessageSearchListCoreState extends State { if (newMessageSearchBloc != _messageSearchBloc) { _messageSearchBloc = newMessageSearchBloc; loadData(); + if (widget.messageSearchListController != null) { + widget.messageSearchListController!.loadData = loadData; + widget.messageSearchListController!.paginateData = paginateData; + } } super.didChangeDependencies(); diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 3e4839b0..d34c563c 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -130,6 +130,10 @@ class UserListCoreState extends State if (newUsersBloc != _usersBloc) { _usersBloc = newUsersBloc; loadData(); + if (widget.userListController != null) { + widget.userListController!.loadData = loadData; + widget.userListController!.paginateData = paginateData; + } } super.didChangeDependencies(); } diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart index f704c9fd..932e35b4 100644 --- a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -107,6 +107,7 @@ void main() { ), ), ); + await tester.pumpAndSettle(); expect(find.byKey(messageSearchListCoreKey), findsOneWidget); expect(controller.loadData, isNotNull); From ea19e9000bbdcc5af30b0349d09e2d358446ec26 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 15:39:21 +0200 Subject: [PATCH 14/16] move get replies logic to didupdatewidget --- .../lib/src/message_list_core.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 732ac3e6..22847f88 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -178,10 +178,10 @@ class MessageListCoreState extends State { final newStreamChannel = StreamChannel.of(context); if (newStreamChannel != _streamChannel) { - _streamChannel = newStreamChannel; - if (_isThreadConversation) { + if (_streamChannel == null /*only first time*/ && _isThreadConversation) { _streamChannel!.getReplies(widget.parentMessage!.id); } + _streamChannel = newStreamChannel; } super.didChangeDependencies(); @@ -194,6 +194,12 @@ class MessageListCoreState extends State { if (widget.messageListController != oldWidget.messageListController) { _setupController(); } + + if (widget.parentMessage?.id != widget.parentMessage?.id) { + if (_isThreadConversation) { + _streamChannel!.getReplies(widget.parentMessage!.id); + } + } } @override From 5de52d6601e30de92e02292b40ea51941ccc7139 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 16:09:46 +0200 Subject: [PATCH 15/16] fix tests --- .../stream_chat_flutter_core/lib/src/message_list_core.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 22847f88..cf11ef5f 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -179,7 +179,7 @@ class MessageListCoreState extends State { if (newStreamChannel != _streamChannel) { if (_streamChannel == null /*only first time*/ && _isThreadConversation) { - _streamChannel!.getReplies(widget.parentMessage!.id); + newStreamChannel.getReplies(widget.parentMessage!.id); } _streamChannel = newStreamChannel; } From 2b9f20a04d6b8bfa528d7b2be59e943367465fc2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 12 May 2021 16:36:44 +0200 Subject: [PATCH 16/16] add small test --- .../test/message_list_core_test.dart | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index f6ea335f..e53cd0a8 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -147,6 +147,56 @@ void main() { }, ); + testWidgets( + 'should assign paginateData callback and paginate data correctly if a MessageListController is passed', + (tester) async { + const messageListCoreKey = Key('messageListCore'); + final controller = MessageListController(); + final messageListCore = MessageListCore( + key: messageListCoreKey, + messageListBuilder: (_, __) => Offstage(), + loadingBuilder: (BuildContext context) => Offstage(), + emptyBuilder: (BuildContext context) => Offstage(), + errorWidgetBuilder: (BuildContext context, Object error) => Offstage(), + messageListController: controller, + ); + + expect(controller.paginateData, isNull); + + final mockChannel = MockChannel(); + + when(() => mockChannel.state.isUpToDate).thenReturn(true); + // when(() => mockChannel.query( + // messagesPagination: any(named: 'messagesPagination'), + // preferOffline: any(named: 'preferOffline'), + // )).thenAnswer((_) => mockChannel.state); + final messages = _generateMessages(); + when(() => mockChannel.state.messages).thenReturn(messages); + when(() => mockChannel.state.messagesStream) + .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true)); + + await tester.pumpWidget( + StreamChannel( + channel: mockChannel, + child: messageListCore, + ), + ); + + final finder = find.byKey(messageListCoreKey); + final coreState = tester.firstState(finder); + expect(finder, findsOneWidget); + expect(controller.paginateData, isNotNull); + + await coreState.paginateData(); + + verify(() => mockChannel.query( + messagesPagination: any(named: 'messagesPagination'), + preferOffline: any(named: 'preferOffline'), + )).called(1); + }, + ); + testWidgets( 'should build error widget if messagesStream emits error', (tester) async {