diff --git a/example/lib/custom_message.dart b/example/lib/custom_message.dart index b8efa294..56ffce7c 100644 --- a/example/lib/custom_message.dart +++ b/example/lib/custom_message.dart @@ -9,24 +9,36 @@ void main() async { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo', ); - final channelClient = client.channel('messaging', id: 'godevs'); - - // ignore: unawaited_futures - channelClient.watch(); - runApp( StreamChat( client: client, child: MaterialApp( - home: StreamChannel( - channelClient: channelClient, - child: ChannelPage(), - ), + home: ChannelListPage(), ), ), ); } +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + ), + ); + } +} + class ChannelPage extends StatelessWidget { const ChannelPage({ Key key, @@ -39,22 +51,7 @@ class ChannelPage extends StatelessWidget { children: [ Expanded( child: MessageListView( - messageBuilder: (context, message, _) { - final isCurrentUser = - StreamChat.of(context).user.id == message.user.id; - final textAlign = - isCurrentUser ? TextAlign.right : TextAlign.left; - return ListTile( - title: Text( - message.text, - textAlign: textAlign, - ), - subtitle: Text( - message.user.extraData['name'], - textAlign: textAlign, - ), - ); - }, + messageBuilder: _messageBuilder, ), ), MessageInput(), @@ -62,4 +59,19 @@ class ChannelPage extends StatelessWidget { ), ); } + + Widget _messageBuilder(context, message, _) { + final isCurrentUser = StreamChat.of(context).user.id == message.user.id; + final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left; + return ListTile( + title: Text( + message.text, + textAlign: textAlign, + ), + subtitle: Text( + message.user.extraData['name'], + textAlign: textAlign, + ), + ); + } } diff --git a/example/lib/customize_channel_preview.dart b/example/lib/customize_channel_preview.dart index 2092e8df..d44ae48f 100644 --- a/example/lib/customize_channel_preview.dart +++ b/example/lib/customize_channel_preview.dart @@ -27,25 +27,9 @@ class ChannelListPage extends StatelessWidget { filter: { 'members': { '\$in': [StreamChat.of(context).user.id], - } - }, - channelPreviewBuilder: (context, channelClient) { - final channelState = channelClient.state.channelState; - return ListTile( - leading: ChannelAvatar( - channel: channelState.channel, - ), - title: ChannelName( - channel: channelState.channel, - ), - trailing: channelClient.state.unreadCount > 0 - ? CircleAvatar( - radius: 10, - child: Text(channelClient.state.unreadCount.toString()), - ) - : SizedBox(), - ); + }, }, + channelPreviewBuilder: _channelPreviewBuilder, sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, @@ -54,6 +38,23 @@ class ChannelListPage extends StatelessWidget { ), ); } + + Widget _channelPreviewBuilder(BuildContext context, Channel channel) { + return ListTile( + leading: ChannelImage( + channel: channel, + ), + title: ChannelName( + channel: channel, + ), + trailing: channel.state.unreadCount > 0 + ? CircleAvatar( + radius: 10, + child: Text(channel.state.unreadCount.toString()), + ) + : SizedBox(), + ); + } } class ChannelPage extends StatelessWidget { diff --git a/example/lib/multiple_conversation.dart b/example/lib/multiple_conversation.dart index 64466191..d0cce521 100644 --- a/example/lib/multiple_conversation.dart +++ b/example/lib/multiple_conversation.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() async { - final client = Client('qk4nn7rpcn75', logLevel: Level.INFO); + final client = Client('qk4nn7rpcn75'); await client.setUser( User(id: 'wild-breeze-7'), diff --git a/example/lib/single_conversation.dart b/example/lib/single_conversation.dart index 6e60f0db..26d58d8b 100644 --- a/example/lib/single_conversation.dart +++ b/example/lib/single_conversation.dart @@ -2,24 +2,27 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() async { - final client = Client('qk4nn7rpcn75'); + final client = Client( + 'qk4nn7rpcn75', + logLevel: Level.INFO, + ); await client.setUser( User(id: 'wild-breeze-7'), 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo', ); - final channelClient = client.channel('messaging', id: 'godevs'); + final channel = client.channel('messaging', id: 'godevs'); // ignore: unawaited_futures - channelClient.watch(); + channel.watch(); runApp( StreamChat( client: client, child: MaterialApp( home: StreamChannel( - channelClient: channelClient, + channel: channel, child: ChannelPage(), ), ), diff --git a/example/lib/threads.dart b/example/lib/threads.dart index 100e5ee6..138528b9 100644 --- a/example/lib/threads.dart +++ b/example/lib/threads.dart @@ -51,9 +51,9 @@ class ChannelPage extends StatelessWidget { children: [ Expanded( child: MessageListView( - threadBuilder: (_, message) { + threadBuilder: (_, parentMessage) { return ThreadPage( - parent: message, + parent: parentMessage, ); }, ), diff --git a/lib/src/channel_image.dart b/lib/src/channel_image.dart index 834eda11..51ae8ff2 100644 --- a/lib/src/channel_image.dart +++ b/lib/src/channel_image.dart @@ -16,12 +16,12 @@ class ChannelImage extends StatelessWidget { Widget build(BuildContext context) { return CircleAvatar( radius: radius, - backgroundImage: channel.extraData.containsKey('image') + backgroundImage: channel.extraData?.containsKey('image') ?? false ? CachedNetworkImageProvider(channel.extraData['image']) : null, - child: channel.extraData.containsKey('image') + child: channel.extraData?.containsKey('image') ?? false ? null - : Text(channel.extraData.containsKey('name') + : Text(channel.extraData?.containsKey('name') ?? false ? channel.extraData['name'][0] : ''), ); diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 5be4dfb1..6ea6638d 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import '../stream_chat_flutter.dart'; import 'channel_preview.dart'; import 'stream_channel.dart'; import 'stream_chat.dart'; -typedef ChannelTapCallback = void Function(ChannelClient, Widget); -typedef ChannelPreviewBuilder = Widget Function(BuildContext, ChannelClient); +typedef ChannelTapCallback = void Function(Channel, Widget); +typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); class ChannelListView extends StatefulWidget { ChannelListView({ @@ -47,7 +48,7 @@ class _ChannelListViewState extends State { options: widget.options, ); }, - child: StreamBuilder>( + child: StreamBuilder>( stream: streamChat.channelsStream, builder: (context, snapshot) { if (!snapshot.hasData) { @@ -63,19 +64,19 @@ class _ChannelListViewState extends State { ); } - final channelsStates = snapshot.data; + final channels = snapshot.data; return ListView.custom( physics: AlwaysScrollableScrollPhysics(), controller: widget._scrollController, childrenDelegate: SliverChildBuilderDelegate( (context, i) { - return _itemBuilder(context, i, channelsStates); + return _itemBuilder(context, i, channels); }, - childCount: (channelsStates.length * 2) + 1, + childCount: (channels.length * 2) + 1, findChildIndexCallback: (key) { final ValueKey valueKey = key; - final index = channelsStates.indexWhere( - (cs) => 'CHANNEL-${cs.channel.id}' == valueKey.value); + final index = channels.indexWhere( + (channel) => 'CHANNEL-${channel.id}' == valueKey.value); return index != -1 ? (index * 2) : null; }, ), @@ -84,7 +85,7 @@ class _ChannelListViewState extends State { ); } - Widget _itemBuilder(context, int i, List channelsStates) { + Widget _itemBuilder(context, int i, List channels) { if (i % 2 != 0) { return _separatorBuilder(context, i); } @@ -92,11 +93,10 @@ class _ChannelListViewState extends State { i = i ~/ 2; final streamChat = StreamChat.of(context); - if (i < channelsStates.length) { - final channelState = channelsStates[i]; + if (i < channels.length) { + final channel = channels[i]; - final channelClient = - streamChat.client.channelClients[channelState.channel.id]; + final channelClient = streamChat.client.channelClients[channel.id]; ChannelTapCallback onTap; if (widget.onChannelTap != null) { @@ -109,7 +109,7 @@ class _ChannelListViewState extends State { builder: (context) { return StreamChannel( child: widget.channelWidget, - channelClient: client, + channel: client, ); }, ), @@ -119,12 +119,11 @@ class _ChannelListViewState extends State { return StreamChannel( key: ValueKey('CHANNEL-${channelClient.id}'), - child: StreamBuilder( - initialData: channelClient.state.channelState, - stream: channelClient.state.channelStateStream, + channel: channelClient, + child: StreamBuilder( + initialData: channelClient.updatedAt, + stream: channelClient.updatedAtStream, builder: (context, snapshot) { - final channelState = snapshot.data; - Widget child; if (widget.channelPreviewBuilder != null) { child = Stack( @@ -147,17 +146,15 @@ class _ChannelListViewState extends State { ); } else { child = ChannelPreview( - channelClient: channelClient, + channel: channelClient, onTap: (channelClient) { onTap(channelClient, widget.channelWidget); }, ); } - return child; }, ), - channelClient: channelClient, ); } else { return _buildQueryProgressIndicator(context, streamChat); diff --git a/lib/src/channel_name.dart b/lib/src/channel_name.dart index a875ab5d..3d7d03de 100644 --- a/lib/src/channel_name.dart +++ b/lib/src/channel_name.dart @@ -12,7 +12,9 @@ class ChannelName extends StatelessWidget { @override Widget build(BuildContext context) { return Text( - channel.extraData['name'] as String ?? channel.cid, + channel?.extraData != null + ? (channel?.extraData['name'] ?? channel.cid) + : channel.cid, style: Theme.of(context).textTheme.body2, ); } diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 2e6f319f..1345921c 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -3,39 +3,37 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'channel_image.dart'; +import '../stream_chat_flutter.dart'; import 'channel_name.dart'; class ChannelPreview extends StatelessWidget { - final void Function(ChannelClient) onTap; - final ChannelState channelState; - final ChannelClient channelClient; + final void Function(Channel) onTap; + final Channel channel; ChannelPreview({ - @required this.channelClient, + @required this.channel, Key key, this.onTap, - }) : channelState = channelClient.state.channelState, - super(key: key); + }) : super(key: key); @override Widget build(BuildContext context) { return ListTile( onTap: () { - onTap(channelClient); + onTap(channel); }, leading: ChannelImage( - channel: channelState.channel, + channel: channel, ), title: ChannelName( - channel: channelState.channel, + channel: channel, ), subtitle: _buildSubtitle(), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ - _buildDate(context, channelState.channel.lastMessageAt), + _buildDate(context, channel.lastMessageAt), ], ), ); @@ -64,11 +62,11 @@ class ChannelPreview extends StatelessWidget { Widget _buildSubtitle() { return StreamBuilder>( - stream: channelClient.state.typingEventsStream, + stream: channel.state.typingEventsStream, initialData: [], builder: (context, snapshot) { final typings = snapshot.data; - final opacity = channelClient.state.unreadCount > .0 ? 1.0 : 0.5; + final opacity = channel.state.unreadCount > .0 ? 1.0 : 0.5; return typings.isNotEmpty ? _buildTypings(typings, context, opacity) : _buildLastMessage(context, opacity); @@ -77,7 +75,7 @@ class ChannelPreview extends StatelessWidget { Widget _buildLastMessage(BuildContext context, double opacity) { final lastMessage = - channelState.messages.isNotEmpty ? channelState.messages.last : null; + channel.state.messages.isNotEmpty ? channel.state.messages.last : null; if (lastMessage == null) { return SizedBox(); } diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 243f1d31..83c0f4f4 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -60,7 +60,7 @@ class _MessageInputState extends State { }, controller: _textController, onChanged: (s) { - StreamChannel.of(context).channelClient.keyStroke(); + StreamChannel.of(context).channel.keyStroke(); setState(() { _messageIsPresent = s.trim().isNotEmpty; }); @@ -121,7 +121,7 @@ class _MessageInputState extends State { FocusScope.of(context).unfocus(); StreamChannel.of(context) - .channelClient + .channel .sendMessage( Message( parentId: widget.parent?.id, diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 0bc5528d..b9f0c0ed 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -67,7 +67,9 @@ class _MessageListViewState extends State { if (widget.parentMessage != null) { if (widget.parentMessageBuilder != null) { return widget.parentMessageBuilder( - context, widget.parentMessage); + context, + widget.parentMessage, + ); } else { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -104,10 +106,6 @@ class _MessageListViewState extends State { } final message = _messages[i]; - if (widget.messageBuilder != null) { - return widget.messageBuilder(context, message, i); - } - final previousMessage = i < _messages.length - 1 ? _messages[i + 1] : null; final nextMessage = i > 0 ? _messages[i - 1] : null; @@ -130,6 +128,13 @@ class _MessageListViewState extends State { ); } + if (widget.messageBuilder != null) { + return Builder( + key: ValueKey('MESSAGE-${message.id}'), + builder: (_) => widget.messageBuilder(context, message, i), + ); + } + return MessageWidget( key: ValueKey('MESSAGE-${message.id}'), previousMessage: previousMessage, @@ -182,15 +187,25 @@ class _MessageListViewState extends State { StreamChannelState streamChannelState, BuildContext context, ) { - return VisibilityDetector( - key: ValueKey('TOP-MESSAGE'), - child: MessageWidget( + Widget messageWidget; + if (widget.messageBuilder != null) { + messageWidget = Builder( + key: ValueKey('MESSAGE-${message.id}'), + builder: (_) => widget.messageBuilder(context, message, 0), + ); + } else { + messageWidget = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), previousMessage: null, message: message, nextMessage: nextMessage, onThreadTap: _onThreadTap, - ), + ); + } + + return VisibilityDetector( + key: ValueKey('TOP-MESSAGE'), + child: messageWidget, onVisibilityChanged: (visibility) { final topIsVisible = visibility.visibleBounds != Rect.zero; if (topIsVisible && !_topWasVisible) { @@ -207,23 +222,33 @@ class _MessageListViewState extends State { Message message, BuildContext context, ) { - return VisibilityDetector( - key: ValueKey('BOTTOM-MESSAGE'), - onVisibilityChanged: (visibility) { - _isBottom = visibility.visibleBounds != Rect.zero; - if (_isBottom) { - if (channelBloc.channelClient.state.unreadCount > 0) { - channelBloc.channelClient.markRead(); - } - } - }, - child: MessageWidget( + Widget messageWidget; + if (widget.messageBuilder != null) { + messageWidget = Builder( + key: ValueKey('MESSAGE-${message.id}'), + builder: (_) => widget.messageBuilder(context, message, 0), + ); + } else { + messageWidget = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), previousMessage: previousMessage, message: message, nextMessage: null, onThreadTap: _onThreadTap, - ), + ); + } + + return VisibilityDetector( + key: ValueKey('BOTTOM-MESSAGE'), + onVisibilityChanged: (visibility) { + _isBottom = visibility.visibleBounds != Rect.zero; + if (_isBottom) { + if (channelBloc.channel.state.unreadCount > 0) { + channelBloc.channel.markRead(); + } + } + }, + child: messageWidget, ); } @@ -234,17 +259,17 @@ class _MessageListViewState extends State { super.initState(); final streamChannel = StreamChannel.of(context); - if (streamChannel.channelClient.state.unreadCount > 0) { - streamChannel.channelClient.markRead(); + if (streamChannel.channel.state.unreadCount > 0) { + streamChannel.channel.markRead(); } Stream> stream; if (widget.parentMessage == null) { - stream = streamChannel.channelStateStream.map((c) => c.messages); + stream = streamChannel.channel.state.messagesStream; } else { streamChannel.getReplies(widget.parentMessage.id); - stream = streamChannel.channelClient.state.threadsStream + stream = streamChannel.channel.state.threadsStream .where((threads) => threads.containsKey(widget.parentMessage.id)) .map((threads) => threads[widget.parentMessage.id]); } @@ -258,7 +283,7 @@ class _MessageListViewState extends State { _messages = newMessages; }); } else if (newMessages.first.user.id == - streamChannel.channelClient.client.state.user.id) { + streamChannel.channel.client.state.user.id) { _scrollController.jumpTo(0); WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { @@ -293,7 +318,7 @@ class _MessageListViewState extends State { context, MaterialPageRoute(builder: (_) { return StreamChannel( - channelClient: StreamChannel.of(context).channelClient, + channel: StreamChannel.of(context).channel, child: widget.threadBuilder(context, message), ); }), diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index efeb6d6e..d1750b94 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -334,7 +334,7 @@ class _MessageWidgetState extends State void sendReaction(String reactionType) { StreamChannel.of(context) - .channelClient + .channel .sendReaction(widget.message.id, reactionType); Navigator.of(context).pop(); } diff --git a/lib/src/stream_channel.dart b/lib/src/stream_channel.dart index 2630249c..f8fbd449 100644 --- a/lib/src/stream_channel.dart +++ b/lib/src/stream_channel.dart @@ -8,13 +8,13 @@ class StreamChannel extends StatefulWidget { StreamChannel({ Key key, @required this.child, - @required this.channelClient, + @required this.channel, }) : super( key: key, ); final Widget child; - final ChannelClient channelClient; + final Channel channel; static StreamChannelState of(BuildContext context) { StreamChannelState streamChannelState; @@ -36,12 +36,10 @@ class StreamChannel extends StatefulWidget { class StreamChannelState extends State { StreamChannelState(); - ChannelClient get channelClient => widget.channelClient; - - ChannelState get channelState => widget.channelClient.state.channelState; + Channel get channel => widget.channel; Stream get channelStateStream => - widget.channelClient.state.channelStateStream; + widget.channel.state.channelStateStream; final BehaviorSubject _queryMessageController = BehaviorSubject(); @@ -51,11 +49,11 @@ class StreamChannelState extends State { _queryMessageController.add(true); String firstId; - if (channelState.messages.isNotEmpty) { - firstId = channelState.messages.first.id; + if (channel.state.messages.isNotEmpty) { + firstId = channel.state.messages.first.id; } - widget.channelClient + widget.channel .query( messagesPagination: PaginationParams( lessThan: firstId, @@ -73,11 +71,11 @@ class StreamChannelState extends State { _queryMessageController.add(true); String firstId; - if (widget.channelClient.state.threads.containsKey(parentId)) { - firstId = widget.channelClient.state.threads[parentId].first.id; + if (widget.channel.state.threads.containsKey(parentId)) { + firstId = widget.channel.state.threads[parentId].first.id; } - return widget.channelClient + return widget.channel .getReplies( parentId, PaginationParams( @@ -100,14 +98,14 @@ class StreamChannelState extends State { @override Widget build(BuildContext context) { - if (widget.channelClient == null) { + if (widget.channel == null) { return Center( child: CircularProgressIndicator(), ); } return FutureBuilder( - future: widget.channelClient.initialized, - initialData: widget.channelClient.state != null, + future: widget.channel.initialized, + initialData: widget.channel.state != null, builder: (context, snapshot) { if (!snapshot.hasData || !snapshot.data) { return Scaffold( diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 79d447e3..e892e0d2 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -18,7 +18,7 @@ class StreamChat extends InheritedWidget { child: child, ) { _subscriptions.add(client.on('message.new').listen((Event e) { - final index = channels.indexWhere((c) => c.channel.cid == e.cid); + final index = channels.indexWhere((c) => c.cid == e.cid); if (index > 0) { final channel = channels.removeAt(index); channels.insert(0, channel); @@ -31,10 +31,9 @@ class StreamChat extends InheritedWidget { Stream get userStream => client.state.userStream; - Stream> get channelsStream => _channelsController.stream; - final BehaviorSubject> _channelsController = - BehaviorSubject(); - final List channels = []; + Stream> get channelsStream => _channelsController.stream; + final BehaviorSubject> _channelsController = BehaviorSubject(); + final List channels = []; final BehaviorSubject _queryChannelsLoadingController = BehaviorSubject.seeded(false); @@ -60,7 +59,7 @@ class StreamChat extends InheritedWidget { options: options, paginationParams: paginationParams, ); - channels.addAll(res.map((c) => c.state.channelState)); + channels.addAll(res); _channelsController.sink.add(channels); _queryChannelsLoadingController.sink.add(false); } catch (e) { diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 989cffb2..9c430f94 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -1,6 +1,6 @@ export 'package:stream_chat/stream_chat.dart'; -export 'src/channel_avatar.dart'; +export 'src/channel_image.dart'; export 'src/channel_list_view.dart'; export 'src/channel_name.dart'; export 'src/channel_preview.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 32cf32e8..00831e3c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,8 @@ dependencies: video_player: ^0.10.7 chewie: ^0.9.8+1 animations: ^1.0.0+3 - stream_chat: ^0.1.6 + stream_chat: + path: ../stream_chat_dart dev_dependencies: pedantic: ^1.8.0+1