From 9be4d206de694cc2657d2b641e07b0fd52c1c79b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Mar 2020 12:19:31 +0100 Subject: [PATCH 001/133] add channel, user and messages db --- lib/src/channel_list_view.dart | 5 ++- lib/src/channel_preview.dart | 6 ++-- lib/src/message_widget.dart | 56 +++++++++++++++++++--------------- lib/src/stream_chat.dart | 26 ++-------------- pubspec.yaml | 3 +- 5 files changed, 43 insertions(+), 53 deletions(-) diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 329f27a5..622fc460 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -215,7 +215,10 @@ class _ChannelListViewState extends State { if (i < channels.length) { final channel = channels[i]; - final channelClient = streamChat.client.channels[channel.cid]; + final channelClient = streamChat.channels.firstWhere( + (c) => c.cid == channel.cid, + orElse: () => null, + ); ChannelTapCallback onTap; if (widget.onChannelTap != null) { diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 93b2d3ff..27b606cc 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -110,15 +110,15 @@ class ChannelPreview extends StatelessWidget { initialData: channel.state.messages, builder: (context, snapshot) { final messages = snapshot.data; - final lastMessage = messages.isNotEmpty ? messages.last : null; + final lastMessage = messages?.isNotEmpty == true ? messages.last : null; if (lastMessage == null) { return SizedBox(); } - String text; + String text = lastMessage.text; if (lastMessage.isDeleted) { text = 'This message was deleted.'; - } else { + } else if (lastMessage.attachments != null) { final prefix = lastMessage.attachments .map((e) { if (e.type == 'image') { diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 4e3cc042..9ec1ca7a 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -101,7 +101,7 @@ class _MessageWidgetState extends State widget.message.isDeleted ? _buildDeletedMessage(alignment) : _buildBubble(context), - if (_streamChannel.channel.config.replies) + if (_streamChannel.channel.config?.replies == true) _buildThreadIndicator(context), if (!_isNextUser) _buildTimestamp(alignment), ], @@ -146,7 +146,8 @@ class _MessageWidgetState extends State UserAvatar(user: widget.message.user), if (_isMyMessage && widget.nextMessage == null && - widget.message.status == MessageSendingStatus.SENT) + (widget.message.status == MessageSendingStatus.SENT || + widget.message.status == null)) Padding( padding: const EdgeInsets.symmetric( horizontal: 1.0, @@ -278,28 +279,30 @@ class _MessageWidgetState extends State var nOfAttachmentWidgets = 0; final column = - List.from(widget.message.attachments.map((attachment) { - nOfAttachmentWidgets++; + List.from(widget.message.attachments?.map((attachment) { + nOfAttachmentWidgets++; - Widget attachmentWidget; - if (attachment.type == 'video') { - attachmentWidget = _buildVideo(attachment); - } else if (attachment.type == 'image' || attachment.type == 'giphy') { - attachmentWidget = _buildImage(attachment); - } + Widget attachmentWidget; + if (attachment.type == 'video') { + attachmentWidget = _buildVideo(attachment); + } else if (attachment.type == 'image' || + attachment.type == 'giphy') { + attachmentWidget = _buildImage(attachment); + } - if (attachmentWidget != null) { - return _buildAttachment( - attachmentWidget, - attachment, - nOfAttachmentWidgets, - context, - ); - } + if (attachmentWidget != null) { + return _buildAttachment( + attachmentWidget, + attachment, + nOfAttachmentWidgets, + context, + ); + } - nOfAttachmentWidgets--; - return SizedBox(); - })); + nOfAttachmentWidgets--; + return SizedBox(); + }) ?? + []); if (widget.message.text.trim().isNotEmpty) { String text = widget.message.text; @@ -312,7 +315,7 @@ class _MessageWidgetState extends State ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - if (_streamChannel.channel.config.reactions && + if (_streamChannel.channel.config?.reactions == true && nOfAttachmentWidgets == 0) Align( child: _buildReactions(), @@ -323,7 +326,9 @@ class _MessageWidgetState extends State Stack( overflow: Overflow.visible, children: [ - if (nOfAttachmentWidgets == 0) _buildReactionPaint(), + if (nOfAttachmentWidgets == 0 && + _streamChannel.channel.config?.reactions == true) + _buildReactionPaint(), _buildMessageText(nOfAttachmentWidgets, text, context), ], ), @@ -333,7 +338,8 @@ class _MessageWidgetState extends State ); } - if (_streamChannel.channel.config.reactions && nOfAttachmentWidgets > 0) { + if (_streamChannel.channel.config?.reactions == true && + nOfAttachmentWidgets > 0) { column.insert( 0, Align( @@ -991,7 +997,7 @@ class _MessageWidgetState extends State @override bool get wantKeepAlive { - return widget.message.attachments.isNotEmpty; + return widget.message.attachments?.isNotEmpty == true; } } diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index ecdcda0e..c002721b 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -171,19 +171,6 @@ class StreamChatState extends State { return theme; } - @override - void initState() { - super.initState(); - _subscriptions.add(widget.client.on('message.new').listen((Event e) { - final index = channels.indexWhere((c) => c.cid == e.cid); - if (index > 0) { - final channel = channels.removeAt(index); - channels.insert(0, channel); - _channelsController.add(channels); - } - })); - } - /// The current user User get user => widget.client.state.user; @@ -191,12 +178,10 @@ class StreamChatState extends State { Stream get userStream => widget.client.state.userStream; /// The current channel list - final List channels = []; + List get channels => client.state.channels; /// The current channel list as a stream - Stream> get channelsStream => _channelsController.stream; - - final BehaviorSubject> _channelsController = BehaviorSubject(); + Stream> get channelsStream => client.state.channelsStream; final BehaviorSubject _queryChannelsLoadingController = BehaviorSubject.seeded(false); @@ -218,16 +203,12 @@ class StreamChatState extends State { _queryChannelsLoadingController.sink.add(true); try { - final res = await widget.client.queryChannels( + await widget.client.queryChannels( filter: filter, sort: sortOptions, options: options, paginationParams: paginationParams, ); - channels.addAll(res); - _channelsController.sink.add(channels); - } catch (e) { - _channelsController.sink.addError(e); } finally { _queryChannelsLoadingController.sink.add(false); } @@ -243,7 +224,6 @@ class StreamChatState extends State { widget.client.dispose(); _subscriptions.forEach((s) => s.cancel()); _queryChannelsLoadingController.close(); - _channelsController.close(); super.dispose(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 28aa55ba..cdfce517 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,8 @@ dependencies: file_picker: ^1.4.3+2 image_picker: ^0.6.3+4 keyboard_visibility: ^0.5.6 - stream_chat: ^0.1.18 + stream_chat: + path: ../stream_chat_dart dev_dependencies: pedantic: ^1.9.0 From ebe252b24a35a74d2f2d387e4a648584bf4657ab Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Mar 2020 13:28:58 +0100 Subject: [PATCH 002/133] hotfix --- lib/src/message_list_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 3d10388b..f84a5635 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -294,7 +294,7 @@ class _MessageListViewState extends State { key: ValueKey('BOTTOM-MESSAGE'), onVisibilityChanged: (visibility) { _isBottom = visibility.visibleBounds != Rect.zero; - if (_isBottom && streamChannel.channel.config.readEvents) { + if (_isBottom && streamChannel.channel.config?.readEvents == true) { if (streamChannel.channel.state.unreadCount > 0) { streamChannel.channel.markRead(); } From 940d7712878abb6362f150fe279a5c2f5dd8b067 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 10 Mar 2020 18:16:10 +0100 Subject: [PATCH 003/133] fix channelpreview lastmessage --- example/lib/main.dart | 11 ++++++----- lib/src/channel_preview.dart | 5 ++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d4855068..fd1311bc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() async { + WidgetsFlutterBinding.ensureInitialized(); final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, @@ -43,11 +44,11 @@ class ChannelListPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, +// filter: { +// 'members': { +// '\$in': [StreamChat.of(context).user.id], +// } +// }, sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 27b606cc..e6d7340a 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -110,7 +110,10 @@ class ChannelPreview extends StatelessWidget { initialData: channel.state.messages, builder: (context, snapshot) { final messages = snapshot.data; - final lastMessage = messages?.isNotEmpty == true ? messages.last : null; + final lastMessage = messages?.isNotEmpty == true + ? messages.lastWhere((m) => + !(m.isDeleted && m.status == MessageSendingStatus.FAILED)) + : null; if (lastMessage == null) { return SizedBox(); } From 44507d51ed4dc197cc0d22c5ab2e8cd7e710a1f2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Mar 2020 11:44:07 +0100 Subject: [PATCH 004/133] add reactions --- example/lib/main.dart | 20 +++++++++++--------- lib/src/message_list_view.dart | 8 +++++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index fd1311bc..6a1660ca 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,10 +8,12 @@ void main() async { logLevel: Level.INFO, ); - await client.setUser( - User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', - ); + await client + .setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ) + .catchError((e) {}); runApp(MyApp(client)); } @@ -44,11 +46,11 @@ class ChannelListPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: ChannelListView( -// filter: { -// 'members': { -// '\$in': [StreamChat.of(context).user.id], -// } -// }, + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index f84a5635..cbdf9749 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -215,9 +215,11 @@ class _MessageListViewState extends State { initialData: false, builder: (context, snapshot) { if (snapshot.hasError) { - print((snapshot.error as Error).stackTrace.toString()); - return Center( - child: Text(snapshot.error.toString()), + return Container( + color: Color(0xffd0021B).withAlpha(26), + child: Center( + child: Text('Error loading messages'), + ), ); } if (!snapshot.data) { From 54f1901620202daceaaceb65a531cc328a009417 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Mar 2020 15:42:29 +0100 Subject: [PATCH 005/133] add update message --- lib/src/channel_list_view.dart | 1 - lib/src/message_input.dart | 5 ++++- lib/src/message_widget.dart | 27 +++++++++++++++++++++++---- lib/src/stream_chat.dart | 5 ----- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 622fc460..7272f851 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -106,7 +106,6 @@ class _ChannelListViewState extends State { final streamChat = StreamChat.of(context); return RefreshIndicator( onRefresh: () async { - streamChat.clearChannels(); return streamChat.queryChannels( filter: widget.filter, sortOptions: widget.sort, diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 2c45d7a2..927643f7 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -664,7 +664,10 @@ class _MessageInputState extends State { sendingFuture = channel.sendMessage(message); } - sendingFuture = StreamChat.of(context).client.updateMessage(message); + sendingFuture = StreamChat.of(context).client.updateMessage( + message, + channel.cid, + ); } else { message = Message( parentId: widget.parentMessage?.id, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 9ec1ca7a..b2cb6755 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -161,7 +161,8 @@ class _MessageWidgetState extends State ), ), if (_isMyMessage && - widget.message.status == MessageSendingStatus.SENDING) + (widget.message.status == MessageSendingStatus.SENDING || + widget.message.status == MessageSendingStatus.UPDATING)) Padding( padding: const EdgeInsets.symmetric( horizontal: 1.0, @@ -177,7 +178,8 @@ class _MessageWidgetState extends State ), ), if (_isMyMessage && - widget.message.status == MessageSendingStatus.FAILED) + (widget.message.status == MessageSendingStatus.FAILED || + widget.message.status == MessageSendingStatus.FAILED_UPDATE)) Padding( padding: const EdgeInsets.symmetric( horizontal: 1.0, @@ -372,8 +374,16 @@ class _MessageWidgetState extends State ), ), onTap: () { + final channel = StreamChannel.of(context).channel; if (widget.message.status == MessageSendingStatus.FAILED) { - StreamChannel.of(context).channel.sendMessage(widget.message); + channel.sendMessage(widget.message); + return; + } + if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) { + StreamChat.of(context).client.updateMessage( + widget.message, + channel.cid, + ); return; } }, @@ -466,6 +476,14 @@ class _MessageWidgetState extends State fontSize: 11, ), ), + if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) + Text( + 'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN', + style: _messageTheme.messageText.copyWith( + color: Colors.black.withOpacity(.5), + fontSize: 11, + ), + ), MarkdownBody( data: text, onTapLink: (link) { @@ -989,7 +1007,8 @@ class _MessageWidgetState extends State topRight: Radius.circular((_isMyMessage && rectBorders) ? 2 : 16), bottomRight: Radius.circular(_isMyMessage ? 2 : 16), ), - color: widget.message.status == MessageSendingStatus.FAILED + color: (widget.message.status == MessageSendingStatus.FAILED || + widget.message.status == MessageSendingStatus.FAILED_UPDATE) ? Color(0xffd0021B).withAlpha(26) : _messageTheme.messageBackgroundColor, ); diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index c002721b..f35515ce 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -214,11 +214,6 @@ class StreamChatState extends State { } } - /// Clear the current channel list - void clearChannels() { - channels.clear(); - } - @override void dispose() { widget.client.dispose(); From fe5adc72dcd51c5b4184b25371574c12301ed3ae Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 11 Mar 2020 17:44:56 +0100 Subject: [PATCH 006/133] automatically retry message delete --- example/ios/Podfile.lock | 18 +++++++---- lib/src/full_screen_image.dart | 26 ++++++++++++++++ lib/src/message_input.dart | 28 ++++++++++++----- lib/src/message_widget.dart | 56 +++++++++++++++++++++++----------- pubspec.yaml | 1 + 5 files changed, 99 insertions(+), 30 deletions(-) create mode 100644 lib/src/full_screen_image.dart diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 07bb3fa8..b713289e 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -12,13 +12,13 @@ PODS: - keyboard_visibility (0.5.0): - Flutter - Reachability + - moor_ffi (0.0.1): + - Flutter - path_provider (0.0.1): - Flutter - path_provider_macos (0.0.1): - Flutter - Reachability (3.2) - - screen (0.0.1): - - Flutter - sqflite (0.0.1): - Flutter - FMDB (~> 2.7.2) @@ -32,6 +32,8 @@ PODS: - Flutter - video_player_web (0.0.1): - Flutter + - wakelock (0.0.1): + - Flutter DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) @@ -39,15 +41,16 @@ DEPENDENCIES: - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - keyboard_visibility (from `.symlinks/plugins/keyboard_visibility/ios`) + - moor_ffi (from `.symlinks/plugins/moor_ffi/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) - - screen (from `.symlinks/plugins/screen/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) - video_player (from `.symlinks/plugins/video_player/ios`) - video_player_web (from `.symlinks/plugins/video_player_web/ios`) + - wakelock (from `.symlinks/plugins/wakelock/ios`) SPEC REPOS: trunk: @@ -65,12 +68,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_picker/ios" keyboard_visibility: :path: ".symlinks/plugins/keyboard_visibility/ios" + moor_ffi: + :path: ".symlinks/plugins/moor_ffi/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" path_provider_macos: :path: ".symlinks/plugins/path_provider_macos/ios" - screen: - :path: ".symlinks/plugins/screen/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" url_launcher: @@ -83,6 +86,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/video_player/ios" video_player_web: :path: ".symlinks/plugins/video_player_web/ios" + wakelock: + :path: ".symlinks/plugins/wakelock/ios" SPEC CHECKSUMS: file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245 @@ -91,16 +96,17 @@ SPEC CHECKSUMS: FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f keyboard_visibility: 96a24de806fe6823c3ad956c01ba2ec6d056616f + moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6 path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 - screen: abd91ca7bf3426e1cc3646d27e9b2358d6bf07b0 sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c video_player: 69c5f029fac4ffe4fc8a85ea7f7b793709661549 video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 + wakelock: bd3dcc6a8bcf53a1c0309780ff192dcee67c6ccb PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83 diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart new file mode 100644 index 00000000..37413322 --- /dev/null +++ b/lib/src/full_screen_image.dart @@ -0,0 +1,26 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; + +class FullScreenImage extends StatelessWidget { + final String url; + + const FullScreenImage({ + Key key, + @required this.url, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + child: PhotoView( + imageProvider: CachedNetworkImageProvider(url), + maxScale: PhotoViewComputedScale.covered, + minScale: PhotoViewComputedScale.contained, + heroAttributes: PhotoViewHeroAttributes( + tag: url, + ), + ), + ); + } +} diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 927643f7..12024e08 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -586,14 +586,28 @@ class _MessageInputState extends State { _attachments.add(attachment); }); - final res = await channel.sendFile( - MultipartFile.fromBytes( - bytes, - filename: file.path.split('/').last, - ), - ); + String url; - attachment.url = res.file; + if (type == FileType.IMAGE) { + final res = await channel.sendImage( + MultipartFile.fromBytes( + bytes, + filename: file.path.split('/').last, + contentType: MediaType.parse('image/jpeg'), + ), + ); + url = res.file; + } else { + final res = await channel.sendFile( + MultipartFile.fromBytes( + bytes, + filename: file.path.split('/').last, + ), + ); + url = res.file; + } + + attachment.url = url; setState(() { attachment.uploaded = true; diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index b2cb6755..427c5419 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/full_screen_image.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; @@ -221,7 +222,7 @@ class _MessageWidgetState extends State alignment: alignment, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 16, + horizontal: 14, vertical: 14, ), child: Text( @@ -895,23 +896,44 @@ class _MessageWidgetState extends State Widget _buildImage( Attachment attachment, ) { - return CachedNetworkImage( - imageUrl: - attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, - errorWidget: (context, url, error) { - return Container( - width: 200, - height: 140, - color: Color(0xffd0021B).withAlpha(26), - child: Center( - child: Icon( - Icons.error_outline, - color: Colors.white, - ), - ), - ); + return GestureDetector( + onTap: () { + print(attachment.toJson()); + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + ); + })); }, - fit: BoxFit.cover, + child: Hero( + tag: attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl, + child: CachedNetworkImage( + placeholder: (_, __) { + return Container( + width: 200, + height: 140, + ); + }, + imageUrl: + attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, + errorWidget: (context, url, error) { + return Container( + width: 200, + height: 140, + color: Color(0xffd0021B).withAlpha(26), + child: Center( + child: Icon( + Icons.error_outline, + color: Colors.white, + ), + ), + ); + }, + fit: BoxFit.cover, + ), + ), ); } diff --git a/pubspec.yaml b/pubspec.yaml index cdfce517..1156b471 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: flutter: sdk: flutter + photo_view: ^0.9.2 rxdart: ^0.23.1 flutter_widgets: ^0.1.11 jiffy: ^3.0.0 From 6f696c41ed9c09999bbad61b8b5d21f37570460e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 12 Mar 2020 10:30:07 +0100 Subject: [PATCH 007/133] add offline attachment rendering --- lib/src/message_input.dart | 5 + lib/src/message_widget.dart | 193 ++++++++++++++++++++---------------- 2 files changed, 112 insertions(+), 86 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 12024e08..ee0bb635 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -573,6 +573,10 @@ class _MessageInputState extends State { file = await FilePicker.getFile(type: type); } + if (file == null) { + return; + } + final channel = StreamChannel.of(context).channel; final bytes = await file.readAsBytes(); @@ -717,6 +721,7 @@ class _MessageInputState extends State { imageUrl: attachment.type == FileType.IMAGE ? attachment.url : null, assetUrl: attachment.url, type: type, + localUri: attachment.file.uri, ); }); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 427c5419..033d695b 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; @@ -450,6 +451,32 @@ class _MessageWidgetState extends State ); } + Widget _buildSendingError(Widget child) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.message.status == MessageSendingStatus.FAILED) + Text( + 'MESSAGE FAILED · CLICK TO TRY AGAIN', + style: _messageTheme.messageText.copyWith( + color: Colors.black.withOpacity(.5), + fontSize: 11, + ), + ), + if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) + Text( + 'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN', + style: _messageTheme.messageText.copyWith( + color: Colors.black.withOpacity(.5), + fontSize: 11, + ), + ), + child, + ], + ); + } + Padding _buildMessageText( int nOfAttachmentWidgets, String text, @@ -465,62 +492,41 @@ class _MessageWidgetState extends State _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0), padding: EdgeInsets.all(10), constraints: BoxConstraints.loose(Size.fromWidth(300)), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.message.status == MessageSendingStatus.FAILED) - Text( - 'MESSAGE FAILED · CLICK TO TRY AGAIN', - style: _messageTheme.messageText.copyWith( - color: Colors.black.withOpacity(.5), - fontSize: 11, - ), - ), - if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) - Text( - 'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN', - style: _messageTheme.messageText.copyWith( - color: Colors.black.withOpacity(.5), - fontSize: 11, - ), - ), - MarkdownBody( - data: text, - onTapLink: (link) { - if (link.startsWith('@')) { - final mentionedUser = - widget.message.mentionedUsers.firstWhere( - (u) => '@${u.name.replaceAll(' ', '')}' == link, - orElse: () => null, - ); + child: _buildSendingError( + MarkdownBody( + data: text, + onTapLink: (link) { + if (link.startsWith('@')) { + final mentionedUser = widget.message.mentionedUsers.firstWhere( + (u) => '@${u.name.replaceAll(' ', '')}' == link, + orElse: () => null, + ); - if (widget.onMentionTap != null) { - widget.onMentionTap(mentionedUser); - } else { - print('tap on ${mentionedUser.name}'); - } + if (widget.onMentionTap != null) { + widget.onMentionTap(mentionedUser); } else { - _launchURL(link); + print('tap on ${mentionedUser.name}'); } - }, - 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, - ), - ), - ).copyWith( - p: _messageTheme.messageText, + } else { + _launchURL(link); + } + }, + 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, + ), ), + ).copyWith( + p: _messageTheme.messageText, ), - ], + ), ), ), ); @@ -896,42 +902,54 @@ class _MessageWidgetState extends State Widget _buildImage( Attachment attachment, ) { - return GestureDetector( - onTap: () { - print(attachment.toJson()); - Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - url: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, + return Hero( + tag: attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl, + child: CachedNetworkImage( + imageBuilder: (context, provider) { + return GestureDetector( + child: Image(image: provider), + onTap: () { + print(attachment.toJson()); + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + ); + })); + }, ); - })); - }, - child: Hero( - tag: attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl, - child: CachedNetworkImage( - placeholder: (_, __) { - return Container( - width: 200, - height: 140, - ); - }, - imageUrl: - attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, - errorWidget: (context, url, error) { - return Container( - width: 200, - height: 140, - color: Color(0xffd0021B).withAlpha(26), - child: Center( - child: Icon( - Icons.error_outline, - color: Colors.white, - ), - ), - ); - }, - fit: BoxFit.cover, + }, + placeholder: (_, __) { + return Container( + width: 200, + height: 140, + ); + }, + imageUrl: + attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, + errorWidget: (context, url, error) => _buildErrorImage(attachment), + fit: BoxFit.cover, + ), + ); + } + + Widget _buildErrorImage(Attachment attachment) { + if (attachment.localUri != null) { + return Image.file( + File(attachment.localUri.path), + ); + } + return Center( + child: Container( + width: 200, + height: 140, + color: Color(0xffd0021B).withAlpha(26), + child: Center( + child: Icon( + Icons.error_outline, + color: Colors.white, + ), ), ), ); @@ -956,6 +974,9 @@ class _MessageWidgetState extends State videoPlayerController: videoController, autoInitialize: true, errorBuilder: (_, e) { + if (attachment.thumbUrl == null) { + return _buildErrorImage(attachment); + } return Stack( children: [ Container( From ce3aae2e3813baaaae0eef4811242933f696c428 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 12 Mar 2020 16:08:42 +0100 Subject: [PATCH 008/133] add thread offline --- example/lib/single_conversation.dart | 3 ++- lib/src/message_list_view.dart | 13 +++++++------ lib/src/message_widget.dart | 1 - 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/example/lib/single_conversation.dart b/example/lib/single_conversation.dart index a7863fc0..92e6e205 100644 --- a/example/lib/single_conversation.dart +++ b/example/lib/single_conversation.dart @@ -25,12 +25,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// If you now run the simulator you will see a single channel UI. void main() async { + WidgetsFlutterBinding.ensureInitialized(); final client = Client( 'b67pax5b2wdq', logLevel: Level.INFO, ); - await client.setUser( + client.setUser( User(id: 'falling-mountain-7'), 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', ); diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index cbdf9749..4d13a129 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -320,11 +320,7 @@ class _MessageListViewState extends State { Stream> stream; if (widget.parentMessage == null) { - stream = streamChannel.channel.state.messagesStream.map((messages) => - messages - .where((m) => - !(m.status == MessageSendingStatus.FAILED && m.isDeleted)) - .toList()); + stream = streamChannel.channel.state.messagesStream; } else { streamChannel.getReplies(widget.parentMessage.id); stream = streamChannel.channel.state.threadsStream @@ -332,7 +328,12 @@ class _MessageListViewState extends State { .map((threads) => threads[widget.parentMessage.id]); } - _streamListener = stream.listen((newMessages) { + _streamListener = stream + .map((messages) => messages + .where((m) => + !(m.status == MessageSendingStatus.FAILED && m.isDeleted)) + .toList()) + .listen((newMessages) { newMessages = newMessages.reversed.toList(); if (_messages.isEmpty || newMessages.isEmpty || diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 033d695b..5f71ca2c 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -909,7 +909,6 @@ class _MessageWidgetState extends State return GestureDetector( child: Image(image: provider), onTap: () { - print(attachment.toJson()); Navigator.push(context, MaterialPageRoute(builder: (_) { return FullScreenImage( url: attachment.imageUrl ?? From ff0e21c345b9f285dd99c8a848582731407bc5ef Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 12 Mar 2020 17:01:40 +0100 Subject: [PATCH 009/133] update example --- example/lib/single_conversation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/single_conversation.dart b/example/lib/single_conversation.dart index 92e6e205..fc52eb23 100644 --- a/example/lib/single_conversation.dart +++ b/example/lib/single_conversation.dart @@ -31,7 +31,7 @@ void main() async { logLevel: Level.INFO, ); - client.setUser( + await client.setUser( User(id: 'falling-mountain-7'), 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', ); From 57341d913cda5833790c0169bd75fa5a44a88f58 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 12 Mar 2020 17:42:34 +0100 Subject: [PATCH 010/133] update single conversation example --- example/lib/single_conversation.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/example/lib/single_conversation.dart b/example/lib/single_conversation.dart index fc52eb23..785c02f2 100644 --- a/example/lib/single_conversation.dart +++ b/example/lib/single_conversation.dart @@ -31,10 +31,12 @@ void main() async { logLevel: Level.INFO, ); - await client.setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', - ); + await client + .setUser( + User(id: 'falling-mountain-7'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + ) + .catchError((e) {}); final channel = client.channel('messaging', id: 'godevs'); From 6f5d014fa40918791f4e922058604724e1da1137 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 12 Mar 2020 18:13:14 +0100 Subject: [PATCH 011/133] fix analysis option --- analysis_options.yaml | 122 +++++++++++++++++++-------------------- lib/src/stream_chat.dart | 2 +- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index acc17647..fedac90d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,61 +1,61 @@ -#include: package:pedantic/analysis_options.yaml -# -#analyzer: -# exclude: -# - lib/**/*.g.dart -# - example/* -# -#linter: -# rules: -# # these rules are documented on and in the same order as -# # the Dart Lint rules page to make maintenance easier -# # https://github.com/dart-lang/linter/blob/master/example/all.yaml -# # - always_declare_return_types -# # - always_specify_types -# # - annotate_overrides -# # - avoid_as -# - avoid_empty_else -# - avoid_init_to_null -# - avoid_return_types_on_setters -# - avoid_web_libraries_in_flutter -# - await_only_futures -# - camel_case_types -# - cancel_subscriptions -# - close_sinks -# # - comment_references # we do not presume as to what people want to reference in their dartdocs -# # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 -# - control_flow_in_finally -# - empty_constructor_bodies -# - empty_statements -# - hash_and_equals -# - implementation_imports -# # - invariant_booleans -# # - iterable_contains_unrelated_type -# - library_names -# # - library_prefixes -# # - list_remove_unrelated_type -# # - literal_only_boolean_expressions -# - non_constant_identifier_names -# # - one_member_abstracts -# # - only_throw_errors -# # - overridden_fields -## - package_api_docs -# - package_names -# - package_prefixed_library_names -# - prefer_is_not_empty -# # - prefer_mixin # https://github.com/dart-lang/language/issues/32 -## - public_member_api_docs -# - slash_for_doc_comments -# # - sort_constructors_first -# # - sort_unnamed_constructors_first -# # - super_goes_last # no longer needed w/ Dart 2 -# - test_types_in_equals -# - throw_in_finally -# # - type_annotate_public_apis # subset of always_specify_types -# - type_init_formals -# # - unawaited_futures -# - unnecessary_brace_in_string_interps -# - unnecessary_getters_setters -# - unnecessary_statements -# - unrelated_type_equality_checks -# - valid_regexps +include: package:pedantic/analysis_options.yaml + +analyzer: + exclude: + - lib/**/*.g.dart + - example/* + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + # - always_declare_return_types + # - always_specify_types + # - annotate_overrides + # - avoid_as + - avoid_empty_else + - avoid_init_to_null + - avoid_return_types_on_setters + - avoid_web_libraries_in_flutter + - await_only_futures + - camel_case_types + - cancel_subscriptions + - close_sinks + # - comment_references # we do not presume as to what people want to reference in their dartdocs + # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + - empty_constructor_bodies + - empty_statements + - hash_and_equals + - implementation_imports + # - invariant_booleans + # - iterable_contains_unrelated_type + - library_names + # - library_prefixes + # - list_remove_unrelated_type + # - literal_only_boolean_expressions + - non_constant_identifier_names + # - one_member_abstracts + # - only_throw_errors + # - overridden_fields +# - package_api_docs + - package_names + - package_prefixed_library_names + - prefer_is_not_empty + # - prefer_mixin # https://github.com/dart-lang/language/issues/32 +# - public_member_api_docs + - slash_for_doc_comments + # - sort_constructors_first + # - sort_unnamed_constructors_first + # - super_goes_last # no longer needed w/ Dart 2 + - test_types_in_equals + - throw_in_finally + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures + - unnecessary_brace_in_string_interps + - unnecessary_getters_setters + - unnecessary_statements + - unrelated_type_equality_checks + - valid_regexps diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index f35515ce..2c5d18a5 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -203,7 +203,7 @@ class StreamChatState extends State { _queryChannelsLoadingController.sink.add(true); try { - await widget.client.queryChannels( + widget.client.queryChannels( filter: filter, sort: sortOptions, options: options, From 031f85edf66f331272923d1ac41b8b78941da0ce Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 13 Mar 2020 14:34:01 +0100 Subject: [PATCH 012/133] show error on channel loading --- example/lib/main.dart | 21 +++++++++------------ example/lib/single_conversation.dart | 11 ++++------- lib/src/channel_list_view.dart | 13 ++++++++++++- lib/src/channel_preview.dart | 2 +- lib/src/message_widget.dart | 6 +++--- lib/src/stream_chat.dart | 9 ++++++--- 6 files changed, 35 insertions(+), 27 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index a22b3eed..041668c6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,18 +2,15 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() async { - WidgetsFlutterBinding.ensureInitialized(); final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, ); - await client - .setUser( - User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', - ) - .catchError((e) {}); + await client.setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); runApp(MyApp(client)); } @@ -44,11 +41,11 @@ class ChannelListPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, +// filter: { +// 'members': { +// '\$in': [StreamChat.of(context).user.id], +// } +// }, sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/example/lib/single_conversation.dart b/example/lib/single_conversation.dart index 785c02f2..a7863fc0 100644 --- a/example/lib/single_conversation.dart +++ b/example/lib/single_conversation.dart @@ -25,18 +25,15 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// If you now run the simulator you will see a single channel UI. void main() async { - WidgetsFlutterBinding.ensureInitialized(); final client = Client( 'b67pax5b2wdq', logLevel: Level.INFO, ); - await client - .setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', - ) - .catchError((e) {}); + await client.setUser( + User(id: 'falling-mountain-7'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + ); final channel = client.channel('messaging', id: 'godevs'); diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 7272f851..41a5bd76 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -125,7 +125,7 @@ class _ChannelListViewState extends State { return widget.errorBuilder(snapshot.error); } - String message = snapshot.error.toString(); + var message = snapshot.error.toString(); if (snapshot.error is DioError) { final dioError = snapshot.error as DioError; if (dioError.type == DioErrorType.RESPONSE) { @@ -287,6 +287,17 @@ class _ChannelListViewState extends State { stream: streamChat.queryChannelsLoading, initialData: false, builder: (context, snapshot) { + if (snapshot.hasError) { + return Container( + color: Color(0xffd0021B).withAlpha(26), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Center( + child: Text('Error loading messages'), + ), + ), + ); + } return Container( height: 100, padding: EdgeInsets.all(32), diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index e6d7340a..cfdb3ea5 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -118,7 +118,7 @@ class ChannelPreview extends StatelessWidget { return SizedBox(); } - String text = lastMessage.text; + var text = lastMessage.text; if (lastMessage.isDeleted) { text = 'This message was deleted.'; } else if (lastMessage.attachments != null) { diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 95cab99d..48a1d1f2 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -95,7 +95,7 @@ class _MessageWidgetState extends State final alignment = _isMyMessage ? Alignment.centerRight : Alignment.centerLeft; - List row = [ + var row = List.from([ Column( crossAxisAlignment: _isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, @@ -113,7 +113,7 @@ class _MessageWidgetState extends State width: 40, ) : _buildUserAvatar(), - ]; + ]); if (!_isMyMessage) { row = row.reversed.toList(); @@ -309,7 +309,7 @@ class _MessageWidgetState extends State []); if (widget.message.text.trim().isNotEmpty) { - String text = widget.message.text; + var text = widget.message.text; text = _replaceMentions(text); column.addAll( diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index a70a4506..e18de6d0 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -197,20 +197,23 @@ class StreamChatState extends State { PaginationParams paginationParams, Map options, }) async { - if (_queryChannelsLoadingController.value) { + if (_queryChannelsLoadingController.value == true) { return; } _queryChannelsLoadingController.sink.add(true); try { - widget.client.queryChannels( + await widget.client.queryChannels( filter: filter, sort: sortOptions, options: options, paginationParams: paginationParams, ); - } finally { _queryChannelsLoadingController.sink.add(false); + } catch (err, stackTrace) { + _queryChannelsLoadingController.addError(err, stackTrace); + } finally { +// _queryChannelsLoadingController.sink.add(false); } } From 4fbd38b0fe48aa00d2b7e9e38cc5e1556fcb1a74 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 13 Mar 2020 15:13:16 +0100 Subject: [PATCH 013/133] hotfix --- lib/src/channel_list_view.dart | 2 +- lib/src/message_widget.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 41a5bd76..14b68bf4 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -293,7 +293,7 @@ class _ChannelListViewState extends State { child: Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Center( - child: Text('Error loading messages'), + child: Text('Error loading channels'), ), ), ); diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 48a1d1f2..cd891ec0 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -156,6 +156,7 @@ class _MessageWidgetState extends State ), child: CircleAvatar( radius: 4, + backgroundColor: StreamChatTheme.of(context).accentColor, child: Icon( Icons.done, size: 4, From ae41eb30bfd5bea5e7aafc1463747c9474902766 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 13 Mar 2020 15:38:23 +0100 Subject: [PATCH 014/133] update example --- example/lib/customize_channel_preview.dart | 4 ++-- example/lib/main.dart | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/example/lib/customize_channel_preview.dart b/example/lib/customize_channel_preview.dart index 949624d1..b6603f06 100644 --- a/example/lib/customize_channel_preview.dart +++ b/example/lib/customize_channel_preview.dart @@ -73,11 +73,11 @@ class ChannelListPage extends StatelessWidget { Widget _channelPreviewBuilder(BuildContext context, Channel channel) { final lastMessage = channel.state.messages.reversed.firstWhere( - (message) => message.type != "deleted", + (message) => message.type != 'deleted', orElse: () => null, ); - final subtitle = (lastMessage == null ? "nothing yet" : lastMessage.text); + final subtitle = (lastMessage == null ? 'nothing yet' : lastMessage.text); final opacity = channel.state.unreadCount > .0 ? 1.0 : 0.5; return ListTile( diff --git a/example/lib/main.dart b/example/lib/main.dart index 041668c6..cb77a82a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,6 +5,7 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, + persistenceEnabled: false, ); await client.setUser( @@ -41,11 +42,11 @@ class ChannelListPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: ChannelListView( -// filter: { -// 'members': { -// '\$in': [StreamChat.of(context).user.id], -// } -// }, + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, From 18765f982310377e209c5d3b8f1f67ff7c21dce2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 13 Mar 2020 15:39:43 +0100 Subject: [PATCH 015/133] fix delete error --- lib/src/message_widget.dart | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index cd891ec0..1e152e72 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -16,6 +16,7 @@ import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_channel.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:video_player/video_player.dart'; @@ -100,7 +101,8 @@ class _MessageWidgetState extends State crossAxisAlignment: _isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - widget.message.isDeleted + (widget.message.isDeleted && + widget.message.status != MessageSendingStatus.FAILED_DELETE) ? _buildDeletedMessage(alignment) : _buildBubble(context), if (_streamChannel.channel.config?.replies == true) @@ -182,7 +184,8 @@ class _MessageWidgetState extends State ), if (_isMyMessage && (widget.message.status == MessageSendingStatus.FAILED || - widget.message.status == MessageSendingStatus.FAILED_UPDATE)) + widget.message.status == MessageSendingStatus.FAILED_UPDATE || + widget.message.status == MessageSendingStatus.FAILED_DELETE)) Padding( padding: const EdgeInsets.symmetric( horizontal: 1.0, @@ -389,6 +392,14 @@ class _MessageWidgetState extends State ); return; } + + if (widget.message.status == MessageSendingStatus.FAILED_DELETE) { + StreamChat.of(context).client.deleteMessage( + widget.message, + channel.cid, + ); + return; + } }, onLongPress: () { if (widget.message.isEphemeral || @@ -474,6 +485,14 @@ class _MessageWidgetState extends State fontSize: 11, ), ), + if (widget.message.status == MessageSendingStatus.FAILED_DELETE) + Text( + 'MESSAGE DELETE FAILED · CLICK TO TRY AGAIN', + style: _messageTheme.messageText.copyWith( + color: Colors.black.withOpacity(.5), + fontSize: 11, + ), + ), child, ], ); @@ -1060,7 +1079,8 @@ class _MessageWidgetState extends State bottomRight: Radius.circular(_isMyMessage ? 2 : 16), ), color: (widget.message.status == MessageSendingStatus.FAILED || - widget.message.status == MessageSendingStatus.FAILED_UPDATE) + widget.message.status == MessageSendingStatus.FAILED_UPDATE || + widget.message.status == MessageSendingStatus.FAILED_DELETE) ? Color(0xffd0021B).withAlpha(26) : _messageTheme.messageBackgroundColor, ); From 2f6143234aaaba988a6d07800ea1a1f11eaa1611 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 13 Mar 2020 15:44:25 +0100 Subject: [PATCH 016/133] fix bug on empty thread --- lib/src/message_list_view.dart | 10 ++++++---- lib/src/stream_channel.dart | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 4d13a129..7b2dc5f1 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -329,10 +329,12 @@ class _MessageListViewState extends State { } _streamListener = stream - .map((messages) => messages - .where((m) => - !(m.status == MessageSendingStatus.FAILED && m.isDeleted)) - .toList()) + .map((messages) => + messages + ?.where((m) => + !(m.status == MessageSendingStatus.FAILED && m.isDeleted)) + ?.toList() ?? + []) .listen((newMessages) { newMessages = newMessages.reversed.toList(); if (_messages.isEmpty || diff --git a/lib/src/stream_channel.dart b/lib/src/stream_channel.dart index 2943441e..70357b65 100644 --- a/lib/src/stream_channel.dart +++ b/lib/src/stream_channel.dart @@ -76,7 +76,6 @@ class StreamChannelState extends State { /// Calls [channel.getReplies] updating [queryMessage] stream Future getReplies(String parentId) async { _queryMessageController.add(true); - print('PARENT $parentId'); String firstId; if (widget.channel.state.threads.containsKey(parentId)) { From b2d27e65d03007740d635d48638ee9a840887865 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sun, 15 Mar 2020 13:59:56 +0100 Subject: [PATCH 017/133] fix linter --- lib/src/message_input.dart | 2 +- lib/src/message_widget.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 93edfb8b..a95144f4 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -593,7 +593,7 @@ class _MessageInputState extends State { String url; - if (type == FileType.IMAGE) { + if (type == FileType.image) { final res = await channel.sendImage( MultipartFile.fromBytes( bytes, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 57d3e2e4..1e152e72 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -96,7 +96,7 @@ class _MessageWidgetState extends State final alignment = _isMyMessage ? Alignment.centerRight : Alignment.centerLeft; - var row = [ + var row = List.from([ Column( crossAxisAlignment: _isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, From af16d907896eaa1c5327abdead630bb5eb4d63df Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 17 Mar 2020 12:11:03 +0100 Subject: [PATCH 018/133] add android example --- .gitignore | 3 ++- example/android/app/build.gradle | 3 +++ .../android/app/src/main/AndroidManifest.xml | 6 ++++- .../kotlin/com/example/example/Application.kt | 22 +++++++++++++++++++ example/android/build.gradle | 1 + example/lib/main.dart | 5 ++--- lib/src/channel_list_view.dart | 2 +- lib/src/stream_chat.dart | 21 +++++++++++++++--- 8 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 example/android/app/src/main/kotlin/com/example/example/Application.kt diff --git a/.gitignore b/.gitignore index 28b825a6..457042a0 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,5 @@ doc/api/ *.js.deps *.js.map -fvm \ No newline at end of file +fvm +google-services.json \ No newline at end of file diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 0f6a5e54..dda667cf 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -64,4 +64,7 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + implementation 'com.google.firebase:firebase-messaging:20.1.2' } + +apply plugin: 'com.google.gms.google-services' diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 1b2ad157..c5f1e85b 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,7 @@ FlutterApplication and put your custom class here. --> + + + + diff --git a/example/android/app/src/main/kotlin/com/example/example/Application.kt b/example/android/app/src/main/kotlin/com/example/example/Application.kt new file mode 100644 index 00000000..dabf2307 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/example/Application.kt @@ -0,0 +1,22 @@ +package com.example.example + +import io.flutter.app.FlutterApplication +import io.flutter.plugin.common.PluginRegistry +import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback +import io.flutter.plugins.GeneratedPluginRegistrant +import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin +import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService +import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin + +class Application : FlutterApplication(), PluginRegistrantCallback { + override fun onCreate() { + super.onCreate() + FlutterFirebaseMessagingService.setPluginRegistrant(this) + } + + override fun registerWith(registry: PluginRegistry?) { + SharedPreferencesPlugin.registerWith(registry?.registrarFor( + "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")); + FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")) + } +} \ No newline at end of file diff --git a/example/android/build.gradle b/example/android/build.gradle index 3100ad2d..70b3637d 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -8,6 +8,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.google.gms:google-services:4.3.2' } } diff --git a/example/lib/main.dart b/example/lib/main.dart index cb77a82a..9d8dc1fa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,12 +5,11 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - persistenceEnabled: false, ); await client.setUser( - User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + User(id: 'super-band-007'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC0wMDcifQ.DlbyHN6nK7jBTJDp6RJOuBJWwnYoy8Zq8208RPL4uLY', ); runApp(MyApp(client)); diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 14b68bf4..67fc3c5f 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -116,7 +116,7 @@ class _ChannelListViewState extends State { child: StreamBuilder>( stream: streamChat.channelsStream, builder: (context, snapshot) { - if (snapshot.hasError) { + if (false && snapshot.hasError) { if (snapshot.error is Error) { print((snapshot.error as Error).stackTrace); } diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index e18de6d0..554afaa1 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -59,7 +59,7 @@ class StreamChat extends StatefulWidget { } } -class StreamChatState extends State { +class StreamChatState extends State with WidgetsBindingObserver { final List _subscriptions = []; Client get client => widget.client; final GlobalKey _navigatorKey = GlobalKey(); @@ -212,8 +212,22 @@ class StreamChatState extends State { _queryChannelsLoadingController.sink.add(false); } catch (err, stackTrace) { _queryChannelsLoadingController.addError(err, stackTrace); - } finally { -// _queryChannelsLoadingController.sink.add(false); + } + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + print('NEW STATE $state'); + if (state == AppLifecycleState.paused) { + client.disconnect(); + } else if (state == AppLifecycleState.resumed) { + client.connect(); } } @@ -221,6 +235,7 @@ class StreamChatState extends State { void dispose() { _subscriptions.forEach((s) => s.cancel()); _queryChannelsLoadingController.close(); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } } From 64a40d60dad09c89a97044b251556bfc7a51a039 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 20 Mar 2020 15:38:12 +0100 Subject: [PATCH 019/133] use normal notifications --- example/ios/Podfile.lock | 147 +++++++++++++++++++++++++++ example/lib/single_conversation.dart | 13 ++- lib/src/channel_list_view.dart | 2 +- lib/src/message_widget.dart | 2 +- lib/src/stream_chat.dart | 5 +- pubspec.yaml | 2 +- 6 files changed, 162 insertions(+), 9 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b713289e..26b542f0 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,12 +1,94 @@ PODS: - file_picker (0.0.1): - Flutter + - Firebase/Core (6.20.0): + - Firebase/CoreOnly + - FirebaseAnalytics (= 6.3.1) + - Firebase/CoreOnly (6.20.0): + - FirebaseCore (= 6.6.4) + - Firebase/Messaging (6.20.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 4.3.0) + - firebase_messaging (0.0.1): + - Firebase/Core + - Firebase/Messaging + - Flutter + - FirebaseAnalytics (6.3.1): + - FirebaseCore (~> 6.6) + - FirebaseInstallations (~> 1.1) + - GoogleAppMeasurement (= 6.3.1) + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - FirebaseAnalyticsInterop (1.5.0) + - FirebaseCore (6.6.4): + - FirebaseCoreDiagnostics (~> 1.2) + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - FirebaseCoreDiagnostics (1.2.2): + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleDataTransportCCTSupport (~> 2.0) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - nanopb (~> 0.3.901) + - FirebaseCoreDiagnosticsInterop (1.2.0) + - FirebaseInstallations (1.1.0): + - FirebaseCore (~> 6.6) + - GoogleUtilities/UserDefaults (~> 6.5) + - PromisesObjC (~> 1.2) + - FirebaseInstanceID (4.3.2): + - FirebaseCore (~> 6.6) + - FirebaseInstallations (~> 1.0) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - FirebaseMessaging (4.3.0): + - FirebaseAnalyticsInterop (~> 1.5) + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - GoogleUtilities/AppDelegateSwizzler (~> 6.5) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Reachability (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - Protobuf (>= 3.9.2, ~> 3.9) - Flutter (1.0.0) + - flutter_apns (0.0.1): + - Flutter - flutter_plugin_android_lifecycle (0.0.1): - Flutter - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) + - GoogleAppMeasurement (6.3.1): + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - GoogleDataTransport (5.0.0) + - GoogleDataTransportCCTSupport (2.0.0): + - GoogleDataTransport (~> 5.0) + - nanopb (~> 0.3.901) + - GoogleUtilities/AppDelegateSwizzler (6.5.2): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (6.5.2) + - GoogleUtilities/Logger (6.5.2): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (6.5.2): + - GoogleUtilities/Logger + - GoogleUtilities/Network (6.5.2): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (6.5.2)" + - GoogleUtilities/Reachability (6.5.2): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (6.5.2): + - GoogleUtilities/Logger - image_picker (0.0.1): - Flutter - keyboard_visibility (0.5.0): @@ -14,11 +96,24 @@ PODS: - Reachability - moor_ffi (0.0.1): - Flutter + - nanopb (0.3.9011): + - nanopb/decode (= 0.3.9011) + - nanopb/encode (= 0.3.9011) + - nanopb/decode (0.3.9011) + - nanopb/encode (0.3.9011) - path_provider (0.0.1): - Flutter - path_provider_macos (0.0.1): - Flutter + - PromisesObjC (1.2.8) + - Protobuf (3.11.4) - Reachability (3.2) + - shared_preferences (0.0.1): + - Flutter + - shared_preferences_macos (0.0.1): + - Flutter + - shared_preferences_web (0.0.1): + - Flutter - sqflite (0.0.1): - Flutter - FMDB (~> 2.7.2) @@ -37,13 +132,18 @@ PODS: DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - Flutter (from `Flutter`) + - flutter_apns (from `.symlinks/plugins/flutter_apns/ios`) - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - keyboard_visibility (from `.symlinks/plugins/keyboard_visibility/ios`) - moor_ffi (from `.symlinks/plugins/moor_ffi/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) + - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) + - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) @@ -54,14 +154,34 @@ DEPENDENCIES: SPEC REPOS: trunk: + - Firebase + - FirebaseAnalytics + - FirebaseAnalyticsInterop + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseCoreDiagnosticsInterop + - FirebaseInstallations + - FirebaseInstanceID + - FirebaseMessaging - FMDB + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleDataTransportCCTSupport + - GoogleUtilities + - nanopb + - PromisesObjC + - Protobuf - Reachability EXTERNAL SOURCES: file_picker: :path: ".symlinks/plugins/file_picker/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" Flutter: :path: Flutter + flutter_apns: + :path: ".symlinks/plugins/flutter_apns/ios" flutter_plugin_android_lifecycle: :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" image_picker: @@ -74,6 +194,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider/ios" path_provider_macos: :path: ".symlinks/plugins/path_provider_macos/ios" + shared_preferences: + :path: ".symlinks/plugins/shared_preferences/ios" + shared_preferences_macos: + :path: ".symlinks/plugins/shared_preferences_macos/ios" + shared_preferences_web: + :path: ".symlinks/plugins/shared_preferences_web/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" url_launcher: @@ -91,15 +217,36 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245 + Firebase: fe7f74012742ab403451dd283e6909b8f1fb348a + firebase_messaging: edab61b94fb3bfb399f32f9d4dedb5196d680084 + FirebaseAnalytics: 572e467f3d977825266e8ccd52674aa3e6f47eac + FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae + FirebaseCore: ed0a24c758a57c2b88c5efa8e6a8195e868af589 + FirebaseCoreDiagnostics: e9b4cd8ba60dee0f2d13347332e4b7898cca5b61 + FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 + FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b + FirebaseInstanceID: 7ee0d6777013bb952f377b41965bf132b6a075be + FirebaseMessaging: 4ec33842d36b3319e062e51fb8b35a74f726950d Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + GoogleAppMeasurement: c29d405ff76e18551b5d158eaba6753fda8c7542 + GoogleDataTransport: a857c6a002d201b524dd4bc2ed7e7355ed07e785 + GoogleDataTransportCCTSupport: 32f75fbe904c82772fcbb6b6bd4525bfb6f2a862 + GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f keyboard_visibility: 96a24de806fe6823c3ad956c01ba2ec6d056616f moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6 + nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 + PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 + Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 + shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 + shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 + shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 diff --git a/example/lib/single_conversation.dart b/example/lib/single_conversation.dart index a7863fc0..1534d089 100644 --- a/example/lib/single_conversation.dart +++ b/example/lib/single_conversation.dart @@ -26,18 +26,23 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// If you now run the simulator you will see a single channel UI. void main() async { final client = Client( - 'b67pax5b2wdq', + 's2dxdhpxd94g', logLevel: Level.INFO, ); await client.setUser( User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.Xd4h2PUBo2NYPk12gjlXDNY71jlyJYTCuQ_moeNbnbA', ); - final channel = client.channel('messaging', id: 'godevs'); + final channel = client.channel('messaging', extraData: { + 'members': [ + 'falling-mountain-7', + '12a73f88-4dd6-44d3-9185-014002d64b33', + ], + }); - // ignore: unawaited_futures + await channel.create(); channel.watch(); runApp(MyApp(client, channel)); diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 67fc3c5f..14b68bf4 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -116,7 +116,7 @@ class _ChannelListViewState extends State { child: StreamBuilder>( stream: streamChat.channelsStream, builder: (context, snapshot) { - if (false && snapshot.hasError) { + if (snapshot.hasError) { if (snapshot.error is Error) { print((snapshot.error as Error).stackTrace); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 1e152e72..741fb27e 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -261,7 +261,7 @@ class _MessageWidgetState extends State row = row.reversed.toList(); } - return widget.message.replyCount > 0 + return (widget.message.replyCount ?? 0) > 0 ? GestureDetector( onTap: () { if (widget.isParent) { diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 554afaa1..96ab0f93 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -223,11 +223,12 @@ class StreamChatState extends State with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { - print('NEW STATE $state'); if (state == AppLifecycleState.paused) { client.disconnect(); } else if (state == AppLifecycleState.resumed) { - client.connect(); + if (client.wsConnectionStatus.value == ConnectionStatus.disconnected) { + client.connect(); + } } } diff --git a/pubspec.yaml b/pubspec.yaml index 3bd75274..62b0652b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: url_launcher: ^5.4.2 video_player: ^0.10.8+1 chewie: ^0.9.10 - file_picker: ^1.5.0 + file_picker: ^1.5.0+2 image_picker: ^0.6.3+4 keyboard_visibility: ^0.5.6 stream_chat: From fabdcc6a3e604c53582832b042156083e6fc039e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 23 Mar 2020 13:02:24 +0100 Subject: [PATCH 020/133] fix example --- example/lib/single_conversation.dart | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/example/lib/single_conversation.dart b/example/lib/single_conversation.dart index 1534d089..7a7b40ab 100644 --- a/example/lib/single_conversation.dart +++ b/example/lib/single_conversation.dart @@ -35,14 +35,9 @@ void main() async { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.Xd4h2PUBo2NYPk12gjlXDNY71jlyJYTCuQ_moeNbnbA', ); - final channel = client.channel('messaging', extraData: { - 'members': [ - 'falling-mountain-7', - '12a73f88-4dd6-44d3-9185-014002d64b33', - ], - }); + final channel = client.channel('messaging', id: 'godevs'); - await channel.create(); + // ignore: unawaited_futures channel.watch(); runApp(MyApp(client, channel)); From f4af785d453df81d43fbb056ed03654b377c002e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 24 Mar 2020 09:11:34 +0100 Subject: [PATCH 021/133] use data notification on android --- .../app/src/main/kotlin/com/example/example/Application.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/android/app/src/main/kotlin/com/example/example/Application.kt b/example/android/app/src/main/kotlin/com/example/example/Application.kt index dabf2307..247fdcf8 100644 --- a/example/android/app/src/main/kotlin/com/example/example/Application.kt +++ b/example/android/app/src/main/kotlin/com/example/example/Application.kt @@ -1,9 +1,9 @@ package com.example.example +import com.example.path_provider.PathProviderPlugin import io.flutter.app.FlutterApplication import io.flutter.plugin.common.PluginRegistry import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback -import io.flutter.plugins.GeneratedPluginRegistrant import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin @@ -16,7 +16,7 @@ class Application : FlutterApplication(), PluginRegistrantCallback { override fun registerWith(registry: PluginRegistry?) { SharedPreferencesPlugin.registerWith(registry?.registrarFor( - "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")); + "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")) FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")) } } \ No newline at end of file From 4356b4f0ea89f3358bad56ddeb22fa6572e918f8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 25 Mar 2020 10:10:20 +0100 Subject: [PATCH 022/133] add android push customization --- .../kotlin/com/example/example/Application.kt | 3 +++ example/lib/main.dart | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/example/android/app/src/main/kotlin/com/example/example/Application.kt b/example/android/app/src/main/kotlin/com/example/example/Application.kt index 247fdcf8..dfe01b38 100644 --- a/example/android/app/src/main/kotlin/com/example/example/Application.kt +++ b/example/android/app/src/main/kotlin/com/example/example/Application.kt @@ -1,5 +1,6 @@ package com.example.example +import com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin import com.example.path_provider.PathProviderPlugin import io.flutter.app.FlutterApplication import io.flutter.plugin.common.PluginRegistry @@ -15,6 +16,8 @@ class Application : FlutterApplication(), PluginRegistrantCallback { } override fun registerWith(registry: PluginRegistry?) { + FlutterLocalNotificationsPlugin.registerWith(registry?.registrarFor( + "com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin")) SharedPreferencesPlugin.registerWith(registry?.registrarFor( "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")) FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")) diff --git a/example/lib/main.dart b/example/lib/main.dart index 9d8dc1fa..247ef027 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,35 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +Future _handleBackgroundNotification( + Map notification, +) async { + final notificationData = await NotificationService.storeMessage(notification); + + final androidPlatformChannelSpecifics = AndroidNotificationDetails( + 'Message notifications', + 'Message notifications', + 'Channel dedicated to message notifications', + importance: Importance.Max, + priority: Priority.High, + ); + + final androidNotificationOptions = AndroidNotificationOptions( + androidNotificationDetails: androidPlatformChannelSpecifics, + id: notificationData.message.id.hashCode, + title: + 'CUSTOM ${notificationData.message.user.name} @ ${notificationData.channel.cid}', + body: notificationData.message.text, + ); + + await NotificationService.sendNotification(androidNotificationOptions); +} + void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, + notificationHandler: _handleBackgroundNotification, ); await client.setUser( From 1de4fa37a87a72998418ba90b33d93b15bb5f795 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 25 Mar 2020 13:31:57 +0100 Subject: [PATCH 023/133] WIP add ios custom notification example --- .../kotlin/com/example/example/Application.kt | 1 - example/ios/Notifications/Info.plist | 31 +++ .../Notifications/NotificationService.swift | 36 ++++ .../Notifications/Notifications.entitlements | 8 + example/ios/Podfile.lock | 10 +- example/ios/Runner.xcodeproj/project.pbxproj | 203 +++++++++++++++++- example/ios/Runner/Runner.entitlements | 8 + 7 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 example/ios/Notifications/Info.plist create mode 100644 example/ios/Notifications/NotificationService.swift create mode 100644 example/ios/Notifications/Notifications.entitlements create mode 100644 example/ios/Runner/Runner.entitlements diff --git a/example/android/app/src/main/kotlin/com/example/example/Application.kt b/example/android/app/src/main/kotlin/com/example/example/Application.kt index dfe01b38..50127016 100644 --- a/example/android/app/src/main/kotlin/com/example/example/Application.kt +++ b/example/android/app/src/main/kotlin/com/example/example/Application.kt @@ -1,7 +1,6 @@ package com.example.example import com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin -import com.example.path_provider.PathProviderPlugin import io.flutter.app.FlutterApplication import io.flutter.plugin.common.PluginRegistry import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback diff --git a/example/ios/Notifications/Info.plist b/example/ios/Notifications/Info.plist new file mode 100644 index 00000000..a225b5ca --- /dev/null +++ b/example/ios/Notifications/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Notifications + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/example/ios/Notifications/NotificationService.swift b/example/ios/Notifications/NotificationService.swift new file mode 100644 index 00000000..a11a8574 --- /dev/null +++ b/example/ios/Notifications/NotificationService.swift @@ -0,0 +1,36 @@ +// +// NotificationService.swift +// Notifications +// +// Created by Salvatore Giordano on 25/03/2020. +// Copyright © 2020 The Chromium Authors. All rights reserved. +// + +import UserNotifications + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + defer { + contentHandler(bestAttemptContent ?? request.content) + } + print("DID RECEIVE NOTIFICATION") + // Modify the notification content here... + bestAttemptContent?.title = "\(bestAttemptContent?.title ?? "") [modified]" + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" + contentHandler(bestAttemptContent) + } + } + +} diff --git a/example/ios/Notifications/Notifications.entitlements b/example/ios/Notifications/Notifications.entitlements new file mode 100644 index 00000000..903def2a --- /dev/null +++ b/example/ios/Notifications/Notifications.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 26b542f0..32f5d66d 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -56,6 +56,8 @@ PODS: - Flutter (1.0.0) - flutter_apns (0.0.1): - Flutter + - flutter_local_notifications (0.0.1): + - Flutter - flutter_plugin_android_lifecycle (0.0.1): - Flutter - FMDB (2.7.5): @@ -135,6 +137,7 @@ DEPENDENCIES: - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - Flutter (from `Flutter`) - flutter_apns (from `.symlinks/plugins/flutter_apns/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - keyboard_visibility (from `.symlinks/plugins/keyboard_visibility/ios`) @@ -182,6 +185,8 @@ EXTERNAL SOURCES: :path: Flutter flutter_apns: :path: ".symlinks/plugins/flutter_apns/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_plugin_android_lifecycle: :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" image_picker: @@ -218,7 +223,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245 Firebase: fe7f74012742ab403451dd283e6909b8f1fb348a - firebase_messaging: edab61b94fb3bfb399f32f9d4dedb5196d680084 + firebase_messaging: cffb57ce40958c6204f03fb0c81713e4cd1e240c FirebaseAnalytics: 572e467f3d977825266e8ccd52674aa3e6f47eac FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae FirebaseCore: ed0a24c758a57c2b88c5efa8e6a8195e868af589 @@ -229,6 +234,7 @@ SPEC CHECKSUMS: FirebaseMessaging: 4ec33842d36b3319e062e51fb8b35a74f726950d Flutter: 0e3d915762c693b495b44d77113d4970485de6ec flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f + flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186 flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a GoogleAppMeasurement: c29d405ff76e18551b5d158eaba6753fda8c7542 @@ -253,7 +259,7 @@ SPEC CHECKSUMS: url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c video_player: 69c5f029fac4ffe4fc8a85ea7f7b793709661549 video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 - wakelock: bd3dcc6a8bcf53a1c0309780ff192dcee67c6ccb + wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 308cdaf9..dd0ef5fc 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 0BC14C50242B5A7A0028DE94 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC14C4F242B5A7A0028DE94 /* NotificationService.swift */; }; + 0BC14C54242B5A7A0028DE94 /* Notifications.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0BC14C4D242B5A7A0028DE94 /* Notifications.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; @@ -20,7 +22,28 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 0BC14C52242B5A7A0028DE94 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0BC14C4C242B5A7A0028DE94; + remoteInfo = Notifications; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ + 0BC14C55242B5A7A0028DE94 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 0BC14C54242B5A7A0028DE94 /* Notifications.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -36,6 +59,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0BC14C4D242B5A7A0028DE94 /* Notifications.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Notifications.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 0BC14C4F242B5A7A0028DE94 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 0BC14C51242B5A7A0028DE94 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0BC14C5A242B5ED90028DE94 /* Notifications.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Notifications.entitlements; sourceTree = ""; }; + 0BC14C5B242B5FF50028DE94 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 2452A9E77396497EB4CF3072 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -58,6 +86,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 0BC14C4A242B5A7A0028DE94 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -71,6 +106,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0BC14C4E242B5A7A0028DE94 /* Notifications */ = { + isa = PBXGroup; + children = ( + 0BC14C5A242B5ED90028DE94 /* Notifications.entitlements */, + 0BC14C4F242B5A7A0028DE94 /* NotificationService.swift */, + 0BC14C51242B5A7A0028DE94 /* Info.plist */, + ); + path = Notifications; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -89,6 +134,7 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + 0BC14C4E242B5A7A0028DE94 /* Notifications */, 97C146EF1CF9000F007C117D /* Products */, CF168B61BAB91958681C7C21 /* Pods */, BC09A38346C8B2CD72199469 /* Frameworks */, @@ -99,6 +145,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 0BC14C4D242B5A7A0028DE94 /* Notifications.appex */, ); name = Products; sourceTree = ""; @@ -106,6 +153,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 0BC14C5B242B5FF50028DE94 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -141,13 +189,29 @@ 7BF51EE28C89025F73A5211F /* Pods-Runner.release.xcconfig */, 68F846A6DB42D92393F5F7E0 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 0BC14C4C242B5A7A0028DE94 /* Notifications */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0BC14C59242B5A7A0028DE94 /* Build configuration list for PBXNativeTarget "Notifications" */; + buildPhases = ( + 0BC14C49242B5A7A0028DE94 /* Sources */, + 0BC14C4A242B5A7A0028DE94 /* Frameworks */, + 0BC14C4B242B5A7A0028DE94 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Notifications; + productName = Notifications; + productReference = 0BC14C4D242B5A7A0028DE94 /* Notifications.appex */; + productType = "com.apple.product-type.app-extension"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -160,10 +224,12 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 5702861DACEDB848A3E454E8 /* [CP] Embed Pods Frameworks */, + 0BC14C55242B5A7A0028DE94 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + 0BC14C53242B5A7A0028DE94 /* PBXTargetDependency */, ); name = Runner; productName = Runner; @@ -176,12 +242,20 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1140; LastUpgradeCheck = 1020; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { + 0BC14C4C242B5A7A0028DE94 = { + CreatedOnToolsVersion = 11.4; + DevelopmentTeam = Q63BUTUB2B; + ProvisioningStyle = Automatic; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = Q63BUTUB2B; LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; }; }; }; @@ -199,11 +273,19 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 0BC14C4C242B5A7A0028DE94 /* Notifications */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 0BC14C4B242B5A7A0028DE94 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +368,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 0BC14C49242B5A7A0028DE94 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0BC14C50242B5A7A0028DE94 /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -297,6 +387,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 0BC14C53242B5A7A0028DE94 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0BC14C4C242B5A7A0028DE94 /* Notifications */; + targetProxy = 0BC14C52242B5A7A0028DE94 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -317,6 +415,81 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 0BC14C56242B5A7A0028DE94 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = Q63BUTUB2B; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Notifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.Notifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0BC14C57242B5A7A0028DE94 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = Q63BUTUB2B; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Notifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.Notifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 0BC14C58242B5A7A0028DE94 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = Q63BUTUB2B; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Notifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.Notifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; @@ -372,9 +545,14 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = Q63BUTUB2B; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -388,6 +566,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -506,9 +685,14 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = Q63BUTUB2B; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -522,6 +706,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -533,9 +718,14 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = Q63BUTUB2B; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -549,6 +739,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -558,6 +749,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 0BC14C59242B5A7A0028DE94 /* Build configuration list for PBXNativeTarget "Notifications" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0BC14C56242B5A7A0028DE94 /* Debug */, + 0BC14C57242B5A7A0028DE94 /* Release */, + 0BC14C58242B5A7A0028DE94 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements new file mode 100644 index 00000000..903def2a --- /dev/null +++ b/example/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + From 24e32f891eb214600c2c17cafd456097bce08986 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 27 Mar 2020 11:07:32 +0100 Subject: [PATCH 024/133] add ios notification example --- .../Notifications/NotificationService.swift | 6 +-- .../Notifications/Notifications.entitlements | 5 +-- example/ios/Runner.xcodeproj/project.pbxproj | 37 ++++++++++--------- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++ example/ios/Runner/Info.plist | 26 ++++++------- example/lib/main.dart | 4 +- 6 files changed, 46 insertions(+), 40 deletions(-) create mode 100644 example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/ios/Notifications/NotificationService.swift b/example/ios/Notifications/NotificationService.swift index a11a8574..bb724f71 100644 --- a/example/ios/Notifications/NotificationService.swift +++ b/example/ios/Notifications/NotificationService.swift @@ -19,16 +19,14 @@ class NotificationService: UNNotificationServiceExtension { defer { contentHandler(bestAttemptContent ?? request.content) } - print("DID RECEIVE NOTIFICATION") - // Modify the notification content here... - bestAttemptContent?.title = "\(bestAttemptContent?.title ?? "") [modified]" + // Modify the notification content here... + bestAttemptContent?.title = "[modified] \(bestAttemptContent?.title ?? "")" } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { - bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" contentHandler(bestAttemptContent) } } diff --git a/example/ios/Notifications/Notifications.entitlements b/example/ios/Notifications/Notifications.entitlements index 903def2a..0c67376e 100644 --- a/example/ios/Notifications/Notifications.entitlements +++ b/example/ios/Notifications/Notifications.entitlements @@ -1,8 +1,5 @@ - - aps-environment - development - + diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index dd0ef5fc..477035e4 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -248,12 +248,12 @@ TargetAttributes = { 0BC14C4C242B5A7A0028DE94 = { CreatedOnToolsVersion = 11.4; - DevelopmentTeam = Q63BUTUB2B; + DevelopmentTeam = EHV7XZLAHA; ProvisioningStyle = Automatic; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = Q63BUTUB2B; + DevelopmentTeam = EHV7XZLAHA; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; }; @@ -425,14 +425,15 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = Q63BUTUB2B; + DEVELOPMENT_TEAM = EHV7XZLAHA; + ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Notifications/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.example.example.Notifications; + PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -452,13 +453,14 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = Q63BUTUB2B; + DEVELOPMENT_TEAM = EHV7XZLAHA; + ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Notifications/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.example.example.Notifications; + PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; @@ -476,13 +478,14 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = Q63BUTUB2B; + DEVELOPMENT_TEAM = EHV7XZLAHA; + ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Notifications/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.example.example.Notifications; + PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; @@ -552,7 +555,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = Q63BUTUB2B; + DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -564,7 +567,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -692,7 +695,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = Q63BUTUB2B; + DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -704,7 +707,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -725,7 +728,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = Q63BUTUB2B; + DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -737,7 +740,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index f6d694bd..ea713691 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -22,6 +22,19 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSAppleMusicUsageDescription + Used to send message attachments + NSCameraUsageDescription + Used to send message attachments + NSMicrophoneUsageDescription + Used to send message attachments + NSPhotoLibraryUsageDescription + Used to send message attachments + UIBackgroundModes + + fetch + remote-notification + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,18 +54,5 @@ UIViewControllerBasedStatusBarAppearance - UIBackgroundModes - - fetch - remote-notification - - NSAppleMusicUsageDescription - Used to send message attachments - NSCameraUsageDescription - Used to send message attachments - NSMicrophoneUsageDescription - Used to send message attachments - NSPhotoLibraryUsageDescription - Used to send message attachments diff --git a/example/lib/main.dart b/example/lib/main.dart index 247ef027..4e5f92a2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -33,8 +33,8 @@ void main() async { ); await client.setUser( - User(id: 'super-band-007'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC0wMDcifQ.DlbyHN6nK7jBTJDp6RJOuBJWwnYoy8Zq8208RPL4uLY', + User(id: 'user1'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcjEifQ.NGZPyPMx7KSVisJmh4tJhOIv7ZjCaMQpOh4gTINvCaU', ); runApp(MyApp(client)); From d94371c61b8da06bbbc9786b86d16fd0d4f5dd19 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 30 Mar 2020 17:13:13 +0200 Subject: [PATCH 025/133] update ios notifications example --- .../Notifications/NotificationService.swift | 19 ++++++++++ example/ios/Podfile | 3 +- example/ios/Podfile.lock | 37 ++++++++++++++++++- example/lib/main.dart | 3 +- pubspec.yaml | 4 +- 5 files changed, 61 insertions(+), 5 deletions(-) diff --git a/example/ios/Notifications/NotificationService.swift b/example/ios/Notifications/NotificationService.swift index bb724f71..471d7d59 100644 --- a/example/ios/Notifications/NotificationService.swift +++ b/example/ios/Notifications/NotificationService.swift @@ -7,6 +7,7 @@ // import UserNotifications +import StreamChatCore class NotificationService: UNNotificationServiceExtension { @@ -14,11 +15,29 @@ class NotificationService: UNNotificationServiceExtension { var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + print("DID RECEIVE") + self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) defer { contentHandler(bestAttemptContent ?? request.content) } + + let apiKey = "s2dxdhpxd94g"; + let userId = "user1" + let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcjEifQ.NGZPyPMx7KSVisJmh4tJhOIv7ZjCaMQpOh4gTINvCaU" + + let messageId = bestAttemptContent?.userInfo["message_id"] as! String + + print("REQUEST CONTENT \(messageId)") + + Client.config = .init(apiKey: apiKey, logOptions: .info) + Client.shared.set(user: User(id: userId, name: ""), token: token) + Client.shared.message(with: messageId).subscribe {res in + print(res) + Client.shared.disconnect() + } + // Modify the notification content here... bestAttemptContent?.title = "[modified] \(bestAttemptContent?.title ?? "")" } diff --git a/example/ios/Podfile b/example/ios/Podfile index b30a428b..d07d830a 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -63,6 +63,7 @@ target 'Runner' do # Keep pod path relative so it can be checked into Podfile.lock. pod 'Flutter', :path => 'Flutter' + pod 'StreamChatCore' # Plugin Pods diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 32f5d66d..0babead8 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -91,6 +91,7 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (6.5.2): - GoogleUtilities/Logger + - GzipSwift (5.0.0) - image_picker (0.0.1): - Flutter - keyboard_visibility (0.5.0): @@ -110,6 +111,16 @@ PODS: - PromisesObjC (1.2.8) - Protobuf (3.11.4) - Reachability (3.2) + - ReachabilitySwift (4.3.1) + - RxAppState (1.6.0): + - RxCocoa (~> 5.0) + - RxSwift (~> 5.0) + - RxCocoa (5.1.1): + - RxRelay (~> 5) + - RxSwift (~> 5) + - RxRelay (5.1.1): + - RxSwift (~> 5) + - RxSwift (5.0.1) - shared_preferences (0.0.1): - Flutter - shared_preferences_macos (0.0.1): @@ -119,6 +130,13 @@ PODS: - sqflite (0.0.1): - Flutter - FMDB (~> 2.7.2) + - Starscream (3.1.1) + - StreamChatCore (1.6.1): + - GzipSwift (~> 5.0.0) + - ReachabilitySwift (~> 4.3.0) + - RxAppState (~> 1.6.0) + - RxSwift (~> 5.0.0) + - Starscream (~> 3.1.0) - url_launcher (0.0.1): - Flutter - url_launcher_macos (0.0.1): @@ -148,6 +166,7 @@ DEPENDENCIES: - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) + - StreamChatCore - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) @@ -171,10 +190,18 @@ SPEC REPOS: - GoogleDataTransport - GoogleDataTransportCCTSupport - GoogleUtilities + - GzipSwift - nanopb - PromisesObjC - Protobuf - Reachability + - ReachabilitySwift + - RxAppState + - RxCocoa + - RxRelay + - RxSwift + - Starscream + - StreamChatCore EXTERNAL SOURCES: file_picker: @@ -241,6 +268,7 @@ SPEC CHECKSUMS: GoogleDataTransport: a857c6a002d201b524dd4bc2ed7e7355ed07e785 GoogleDataTransportCCTSupport: 32f75fbe904c82772fcbb6b6bd4525bfb6f2a862 GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e + GzipSwift: 5592f4d62b641e04d06443ba471f8ed76b1363e4 image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f keyboard_visibility: 96a24de806fe6823c3ad956c01ba2ec6d056616f moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6 @@ -250,10 +278,17 @@ SPEC CHECKSUMS: PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 + ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b + RxAppState: b633d7370970ecb80081912ea08a7bf3eb644b4e + RxCocoa: 32065309a38d29b5b0db858819b5bf9ef038b601 + RxRelay: d77f7d771495f43c556cbc43eebd1bb54d01e8e9 + RxSwift: e2dc62b366a3adf6a0be44ba9f405efd4c94e0c4 shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 + Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 + StreamChatCore: f57cbc5bd023f5ab579ed98b9f59e3b7d0b42e99 url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c @@ -261,6 +296,6 @@ SPEC CHECKSUMS: video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 -PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83 +PODFILE CHECKSUM: d834edc7a7591fcbf5e5250663b679410c448672 COCOAPODS: 1.8.4 diff --git a/example/lib/main.dart b/example/lib/main.dart index 4e5f92a2..191dec25 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,7 +4,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; Future _handleBackgroundNotification( Map notification, ) async { - final notificationData = await NotificationService.storeMessage(notification); + final notificationData = + await NotificationService.getAndStoreMessage(notification); final androidPlatformChannelSpecifics = AndroidNotificationDetails( 'Message notifications', diff --git a/pubspec.yaml b/pubspec.yaml index 62b0652b..a38fde74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,8 +17,8 @@ dependencies: url_launcher: ^5.4.2 video_player: ^0.10.8+1 chewie: ^0.9.10 - file_picker: ^1.5.0+2 - image_picker: ^0.6.3+4 + file_picker: ^1.5.1 + image_picker: ^0.6.4 keyboard_visibility: ^0.5.6 stream_chat: path: ../stream_chat_dart From 1c2f8dcc741e96b90393b42aa3ebdef8647607ef Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 31 Mar 2020 10:28:59 +0200 Subject: [PATCH 026/133] merge master --- CHANGELOG.md | 4 + README.md | 6 +- example/ios/Podfile.lock | 301 ------------------------------------- lib/src/message_input.dart | 2 +- pubspec.yaml | 3 +- 5 files changed, 11 insertions(+), 305 deletions(-) delete mode 100644 example/ios/Podfile.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index c2355893..8dfee43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.12 + +- Fix dependency error on iOS using flutter_form_builder + ## 0.1.11 - Fix bug in ChannelPreview when list of messages is empty diff --git a/README.md b/README.md index 912ebe51..7515ab60 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,12 @@ All set ✅ The library uses [flutter file picker plugin](https://github.com/miguelpruivo/flutter_file_picker) to pick files from the os. +Follow [this wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki/Setup#ios) to fulfill iOS requirements. -Follow [this wiki](https://github.com/miguelpruivo/flutter_file_picker) to fulfill iOS requirements. +We also use [video_player](https://pub.dev/packages/video_player) to reproduce videos. Follow [this guide](https://pub.dev/packages/video_player#installation) to fulfill the requirements. + +To pick images from the camera we use the [image_picker](https://pub.dev/packages/image_picker) plugin. +Follow [these instructions](https://pub.dev/packages/image_picker#ios) to check the requirements. ## Docs diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock deleted file mode 100644 index 0babead8..00000000 --- a/example/ios/Podfile.lock +++ /dev/null @@ -1,301 +0,0 @@ -PODS: - - file_picker (0.0.1): - - Flutter - - Firebase/Core (6.20.0): - - Firebase/CoreOnly - - FirebaseAnalytics (= 6.3.1) - - Firebase/CoreOnly (6.20.0): - - FirebaseCore (= 6.6.4) - - Firebase/Messaging (6.20.0): - - Firebase/CoreOnly - - FirebaseMessaging (~> 4.3.0) - - firebase_messaging (0.0.1): - - Firebase/Core - - Firebase/Messaging - - Flutter - - FirebaseAnalytics (6.3.1): - - FirebaseCore (~> 6.6) - - FirebaseInstallations (~> 1.1) - - GoogleAppMeasurement (= 6.3.1) - - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - - GoogleUtilities/MethodSwizzler (~> 6.0) - - GoogleUtilities/Network (~> 6.0) - - "GoogleUtilities/NSData+zlib (~> 6.0)" - - nanopb (= 0.3.9011) - - FirebaseAnalyticsInterop (1.5.0) - - FirebaseCore (6.6.4): - - FirebaseCoreDiagnostics (~> 1.2) - - FirebaseCoreDiagnosticsInterop (~> 1.2) - - GoogleUtilities/Environment (~> 6.5) - - GoogleUtilities/Logger (~> 6.5) - - FirebaseCoreDiagnostics (1.2.2): - - FirebaseCoreDiagnosticsInterop (~> 1.2) - - GoogleDataTransportCCTSupport (~> 2.0) - - GoogleUtilities/Environment (~> 6.5) - - GoogleUtilities/Logger (~> 6.5) - - nanopb (~> 0.3.901) - - FirebaseCoreDiagnosticsInterop (1.2.0) - - FirebaseInstallations (1.1.0): - - FirebaseCore (~> 6.6) - - GoogleUtilities/UserDefaults (~> 6.5) - - PromisesObjC (~> 1.2) - - FirebaseInstanceID (4.3.2): - - FirebaseCore (~> 6.6) - - FirebaseInstallations (~> 1.0) - - GoogleUtilities/Environment (~> 6.5) - - GoogleUtilities/UserDefaults (~> 6.5) - - FirebaseMessaging (4.3.0): - - FirebaseAnalyticsInterop (~> 1.5) - - FirebaseCore (~> 6.6) - - FirebaseInstanceID (~> 4.3) - - GoogleUtilities/AppDelegateSwizzler (~> 6.5) - - GoogleUtilities/Environment (~> 6.5) - - GoogleUtilities/Reachability (~> 6.5) - - GoogleUtilities/UserDefaults (~> 6.5) - - Protobuf (>= 3.9.2, ~> 3.9) - - Flutter (1.0.0) - - flutter_apns (0.0.1): - - Flutter - - flutter_local_notifications (0.0.1): - - Flutter - - flutter_plugin_android_lifecycle (0.0.1): - - Flutter - - FMDB (2.7.5): - - FMDB/standard (= 2.7.5) - - FMDB/standard (2.7.5) - - GoogleAppMeasurement (6.3.1): - - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - - GoogleUtilities/MethodSwizzler (~> 6.0) - - GoogleUtilities/Network (~> 6.0) - - "GoogleUtilities/NSData+zlib (~> 6.0)" - - nanopb (= 0.3.9011) - - GoogleDataTransport (5.0.0) - - GoogleDataTransportCCTSupport (2.0.0): - - GoogleDataTransport (~> 5.0) - - nanopb (~> 0.3.901) - - GoogleUtilities/AppDelegateSwizzler (6.5.2): - - GoogleUtilities/Environment - - GoogleUtilities/Logger - - GoogleUtilities/Network - - GoogleUtilities/Environment (6.5.2) - - GoogleUtilities/Logger (6.5.2): - - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (6.5.2): - - GoogleUtilities/Logger - - GoogleUtilities/Network (6.5.2): - - GoogleUtilities/Logger - - "GoogleUtilities/NSData+zlib" - - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (6.5.2)" - - GoogleUtilities/Reachability (6.5.2): - - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (6.5.2): - - GoogleUtilities/Logger - - GzipSwift (5.0.0) - - image_picker (0.0.1): - - Flutter - - keyboard_visibility (0.5.0): - - Flutter - - Reachability - - moor_ffi (0.0.1): - - Flutter - - nanopb (0.3.9011): - - nanopb/decode (= 0.3.9011) - - nanopb/encode (= 0.3.9011) - - nanopb/decode (0.3.9011) - - nanopb/encode (0.3.9011) - - path_provider (0.0.1): - - Flutter - - path_provider_macos (0.0.1): - - Flutter - - PromisesObjC (1.2.8) - - Protobuf (3.11.4) - - Reachability (3.2) - - ReachabilitySwift (4.3.1) - - RxAppState (1.6.0): - - RxCocoa (~> 5.0) - - RxSwift (~> 5.0) - - RxCocoa (5.1.1): - - RxRelay (~> 5) - - RxSwift (~> 5) - - RxRelay (5.1.1): - - RxSwift (~> 5) - - RxSwift (5.0.1) - - shared_preferences (0.0.1): - - Flutter - - shared_preferences_macos (0.0.1): - - Flutter - - shared_preferences_web (0.0.1): - - Flutter - - sqflite (0.0.1): - - Flutter - - FMDB (~> 2.7.2) - - Starscream (3.1.1) - - StreamChatCore (1.6.1): - - GzipSwift (~> 5.0.0) - - ReachabilitySwift (~> 4.3.0) - - RxAppState (~> 1.6.0) - - RxSwift (~> 5.0.0) - - Starscream (~> 3.1.0) - - url_launcher (0.0.1): - - Flutter - - url_launcher_macos (0.0.1): - - Flutter - - url_launcher_web (0.0.1): - - Flutter - - video_player (0.0.1): - - Flutter - - video_player_web (0.0.1): - - Flutter - - wakelock (0.0.1): - - Flutter - -DEPENDENCIES: - - file_picker (from `.symlinks/plugins/file_picker/ios`) - - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - - Flutter (from `Flutter`) - - flutter_apns (from `.symlinks/plugins/flutter_apns/ios`) - - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) - - image_picker (from `.symlinks/plugins/image_picker/ios`) - - keyboard_visibility (from `.symlinks/plugins/keyboard_visibility/ios`) - - moor_ffi (from `.symlinks/plugins/moor_ffi/ios`) - - path_provider (from `.symlinks/plugins/path_provider/ios`) - - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) - - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) - - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) - - sqflite (from `.symlinks/plugins/sqflite/ios`) - - StreamChatCore - - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) - - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) - - video_player (from `.symlinks/plugins/video_player/ios`) - - video_player_web (from `.symlinks/plugins/video_player_web/ios`) - - wakelock (from `.symlinks/plugins/wakelock/ios`) - -SPEC REPOS: - trunk: - - Firebase - - FirebaseAnalytics - - FirebaseAnalyticsInterop - - FirebaseCore - - FirebaseCoreDiagnostics - - FirebaseCoreDiagnosticsInterop - - FirebaseInstallations - - FirebaseInstanceID - - FirebaseMessaging - - FMDB - - GoogleAppMeasurement - - GoogleDataTransport - - GoogleDataTransportCCTSupport - - GoogleUtilities - - GzipSwift - - nanopb - - PromisesObjC - - Protobuf - - Reachability - - ReachabilitySwift - - RxAppState - - RxCocoa - - RxRelay - - RxSwift - - Starscream - - StreamChatCore - -EXTERNAL SOURCES: - file_picker: - :path: ".symlinks/plugins/file_picker/ios" - firebase_messaging: - :path: ".symlinks/plugins/firebase_messaging/ios" - Flutter: - :path: Flutter - flutter_apns: - :path: ".symlinks/plugins/flutter_apns/ios" - flutter_local_notifications: - :path: ".symlinks/plugins/flutter_local_notifications/ios" - flutter_plugin_android_lifecycle: - :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" - image_picker: - :path: ".symlinks/plugins/image_picker/ios" - keyboard_visibility: - :path: ".symlinks/plugins/keyboard_visibility/ios" - moor_ffi: - :path: ".symlinks/plugins/moor_ffi/ios" - path_provider: - :path: ".symlinks/plugins/path_provider/ios" - path_provider_macos: - :path: ".symlinks/plugins/path_provider_macos/ios" - shared_preferences: - :path: ".symlinks/plugins/shared_preferences/ios" - shared_preferences_macos: - :path: ".symlinks/plugins/shared_preferences_macos/ios" - shared_preferences_web: - :path: ".symlinks/plugins/shared_preferences_web/ios" - sqflite: - :path: ".symlinks/plugins/sqflite/ios" - url_launcher: - :path: ".symlinks/plugins/url_launcher/ios" - url_launcher_macos: - :path: ".symlinks/plugins/url_launcher_macos/ios" - url_launcher_web: - :path: ".symlinks/plugins/url_launcher_web/ios" - video_player: - :path: ".symlinks/plugins/video_player/ios" - video_player_web: - :path: ".symlinks/plugins/video_player_web/ios" - wakelock: - :path: ".symlinks/plugins/wakelock/ios" - -SPEC CHECKSUMS: - file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245 - Firebase: fe7f74012742ab403451dd283e6909b8f1fb348a - firebase_messaging: cffb57ce40958c6204f03fb0c81713e4cd1e240c - FirebaseAnalytics: 572e467f3d977825266e8ccd52674aa3e6f47eac - FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae - FirebaseCore: ed0a24c758a57c2b88c5efa8e6a8195e868af589 - FirebaseCoreDiagnostics: e9b4cd8ba60dee0f2d13347332e4b7898cca5b61 - FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 - FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b - FirebaseInstanceID: 7ee0d6777013bb952f377b41965bf132b6a075be - FirebaseMessaging: 4ec33842d36b3319e062e51fb8b35a74f726950d - Flutter: 0e3d915762c693b495b44d77113d4970485de6ec - flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f - flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186 - flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - GoogleAppMeasurement: c29d405ff76e18551b5d158eaba6753fda8c7542 - GoogleDataTransport: a857c6a002d201b524dd4bc2ed7e7355ed07e785 - GoogleDataTransportCCTSupport: 32f75fbe904c82772fcbb6b6bd4525bfb6f2a862 - GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e - GzipSwift: 5592f4d62b641e04d06443ba471f8ed76b1363e4 - image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f - keyboard_visibility: 96a24de806fe6823c3ad956c01ba2ec6d056616f - moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6 - nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd - path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d - path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 - PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 - Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 - Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 - ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b - RxAppState: b633d7370970ecb80081912ea08a7bf3eb644b4e - RxCocoa: 32065309a38d29b5b0db858819b5bf9ef038b601 - RxRelay: d77f7d771495f43c556cbc43eebd1bb54d01e8e9 - RxSwift: e2dc62b366a3adf6a0be44ba9f405efd4c94e0c4 - shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 - shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 - shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 - sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 - Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 - StreamChatCore: f57cbc5bd023f5ab579ed98b9f59e3b7d0b42e99 - url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 - url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 - url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c - video_player: 69c5f029fac4ffe4fc8a85ea7f7b793709661549 - video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 - wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 - -PODFILE CHECKSUM: d834edc7a7591fcbf5e5250663b679410c448672 - -COCOAPODS: 1.8.4 diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index a95144f4..f0836cdf 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -3,8 +3,8 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:keyboard_visibility/keyboard_visibility.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index a38fde74..93a31671 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.1.11 +version: 0.1.12 environment: sdk: ">=2.3.0 <3.0.0" @@ -19,7 +19,6 @@ dependencies: chewie: ^0.9.10 file_picker: ^1.5.1 image_picker: ^0.6.4 - keyboard_visibility: ^0.5.6 stream_chat: path: ../stream_chat_dart visibility_detector: ^0.1.4 From 8cbf0b2f274a905f3584c269ade52388be522643 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 31 Mar 2020 10:34:16 +0200 Subject: [PATCH 027/133] update dependencies --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index 93a31671..3efd3ef5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: chewie: ^0.9.10 file_picker: ^1.5.1 image_picker: ^0.6.4 + flutter_keyboard_visibility: ^0.7.0 stream_chat: path: ../stream_chat_dart visibility_detector: ^0.1.4 From f25d1cba3de4b18d4a07fa00c5553ee86ef9fa6c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 31 Mar 2020 13:03:53 +0200 Subject: [PATCH 028/133] fix ios notifications --- .../Notifications/NotificationService.swift | 11 +- example/ios/Podfile.lock | 297 ++++++++++++++++++ example/ios/Runner.xcodeproj/project.pbxproj | 2 +- 3 files changed, 304 insertions(+), 6 deletions(-) create mode 100644 example/ios/Podfile.lock diff --git a/example/ios/Notifications/NotificationService.swift b/example/ios/Notifications/NotificationService.swift index 471d7d59..0c5eea7f 100644 --- a/example/ios/Notifications/NotificationService.swift +++ b/example/ios/Notifications/NotificationService.swift @@ -23,17 +23,17 @@ class NotificationService: UNNotificationServiceExtension { contentHandler(bestAttemptContent ?? request.content) } - let apiKey = "s2dxdhpxd94g"; + let apiKey = "s2dxdhpxd94g"; let userId = "user1" let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcjEifQ.NGZPyPMx7KSVisJmh4tJhOIv7ZjCaMQpOh4gTINvCaU" - + let messageId = bestAttemptContent?.userInfo["message_id"] as! String - + print("REQUEST CONTENT \(messageId)") - + Client.config = .init(apiKey: apiKey, logOptions: .info) Client.shared.set(user: User(id: userId, name: ""), token: token) - Client.shared.message(with: messageId).subscribe {res in + Client.shared.message(with: messageId).subscribe { res in print(res) Client.shared.disconnect() } @@ -43,6 +43,7 @@ class NotificationService: UNNotificationServiceExtension { } override func serviceExtensionTimeWillExpire() { + print("serviceExtensionTimeWillExpire") // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock new file mode 100644 index 00000000..c1ed6914 --- /dev/null +++ b/example/ios/Podfile.lock @@ -0,0 +1,297 @@ +PODS: + - file_picker (0.0.1): + - Flutter + - Firebase/Core (6.20.0): + - Firebase/CoreOnly + - FirebaseAnalytics (= 6.3.1) + - Firebase/CoreOnly (6.20.0): + - FirebaseCore (= 6.6.4) + - Firebase/Messaging (6.20.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 4.3.0) + - firebase_messaging (0.0.1): + - Firebase/Core + - Firebase/Messaging + - Flutter + - FirebaseAnalytics (6.3.1): + - FirebaseCore (~> 6.6) + - FirebaseInstallations (~> 1.1) + - GoogleAppMeasurement (= 6.3.1) + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - FirebaseAnalyticsInterop (1.5.0) + - FirebaseCore (6.6.4): + - FirebaseCoreDiagnostics (~> 1.2) + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - FirebaseCoreDiagnostics (1.2.2): + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleDataTransportCCTSupport (~> 2.0) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - nanopb (~> 0.3.901) + - FirebaseCoreDiagnosticsInterop (1.2.0) + - FirebaseInstallations (1.1.0): + - FirebaseCore (~> 6.6) + - GoogleUtilities/UserDefaults (~> 6.5) + - PromisesObjC (~> 1.2) + - FirebaseInstanceID (4.3.2): + - FirebaseCore (~> 6.6) + - FirebaseInstallations (~> 1.0) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - FirebaseMessaging (4.3.0): + - FirebaseAnalyticsInterop (~> 1.5) + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - GoogleUtilities/AppDelegateSwizzler (~> 6.5) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Reachability (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - Protobuf (>= 3.9.2, ~> 3.9) + - Flutter (1.0.0) + - flutter_apns (0.0.1): + - Flutter + - flutter_keyboard_visibility (0.7.0): + - Flutter + - flutter_local_notifications (0.0.1): + - Flutter + - flutter_plugin_android_lifecycle (0.0.1): + - Flutter + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) + - GoogleAppMeasurement (6.3.1): + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - GoogleDataTransport (5.0.0) + - GoogleDataTransportCCTSupport (2.0.0): + - GoogleDataTransport (~> 5.0) + - nanopb (~> 0.3.901) + - GoogleUtilities/AppDelegateSwizzler (6.5.2): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (6.5.2) + - GoogleUtilities/Logger (6.5.2): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (6.5.2): + - GoogleUtilities/Logger + - GoogleUtilities/Network (6.5.2): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (6.5.2)" + - GoogleUtilities/Reachability (6.5.2): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (6.5.2): + - GoogleUtilities/Logger + - GzipSwift (5.0.0) + - image_picker (0.0.1): + - Flutter + - moor_ffi (0.0.1): + - Flutter + - nanopb (0.3.9011): + - nanopb/decode (= 0.3.9011) + - nanopb/encode (= 0.3.9011) + - nanopb/decode (0.3.9011) + - nanopb/encode (0.3.9011) + - path_provider (0.0.1): + - Flutter + - path_provider_macos (0.0.1): + - Flutter + - PromisesObjC (1.2.8) + - Protobuf (3.11.4) + - ReachabilitySwift (4.3.1) + - RxAppState (1.6.0): + - RxCocoa (~> 5.0) + - RxSwift (~> 5.0) + - RxCocoa (5.1.1): + - RxRelay (~> 5) + - RxSwift (~> 5) + - RxRelay (5.1.1): + - RxSwift (~> 5) + - RxSwift (5.0.1) + - shared_preferences (0.0.1): + - Flutter + - shared_preferences_macos (0.0.1): + - Flutter + - shared_preferences_web (0.0.1): + - Flutter + - sqflite (0.0.1): + - Flutter + - FMDB (~> 2.7.2) + - Starscream (3.1.1) + - StreamChatCore (1.6.1): + - GzipSwift (~> 5.0.0) + - ReachabilitySwift (~> 4.3.0) + - RxAppState (~> 1.6.0) + - RxSwift (~> 5.0.0) + - Starscream (~> 3.1.0) + - url_launcher (0.0.1): + - Flutter + - url_launcher_macos (0.0.1): + - Flutter + - url_launcher_web (0.0.1): + - Flutter + - video_player (0.0.1): + - Flutter + - video_player_web (0.0.1): + - Flutter + - wakelock (0.0.1): + - Flutter + +DEPENDENCIES: + - file_picker (from `.symlinks/plugins/file_picker/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) + - Flutter (from `Flutter`) + - flutter_apns (from `.symlinks/plugins/flutter_apns/ios`) + - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) + - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) + - image_picker (from `.symlinks/plugins/image_picker/ios`) + - moor_ffi (from `.symlinks/plugins/moor_ffi/ios`) + - path_provider (from `.symlinks/plugins/path_provider/ios`) + - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) + - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) + - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) + - sqflite (from `.symlinks/plugins/sqflite/ios`) + - StreamChatCore + - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) + - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) + - video_player (from `.symlinks/plugins/video_player/ios`) + - video_player_web (from `.symlinks/plugins/video_player_web/ios`) + - wakelock (from `.symlinks/plugins/wakelock/ios`) + +SPEC REPOS: + trunk: + - Firebase + - FirebaseAnalytics + - FirebaseAnalyticsInterop + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseCoreDiagnosticsInterop + - FirebaseInstallations + - FirebaseInstanceID + - FirebaseMessaging + - FMDB + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleDataTransportCCTSupport + - GoogleUtilities + - GzipSwift + - nanopb + - PromisesObjC + - Protobuf + - ReachabilitySwift + - RxAppState + - RxCocoa + - RxRelay + - RxSwift + - Starscream + - StreamChatCore + +EXTERNAL SOURCES: + file_picker: + :path: ".symlinks/plugins/file_picker/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" + Flutter: + :path: Flutter + flutter_apns: + :path: ".symlinks/plugins/flutter_apns/ios" + flutter_keyboard_visibility: + :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" + flutter_plugin_android_lifecycle: + :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" + image_picker: + :path: ".symlinks/plugins/image_picker/ios" + moor_ffi: + :path: ".symlinks/plugins/moor_ffi/ios" + path_provider: + :path: ".symlinks/plugins/path_provider/ios" + path_provider_macos: + :path: ".symlinks/plugins/path_provider_macos/ios" + shared_preferences: + :path: ".symlinks/plugins/shared_preferences/ios" + shared_preferences_macos: + :path: ".symlinks/plugins/shared_preferences_macos/ios" + shared_preferences_web: + :path: ".symlinks/plugins/shared_preferences_web/ios" + sqflite: + :path: ".symlinks/plugins/sqflite/ios" + url_launcher: + :path: ".symlinks/plugins/url_launcher/ios" + url_launcher_macos: + :path: ".symlinks/plugins/url_launcher_macos/ios" + url_launcher_web: + :path: ".symlinks/plugins/url_launcher_web/ios" + video_player: + :path: ".symlinks/plugins/video_player/ios" + video_player_web: + :path: ".symlinks/plugins/video_player_web/ios" + wakelock: + :path: ".symlinks/plugins/wakelock/ios" + +SPEC CHECKSUMS: + file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245 + Firebase: fe7f74012742ab403451dd283e6909b8f1fb348a + firebase_messaging: cffb57ce40958c6204f03fb0c81713e4cd1e240c + FirebaseAnalytics: 572e467f3d977825266e8ccd52674aa3e6f47eac + FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae + FirebaseCore: ed0a24c758a57c2b88c5efa8e6a8195e868af589 + FirebaseCoreDiagnostics: e9b4cd8ba60dee0f2d13347332e4b7898cca5b61 + FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 + FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b + FirebaseInstanceID: 7ee0d6777013bb952f377b41965bf132b6a075be + FirebaseMessaging: 4ec33842d36b3319e062e51fb8b35a74f726950d + Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f + flutter_keyboard_visibility: 6195387fb6d8f46e5cd6dda4a4154e41f800f545 + flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186 + flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + GoogleAppMeasurement: c29d405ff76e18551b5d158eaba6753fda8c7542 + GoogleDataTransport: a857c6a002d201b524dd4bc2ed7e7355ed07e785 + GoogleDataTransportCCTSupport: 32f75fbe904c82772fcbb6b6bd4525bfb6f2a862 + GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e + GzipSwift: 5592f4d62b641e04d06443ba471f8ed76b1363e4 + image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f + moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6 + nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd + path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d + path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 + PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 + Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 + ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b + RxAppState: b633d7370970ecb80081912ea08a7bf3eb644b4e + RxCocoa: 32065309a38d29b5b0db858819b5bf9ef038b601 + RxRelay: d77f7d771495f43c556cbc43eebd1bb54d01e8e9 + RxSwift: e2dc62b366a3adf6a0be44ba9f405efd4c94e0c4 + shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 + shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 + shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 + sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 + Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 + StreamChatCore: f57cbc5bd023f5ab579ed98b9f59e3b7d0b42e99 + url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 + url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 + url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c + video_player: 69c5f029fac4ffe4fc8a85ea7f7b793709661549 + video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 + wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 + +PODFILE CHECKSUM: d834edc7a7591fcbf5e5250663b679410c448672 + +COCOAPODS: 1.8.4 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 477035e4..d51bae74 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -132,8 +132,8 @@ 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( - 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + 9740EEB11CF90186004384FC /* Flutter */, 0BC14C4E242B5A7A0028DE94 /* Notifications */, 97C146EF1CF9000F007C117D /* Products */, CF168B61BAB91958681C7C21 /* Pods */, From 27c3a7f390ebb6c46257e0371eeb9b3fd6b789f3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 6 Apr 2020 09:38:13 +0200 Subject: [PATCH 029/133] update example with extension --- .../Notifications/NotificationService.swift | 142 +++++++-- .../Notifications/Notifications.entitlements | 7 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 285 ++++++++++++++++++ example/ios/Runner.xcodeproj/project.pbxproj | 3 + example/ios/Runner/AppDelegate.swift | 42 ++- example/ios/Runner/Runner.entitlements | 4 + lib/src/stream_chat.dart | 1 + 8 files changed, 452 insertions(+), 34 deletions(-) create mode 100644 example/ios/Podfile.lock diff --git a/example/ios/Notifications/NotificationService.swift b/example/ios/Notifications/NotificationService.swift index 0c5eea7f..c9d32623 100644 --- a/example/ios/Notifications/NotificationService.swift +++ b/example/ios/Notifications/NotificationService.swift @@ -7,48 +7,140 @@ // import UserNotifications -import StreamChatCore +import StreamChatClient class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? - + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { - print("DID RECEIVE") - self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) - defer { - contentHandler(bestAttemptContent ?? request.content) - } - let apiKey = "s2dxdhpxd94g"; - let userId = "user1" - let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcjEifQ.NGZPyPMx7KSVisJmh4tJhOIv7ZjCaMQpOh4gTINvCaU" - + let sharedDefaults = UserDefaults(suiteName: "group.io.stream.flutter") + let apiKey = sharedDefaults!.string(forKey: "KEY_API_KEY")! + let userId = sharedDefaults!.string(forKey: "KEY_USER_ID")! + let token = sharedDefaults!.string(forKey: "KEY_TOKEN")! + let messageId = bestAttemptContent?.userInfo["message_id"] as! String - - print("REQUEST CONTENT \(messageId)") - - Client.config = .init(apiKey: apiKey, logOptions: .info) - Client.shared.set(user: User(id: userId, name: ""), token: token) - Client.shared.message(with: messageId).subscribe { res in - print(res) - Client.shared.disconnect() + + Client.config = .init(apiKey: apiKey, logOptions: .error) + Client.shared.set(user: User(id: userId), token: token) { res in + if res.isConnected { + Client.shared.message(withId: messageId) { res in + if let message = res.value?.message, + let channel = res.value?.channel { + let messageWrapper = MessageWrapper(id: message.id, channel: ChannelWrapper(id: channel.id, cid: channel.cid, type: channel.type, name: channel.name, imageURL: channel.imageURL, lastMessageDate: channel.lastMessageDate, created: channel.created, deleted: channel.deleted, createdBy: channel.createdBy, config: channel.config, frozen: channel.frozen, extraData: channel.extraData), type: message.type, user: message.user, created: message.created, updated: message.updated, text: message.text, command: message.command, args: message.args, attachments: message.attachments, parentId: message.parentId, showReplyInChannel: message.showReplyInChannel, mentionedUsers: message.mentionedUsers, extraData: message.extraData) + if let encodedData = try? JSONEncoder.stream.encode(messageWrapper) { + let storedMessages = sharedDefaults?.stringArray(forKey: "messageQueue") ?? [] + let encodedString = String(data: encodedData, encoding: .utf8)! + sharedDefaults?.setValue(storedMessages + [encodedString], forKey: "messageQueue") + + // Modify the notification content here... + self.bestAttemptContent?.title = "[modified] \(self.bestAttemptContent?.title ?? "")" + contentHandler(self.bestAttemptContent ?? request.content) + } + Client.shared.disconnect() + } + } + } } - - // Modify the notification content here... - bestAttemptContent?.title = "[modified] \(bestAttemptContent?.title ?? "")" } override func serviceExtensionTimeWillExpire() { - print("serviceExtensionTimeWillExpire") - // Called just before the extension will be terminated by the system. - // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } } +} +public struct MessageWrapper: Encodable { + private enum CodingKeys: String, CodingKey { + case id + case channel + case type + case user + case created = "created_at" + case updated = "updated_at" + case text + case command + case args + case attachments + case parentId = "parent_id" + case showReplyInChannel = "show_in_channel" + case mentionedUsers = "mentioned_users" + } + + /// A message id. + public let id: String + /// The channel cid. + public let channel: ChannelWrapper? + /// A message type (see `MessageType`). + public let type: MessageType + /// A user (see `User`). + public let user: User + /// A created date. + public let created: Date + /// A updated date. + public let updated: Date + /// A text. + public let text: String + /// A used command name. + public let command: String? + /// A used command args. + public let args: String? + /// Attachments (see `Attachment`). + public let attachments: [Attachment] + /// A parent message id. + public let parentId: String? + /// Check if this reply message needs to show in the channel. + public let showReplyInChannel: Bool + /// Mentioned users (see `User`). + public let mentionedUsers: [User] + /// An extra data for the message. + public let extraData: Codable? +} + +public struct ChannelWrapper: Encodable { + /// Coding keys for the encoding. + private enum CodingKeys: String, CodingKey { + case id + case cid + case type + case name + case imageURL = "image" + case members + case lastMessageDate = "last_message_at" + case createdBy = "created_by" + case created = "created_at" + case deleted = "deleted_at" + } + + /// A channel id. + public let id: String + /// A channel type + id. + public let cid: ChannelId + /// A channel type. + public let type: ChannelType + /// A channel name. + public let name: String? + /// An image of the channel. + public let imageURL: URL? + /// The last message date. + public let lastMessageDate: Date? + /// A channel created date. + public let created: Date + /// A channel deleted date. + public let deleted: Date? + /// A creator of the channel. + public let createdBy: User? + /// A config. + public let config: Channel.Config + /// Checks if the channel is frozen. + public let frozen: Bool + /// A list of user ids of the channel members. + public let members = Set() + /// An extra data for the channel. + public let extraData: Codable? } diff --git a/example/ios/Notifications/Notifications.entitlements b/example/ios/Notifications/Notifications.entitlements index 0c67376e..00390120 100644 --- a/example/ios/Notifications/Notifications.entitlements +++ b/example/ios/Notifications/Notifications.entitlements @@ -1,5 +1,10 @@ - + + com.apple.security.application-groups + + group.io.stream.flutter + + diff --git a/example/ios/Podfile b/example/ios/Podfile index d07d830a..d4fe653f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -63,7 +63,7 @@ target 'Runner' do # Keep pod path relative so it can be checked into Podfile.lock. pod 'Flutter', :path => 'Flutter' - pod 'StreamChatCore' + pod 'StreamChatClient', :git => 'https://github.com/GetStream/stream-chat-swift.git', :branch => 'release/2.0' # Plugin Pods diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock new file mode 100644 index 00000000..7a4583ed --- /dev/null +++ b/example/ios/Podfile.lock @@ -0,0 +1,285 @@ +PODS: + - file_picker (0.0.1): + - Flutter + - Firebase/Core (6.20.0): + - Firebase/CoreOnly + - FirebaseAnalytics (= 6.3.1) + - Firebase/CoreOnly (6.20.0): + - FirebaseCore (= 6.6.4) + - Firebase/Messaging (6.20.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 4.3.0) + - firebase_messaging (0.0.1): + - Firebase/Core + - Firebase/Messaging + - Flutter + - FirebaseAnalytics (6.3.1): + - FirebaseCore (~> 6.6) + - FirebaseInstallations (~> 1.1) + - GoogleAppMeasurement (= 6.3.1) + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - FirebaseAnalyticsInterop (1.5.0) + - FirebaseCore (6.6.4): + - FirebaseCoreDiagnostics (~> 1.2) + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - FirebaseCoreDiagnostics (1.2.2): + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleDataTransportCCTSupport (~> 2.0) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - nanopb (~> 0.3.901) + - FirebaseCoreDiagnosticsInterop (1.2.0) + - FirebaseInstallations (1.1.0): + - FirebaseCore (~> 6.6) + - GoogleUtilities/UserDefaults (~> 6.5) + - PromisesObjC (~> 1.2) + - FirebaseInstanceID (4.3.2): + - FirebaseCore (~> 6.6) + - FirebaseInstallations (~> 1.0) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - FirebaseMessaging (4.3.0): + - FirebaseAnalyticsInterop (~> 1.5) + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - GoogleUtilities/AppDelegateSwizzler (~> 6.5) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Reachability (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - Protobuf (>= 3.9.2, ~> 3.9) + - Flutter (1.0.0) + - flutter_apns (0.0.1): + - Flutter + - flutter_keyboard_visibility (0.7.0): + - Flutter + - flutter_local_notifications (0.0.1): + - Flutter + - flutter_plugin_android_lifecycle (0.0.1): + - Flutter + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) + - GoogleAppMeasurement (6.3.1): + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (= 0.3.9011) + - GoogleDataTransport (5.0.0) + - GoogleDataTransportCCTSupport (2.0.0): + - GoogleDataTransport (~> 5.0) + - nanopb (~> 0.3.901) + - GoogleUtilities/AppDelegateSwizzler (6.5.2): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (6.5.2) + - GoogleUtilities/Logger (6.5.2): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (6.5.2): + - GoogleUtilities/Logger + - GoogleUtilities/Network (6.5.2): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (6.5.2)" + - GoogleUtilities/Reachability (6.5.2): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (6.5.2): + - GoogleUtilities/Logger + - GzipSwift (5.0.0) + - image_picker (0.0.1): + - Flutter + - moor_ffi (0.0.1): + - Flutter + - nanopb (0.3.9011): + - nanopb/decode (= 0.3.9011) + - nanopb/encode (= 0.3.9011) + - nanopb/decode (0.3.9011) + - nanopb/encode (0.3.9011) + - path_provider (0.0.1): + - Flutter + - path_provider_macos (0.0.1): + - Flutter + - PromisesObjC (1.2.8) + - Protobuf (3.11.4) + - ReachabilitySwift (4.3.1) + - shared_preferences (0.0.1): + - Flutter + - shared_preferences_macos (0.0.1): + - Flutter + - shared_preferences_web (0.0.1): + - Flutter + - sqflite (0.0.1): + - Flutter + - FMDB (~> 2.7.2) + - Starscream (3.1.1) + - StreamChatClient (2.0.0): + - GzipSwift (~> 5.0.0) + - ReachabilitySwift (~> 4.3.0) + - Starscream (~> 3.1.0) + - url_launcher (0.0.1): + - Flutter + - url_launcher_macos (0.0.1): + - Flutter + - url_launcher_web (0.0.1): + - Flutter + - video_player (0.0.1): + - Flutter + - video_player_web (0.0.1): + - Flutter + - wakelock (0.0.1): + - Flutter + +DEPENDENCIES: + - file_picker (from `.symlinks/plugins/file_picker/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) + - Flutter (from `Flutter`) + - flutter_apns (from `.symlinks/plugins/flutter_apns/ios`) + - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) + - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) + - image_picker (from `.symlinks/plugins/image_picker/ios`) + - moor_ffi (from `.symlinks/plugins/moor_ffi/ios`) + - path_provider (from `.symlinks/plugins/path_provider/ios`) + - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) + - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) + - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) + - sqflite (from `.symlinks/plugins/sqflite/ios`) + - StreamChatClient (from `https://github.com/GetStream/stream-chat-swift.git`, branch `release/2.0`) + - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) + - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) + - video_player (from `.symlinks/plugins/video_player/ios`) + - video_player_web (from `.symlinks/plugins/video_player_web/ios`) + - wakelock (from `.symlinks/plugins/wakelock/ios`) + +SPEC REPOS: + trunk: + - Firebase + - FirebaseAnalytics + - FirebaseAnalyticsInterop + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseCoreDiagnosticsInterop + - FirebaseInstallations + - FirebaseInstanceID + - FirebaseMessaging + - FMDB + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleDataTransportCCTSupport + - GoogleUtilities + - GzipSwift + - nanopb + - PromisesObjC + - Protobuf + - ReachabilitySwift + - Starscream + +EXTERNAL SOURCES: + file_picker: + :path: ".symlinks/plugins/file_picker/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" + Flutter: + :path: Flutter + flutter_apns: + :path: ".symlinks/plugins/flutter_apns/ios" + flutter_keyboard_visibility: + :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" + flutter_plugin_android_lifecycle: + :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" + image_picker: + :path: ".symlinks/plugins/image_picker/ios" + moor_ffi: + :path: ".symlinks/plugins/moor_ffi/ios" + path_provider: + :path: ".symlinks/plugins/path_provider/ios" + path_provider_macos: + :path: ".symlinks/plugins/path_provider_macos/ios" + shared_preferences: + :path: ".symlinks/plugins/shared_preferences/ios" + shared_preferences_macos: + :path: ".symlinks/plugins/shared_preferences_macos/ios" + shared_preferences_web: + :path: ".symlinks/plugins/shared_preferences_web/ios" + sqflite: + :path: ".symlinks/plugins/sqflite/ios" + StreamChatClient: + :branch: release/2.0 + :git: https://github.com/GetStream/stream-chat-swift.git + url_launcher: + :path: ".symlinks/plugins/url_launcher/ios" + url_launcher_macos: + :path: ".symlinks/plugins/url_launcher_macos/ios" + url_launcher_web: + :path: ".symlinks/plugins/url_launcher_web/ios" + video_player: + :path: ".symlinks/plugins/video_player/ios" + video_player_web: + :path: ".symlinks/plugins/video_player_web/ios" + wakelock: + :path: ".symlinks/plugins/wakelock/ios" + +CHECKOUT OPTIONS: + StreamChatClient: + :commit: 941fed4d692712fe32a8c3fa7a3acc01a4b6f60e + :git: https://github.com/GetStream/stream-chat-swift.git + +SPEC CHECKSUMS: + file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245 + Firebase: fe7f74012742ab403451dd283e6909b8f1fb348a + firebase_messaging: cffb57ce40958c6204f03fb0c81713e4cd1e240c + FirebaseAnalytics: 572e467f3d977825266e8ccd52674aa3e6f47eac + FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae + FirebaseCore: ed0a24c758a57c2b88c5efa8e6a8195e868af589 + FirebaseCoreDiagnostics: e9b4cd8ba60dee0f2d13347332e4b7898cca5b61 + FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 + FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b + FirebaseInstanceID: 7ee0d6777013bb952f377b41965bf132b6a075be + FirebaseMessaging: 4ec33842d36b3319e062e51fb8b35a74f726950d + Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f + flutter_keyboard_visibility: 6195387fb6d8f46e5cd6dda4a4154e41f800f545 + flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186 + flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + GoogleAppMeasurement: c29d405ff76e18551b5d158eaba6753fda8c7542 + GoogleDataTransport: a857c6a002d201b524dd4bc2ed7e7355ed07e785 + GoogleDataTransportCCTSupport: 32f75fbe904c82772fcbb6b6bd4525bfb6f2a862 + GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e + GzipSwift: 5592f4d62b641e04d06443ba471f8ed76b1363e4 + image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f + moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6 + nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd + path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d + path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 + PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 + Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 + ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b + shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 + shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 + shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 + sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 + Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 + StreamChatClient: a5b5a85b0bcccf3ccb26a6847f110912a8c05e92 + url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 + url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 + url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c + video_player: 69c5f029fac4ffe4fc8a85ea7f7b793709661549 + video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 + wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 + +PODFILE CHECKSUM: fc856097c8855a277ba9200358b7361bf84ee637 + +COCOAPODS: 1.8.4 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index d51bae74..54ee7f7f 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -562,6 +562,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -702,6 +703,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -735,6 +737,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4a..fd5f82d2 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -3,11 +3,39 @@ import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + let sharedDefaults = UserDefaults(suiteName: "group.io.stream.flutter") + + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + if let jsonMessage = sharedDefaults?.stringArray(forKey: "messageQueue") { + UserDefaults.standard.setValue(jsonMessage, forKey: "flutter.messageQueue") + sharedDefaults?.removeObject(forKey: "messageQueue") + } + + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func applicationDidEnterBackground(_ application: UIApplication) { + if let apiKey = UserDefaults.standard.string(forKey: "flutter.KEY_API_KEY") { + sharedDefaults?.setValue(apiKey, forKey: "KEY_API_KEY") + } + + if let token = UserDefaults.standard.string(forKey: "flutter.KEY_TOKEN") { + sharedDefaults?.setValue(token, forKey: "KEY_TOKEN") + } + + if let userId = UserDefaults.standard.string(forKey: "flutter.KEY_USER_ID") { + sharedDefaults?.setValue(userId, forKey: "KEY_USER_ID") + } + } + + override func applicationWillEnterForeground(_ application: UIApplication) { + if let jsonMessage = sharedDefaults?.stringArray(forKey: "messageQueue") { + UserDefaults.standard.setValue(jsonMessage, forKey: "flutter.messageQueue") + sharedDefaults?.removeObject(forKey: "messageQueue") + } + } } diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements index 903def2a..967ba7f2 100644 --- a/example/ios/Runner/Runner.entitlements +++ b/example/ios/Runner/Runner.entitlements @@ -4,5 +4,9 @@ aps-environment development + com.apple.security.application-groups + + group.io.stream.flutter + diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 038aefe4..b1c467ba 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -229,6 +229,7 @@ class StreamChatState extends State client.disconnect(); } else if (state == AppLifecycleState.resumed) { if (client.wsConnectionStatus.value == ConnectionStatus.disconnected) { + NotificationService.handleIosMessageQueue(client); client.connect(); } } From fe7219a3c174caff89797ae270f2ee5eb5c6b078 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 6 Apr 2020 11:21:46 +0200 Subject: [PATCH 030/133] fix android background push --- .../kotlin/com/example/example/Application.kt | 7 ++++-- lib/src/channel_list_view.dart | 24 ++++++++++++++++++- lib/src/stream_chat.dart | 2 ++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/example/android/app/src/main/kotlin/com/example/example/Application.kt b/example/android/app/src/main/kotlin/com/example/example/Application.kt index 50127016..dd47a03c 100644 --- a/example/android/app/src/main/kotlin/com/example/example/Application.kt +++ b/example/android/app/src/main/kotlin/com/example/example/Application.kt @@ -7,6 +7,7 @@ import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin +import io.flutter.plugins.pathprovider.PathProviderPlugin class Application : FlutterApplication(), PluginRegistrantCallback { override fun onCreate() { @@ -15,10 +16,12 @@ class Application : FlutterApplication(), PluginRegistrantCallback { } override fun registerWith(registry: PluginRegistry?) { - FlutterLocalNotificationsPlugin.registerWith(registry?.registrarFor( - "com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin")) + PathProviderPlugin.registerWith(registry?.registrarFor( + "io.flutter.plugins.pathprovider.PathProviderPlugin")) SharedPreferencesPlugin.registerWith(registry?.registrarFor( "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")) + FlutterLocalNotificationsPlugin.registerWith(registry?.registrarFor( + "com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin")) FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")) } } \ No newline at end of file diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 14b68bf4..8bcaaa96 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -98,7 +98,8 @@ class ChannelListView extends StatefulWidget { _ChannelListViewState createState() => _ChannelListViewState(); } -class _ChannelListViewState extends State { +class _ChannelListViewState extends State + with WidgetsBindingObserver { final ScrollController _scrollController = ScrollController(); @override @@ -334,6 +335,8 @@ class _ChannelListViewState extends State { void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); + final streamChat = StreamChat.of(context); streamChat.queryChannels( filter: widget.filter, @@ -346,4 +349,23 @@ class _ChannelListViewState extends State { _listenChannelPagination(streamChat); }); } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + StreamChat.of(context).queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + onlyOffline: true, + ); + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } } diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index b1c467ba..76576273 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -198,6 +198,7 @@ class StreamChatState extends State List sortOptions, PaginationParams paginationParams, Map options, + bool onlyOffline = false, }) async { if (_queryChannelsLoadingController.value == true) { return; @@ -210,6 +211,7 @@ class StreamChatState extends State sort: sortOptions, options: options, paginationParams: paginationParams, + onlyOffline: onlyOffline, ); _queryChannelsLoadingController.sink.add(false); } catch (err, stackTrace) { From e35d0bc9f5458c2772f9dc547d4471531a37c791 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 6 Apr 2020 16:11:53 +0200 Subject: [PATCH 031/133] return a stream from querychannels and add channelsBloc to handle channelListview --- example/lib/main.dart | 33 ++++++++++------ lib/src/channel_list_view.dart | 31 +++++++++------ lib/src/channels_bloc.dart | 72 ++++++++++++++++++++++++++++++++++ lib/src/stream_chat.dart | 51 +----------------------- pubspec.yaml | 2 +- 5 files changed, 115 insertions(+), 74 deletions(-) create mode 100644 lib/src/channels_bloc.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 8182be2e..a4b343fe 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -63,17 +63,28 @@ 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(), + body: PageView( + children: [ + ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + ), + ChannelListView( + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + ), + ], ), ); } diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 8bcaaa96..13a43fd7 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/channels_bloc.dart'; import '../stream_chat_flutter.dart'; import 'channel_preview.dart'; @@ -99,15 +100,16 @@ class ChannelListView extends StatefulWidget { } class _ChannelListViewState extends State - with WidgetsBindingObserver { + with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { final ScrollController _scrollController = ScrollController(); + ChannelsBloc channelsBloc; @override Widget build(BuildContext context) { - final streamChat = StreamChat.of(context); + super.build(context); return RefreshIndicator( onRefresh: () async { - return streamChat.queryChannels( + return channelsBloc.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -115,7 +117,7 @@ class _ChannelListViewState extends State ); }, child: StreamBuilder>( - stream: streamChat.channelsStream, + stream: channelsBloc.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { if (snapshot.error is Error) { @@ -163,7 +165,7 @@ class _ChannelListViewState extends State ), FlatButton( onPressed: () { - streamChat.queryChannels( + channelsBloc.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -215,7 +217,7 @@ class _ChannelListViewState extends State if (i < channels.length) { final channel = channels[i]; - final channelClient = streamChat.channels.firstWhere( + final channelClient = channelsBloc.channels.firstWhere( (c) => c.cid == channel.cid, orElse: () => null, ); @@ -285,7 +287,7 @@ class _ChannelListViewState extends State Widget _buildQueryProgressIndicator(context, StreamChatState streamChat) { return StreamBuilder( - stream: streamChat.queryChannelsLoading, + stream: channelsBloc.queryChannelsLoading, initialData: false, builder: (context, snapshot) { if (snapshot.hasError) { @@ -320,11 +322,11 @@ class _ChannelListViewState extends State void _listenChannelPagination(StreamChatState streamChat) { if (_scrollController.position.maxScrollExtent == _scrollController.offset) { - streamChat.queryChannels( + channelsBloc.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination.copyWith( - offset: streamChat.channels.length, + offset: channelsBloc.channels.length, ), options: widget.options, ); @@ -334,11 +336,12 @@ class _ChannelListViewState extends State @override void initState() { super.initState(); + final streamChat = StreamChat.of(context); + channelsBloc = ChannelsBloc(streamChat.client); WidgetsBinding.instance.addObserver(this); - final streamChat = StreamChat.of(context); - streamChat.queryChannels( + channelsBloc.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -353,7 +356,7 @@ class _ChannelListViewState extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - StreamChat.of(context).queryChannels( + channelsBloc.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -366,6 +369,10 @@ class _ChannelListViewState extends State @override void dispose() { WidgetsBinding.instance.removeObserver(this); + channelsBloc.dispose(); super.dispose(); } + + @override + bool get wantKeepAlive => true; } diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart new file mode 100644 index 00000000..b9620834 --- /dev/null +++ b/lib/src/channels_bloc.dart @@ -0,0 +1,72 @@ +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat/stream_chat.dart'; + +class ChannelsBloc { + final Client client; + + ChannelsBloc(this.client); + + /// The current channel list + List get channels => _channelsController.value; + + /// The current channel list as a stream + Stream> get channelsStream => _channelsController.stream; + + final BehaviorSubject _queryChannelsLoadingController = + BehaviorSubject.seeded(false); + + final BehaviorSubject> _channelsController = + BehaviorSubject.seeded([]); + + /// The stream notifying the state of queryChannel call + Stream get queryChannelsLoading => + _queryChannelsLoadingController.stream; + + /// Calls [client.queryChannels] updating [queryChannelsLoading] stream + Future queryChannels({ + Map filter, + List sortOptions, + PaginationParams paginationParams, + Map options, + bool onlyOffline = false, + }) async { + if (_queryChannelsLoadingController.value == true) { + return; + } + _queryChannelsLoadingController.sink.add(true); + + try { + final clear = paginationParams == null || + paginationParams.offset == null || + paginationParams.offset == 0; + final oldChannels = List.from(channels); + client + .queryChannels( + filter: filter, + sort: sortOptions, + options: options, + paginationParams: paginationParams, + onlyOffline: onlyOffline, + ) + .listen((channels) { + if (clear) { + _channelsController.add(channels); + } else { + final l = oldChannels + channels; + _channelsController.add(l); + } + }, onDone: () { + _queryChannelsLoadingController.sink.add(false); + }, onError: (err, stackTrace) { + _queryChannelsLoadingController.addError(err, stackTrace); + }); + } catch (err, stackTrace) { + _queryChannelsLoadingController.addError(err, stackTrace); + } + } + + void dispose() { + _channelsController.close(); + _queryChannelsLoadingController.close(); + } +} diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 76576273..915562f8 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; @@ -59,15 +58,12 @@ class StreamChat extends StatefulWidget { } } -class StreamChatState extends State - with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { - final List _subscriptions = []; +class StreamChatState extends State with WidgetsBindingObserver { Client get client => widget.client; final GlobalKey _navigatorKey = GlobalKey(); @override Widget build(BuildContext context) { - super.build(context); final theme = _getTheme(context, widget.streamChatThemeData); return StreamChatTheme( data: theme, @@ -179,46 +175,6 @@ class StreamChatState extends State /// The current user as a stream Stream get userStream => widget.client.state.userStream; - /// The current channel list - List get channels => client.state.channels; - - /// The current channel list as a stream - Stream> get channelsStream => client.state.channelsStream; - - final BehaviorSubject _queryChannelsLoadingController = - BehaviorSubject.seeded(false); - - /// The stream notifying the state of queryChannel call - Stream get queryChannelsLoading => - _queryChannelsLoadingController.stream; - - /// Calls [client.queryChannels] updating [queryChannelsLoading] stream - Future queryChannels({ - Map filter, - List sortOptions, - PaginationParams paginationParams, - Map options, - bool onlyOffline = false, - }) async { - if (_queryChannelsLoadingController.value == true) { - return; - } - _queryChannelsLoadingController.sink.add(true); - - try { - await widget.client.queryChannels( - filter: filter, - sort: sortOptions, - options: options, - paginationParams: paginationParams, - onlyOffline: onlyOffline, - ); - _queryChannelsLoadingController.sink.add(false); - } catch (err, stackTrace) { - _queryChannelsLoadingController.addError(err, stackTrace); - } - } - @override void initState() { super.initState(); @@ -239,12 +195,7 @@ class StreamChatState extends State @override void dispose() { - _subscriptions.forEach((s) => s.cancel()); - _queryChannelsLoadingController.close(); WidgetsBinding.instance.removeObserver(this); super.dispose(); } - - @override - bool get wantKeepAlive => true; } diff --git a/pubspec.yaml b/pubspec.yaml index 0207b7a6..765ce1ff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: url_launcher: ^5.4.2 video_player: ^0.10.8+1 chewie: ^0.9.10 - file_picker: ^1.5.1 + file_picker: ^1.6.0 image_picker: ^0.6.4 flutter_keyboard_visibility: ^0.7.0 stream_chat: From 713a3dd07d18c6bb93edb154bacece817ec419cc Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 6 Apr 2020 16:33:22 +0200 Subject: [PATCH 032/133] listen for new messages in channelbloc --- lib/src/channels_bloc.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart index b9620834..0ced2b82 100644 --- a/lib/src/channels_bloc.dart +++ b/lib/src/channels_bloc.dart @@ -4,7 +4,17 @@ import 'package:stream_chat/stream_chat.dart'; class ChannelsBloc { final Client client; - ChannelsBloc(this.client); + ChannelsBloc(this.client) { + client.on(EventType.messageNew).listen((e) { + final newChannels = List.from(channels ?? []); + final index = newChannels.indexWhere((c) => c.cid == e.cid); + if (index > 0) { + final channel = newChannels.removeAt(index); + newChannels.insert(0, channel); + _channelsController.add(newChannels); + } + }); + } /// The current channel list List get channels => _channelsController.value; @@ -58,6 +68,8 @@ class ChannelsBloc { }, onDone: () { _queryChannelsLoadingController.sink.add(false); }, onError: (err, stackTrace) { + print(err); + print(stackTrace); _queryChannelsLoadingController.addError(err, stackTrace); }); } catch (err, stackTrace) { From fa2be8397dc614bc84f3ae0545e39f4a5e665fe4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 6 Apr 2020 16:46:00 +0200 Subject: [PATCH 033/133] use a map for channels in client --- lib/src/channels_bloc.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart index 0ced2b82..fa111710 100644 --- a/lib/src/channels_bloc.dart +++ b/lib/src/channels_bloc.dart @@ -1,11 +1,14 @@ +import 'dart:async'; + import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; class ChannelsBloc { final Client client; + StreamSubscription _newMessagesSubscription; ChannelsBloc(this.client) { - client.on(EventType.messageNew).listen((e) { + _newMessagesSubscription = client.on(EventType.messageNew).listen((e) { final newChannels = List.from(channels ?? []); final index = newChannels.indexWhere((c) => c.cid == e.cid); if (index > 0) { @@ -80,5 +83,6 @@ class ChannelsBloc { void dispose() { _channelsController.close(); _queryChannelsLoadingController.close(); + _newMessagesSubscription.cancel(); } } From dc90e3874322530dec406f2ffb8ee3f8f12d6d6b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 7 Apr 2020 10:14:00 +0200 Subject: [PATCH 034/133] remove channels bloc --- example/lib/main.dart | 47 +++++++++++------- lib/src/channel_list_view.dart | 28 +++++------ lib/src/channels_bloc.dart | 88 --------------------------------- lib/src/stream_chat.dart | 90 +++++++++++++++++++++++++++++++++- 4 files changed, 130 insertions(+), 123 deletions(-) delete mode 100644 lib/src/channels_bloc.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index a4b343fe..38de0083 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -50,39 +50,48 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: Container( - child: StreamChat( - client: client, - child: ChannelListPage(), - ), + child: ChannelListPage(client), ), ); } } class ChannelListPage extends StatelessWidget { + final Client client; + + ChannelListPage(this.client); + @override Widget build(BuildContext context) { return Scaffold( body: PageView( children: [ - ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, - sort: [SortOption('last_message_at')], - pagination: PaginationParams( - limit: 20, + StreamChat( + client: client, + child: Builder( + builder: (context) => ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + ), ), - channelWidget: ChannelPage(), ), - ChannelListView( - sort: [SortOption('last_message_at')], - pagination: PaginationParams( - limit: 20, + StreamChat( + client: client, + child: ChannelListView( + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), ), - channelWidget: ChannelPage(), ), ], ), diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 13a43fd7..580d4d84 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/channels_bloc.dart'; import '../stream_chat_flutter.dart'; import 'channel_preview.dart'; @@ -100,16 +99,16 @@ class ChannelListView extends StatefulWidget { } class _ChannelListViewState extends State - with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { + with WidgetsBindingObserver { final ScrollController _scrollController = ScrollController(); - ChannelsBloc channelsBloc; @override Widget build(BuildContext context) { - super.build(context); + final streamChat = StreamChat.of(context); + return RefreshIndicator( onRefresh: () async { - return channelsBloc.queryChannels( + return streamChat.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -117,7 +116,7 @@ class _ChannelListViewState extends State ); }, child: StreamBuilder>( - stream: channelsBloc.channelsStream, + stream: streamChat.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { if (snapshot.error is Error) { @@ -165,7 +164,7 @@ class _ChannelListViewState extends State ), FlatButton( onPressed: () { - channelsBloc.queryChannels( + streamChat.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -217,7 +216,7 @@ class _ChannelListViewState extends State if (i < channels.length) { final channel = channels[i]; - final channelClient = channelsBloc.channels.firstWhere( + final channelClient = streamChat.channels.firstWhere( (c) => c.cid == channel.cid, orElse: () => null, ); @@ -287,7 +286,7 @@ class _ChannelListViewState extends State Widget _buildQueryProgressIndicator(context, StreamChatState streamChat) { return StreamBuilder( - stream: channelsBloc.queryChannelsLoading, + stream: streamChat.queryChannelsLoading, initialData: false, builder: (context, snapshot) { if (snapshot.hasError) { @@ -322,11 +321,11 @@ class _ChannelListViewState extends State void _listenChannelPagination(StreamChatState streamChat) { if (_scrollController.position.maxScrollExtent == _scrollController.offset) { - channelsBloc.queryChannels( + streamChat.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination.copyWith( - offset: channelsBloc.channels.length, + offset: streamChat.channels.length, ), options: widget.options, ); @@ -336,12 +335,12 @@ class _ChannelListViewState extends State @override void initState() { super.initState(); + final streamChat = StreamChat.of(context); - channelsBloc = ChannelsBloc(streamChat.client); WidgetsBinding.instance.addObserver(this); - channelsBloc.queryChannels( + streamChat.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -356,7 +355,7 @@ class _ChannelListViewState extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - channelsBloc.queryChannels( + StreamChat.of(context).queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -369,7 +368,6 @@ class _ChannelListViewState extends State @override void dispose() { WidgetsBinding.instance.removeObserver(this); - channelsBloc.dispose(); super.dispose(); } diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart deleted file mode 100644 index fa111710..00000000 --- a/lib/src/channels_bloc.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/stream_chat.dart'; - -class ChannelsBloc { - final Client client; - StreamSubscription _newMessagesSubscription; - - ChannelsBloc(this.client) { - _newMessagesSubscription = client.on(EventType.messageNew).listen((e) { - final newChannels = List.from(channels ?? []); - final index = newChannels.indexWhere((c) => c.cid == e.cid); - if (index > 0) { - final channel = newChannels.removeAt(index); - newChannels.insert(0, channel); - _channelsController.add(newChannels); - } - }); - } - - /// The current channel list - List get channels => _channelsController.value; - - /// The current channel list as a stream - Stream> get channelsStream => _channelsController.stream; - - final BehaviorSubject _queryChannelsLoadingController = - BehaviorSubject.seeded(false); - - final BehaviorSubject> _channelsController = - BehaviorSubject.seeded([]); - - /// The stream notifying the state of queryChannel call - Stream get queryChannelsLoading => - _queryChannelsLoadingController.stream; - - /// Calls [client.queryChannels] updating [queryChannelsLoading] stream - Future queryChannels({ - Map filter, - List sortOptions, - PaginationParams paginationParams, - Map options, - bool onlyOffline = false, - }) async { - if (_queryChannelsLoadingController.value == true) { - return; - } - _queryChannelsLoadingController.sink.add(true); - - try { - final clear = paginationParams == null || - paginationParams.offset == null || - paginationParams.offset == 0; - final oldChannels = List.from(channels); - client - .queryChannels( - filter: filter, - sort: sortOptions, - options: options, - paginationParams: paginationParams, - onlyOffline: onlyOffline, - ) - .listen((channels) { - if (clear) { - _channelsController.add(channels); - } else { - final l = oldChannels + channels; - _channelsController.add(l); - } - }, onDone: () { - _queryChannelsLoadingController.sink.add(false); - }, onError: (err, stackTrace) { - print(err); - print(stackTrace); - _queryChannelsLoadingController.addError(err, stackTrace); - }); - } catch (err, stackTrace) { - _queryChannelsLoadingController.addError(err, stackTrace); - } - } - - void dispose() { - _channelsController.close(); - _queryChannelsLoadingController.close(); - _newMessagesSubscription.cancel(); - } -} diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 915562f8..24412d52 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; @@ -58,12 +59,15 @@ class StreamChat extends StatefulWidget { } } -class StreamChatState extends State with WidgetsBindingObserver { +class StreamChatState extends State + with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { Client get client => widget.client; final GlobalKey _navigatorKey = GlobalKey(); @override Widget build(BuildContext context) { + super.build(context); + final theme = _getTheme(context, widget.streamChatThemeData); return StreamChatTheme( data: theme, @@ -175,9 +179,83 @@ class StreamChatState extends State with WidgetsBindingObserver { /// The current user as a stream Stream get userStream => widget.client.state.userStream; + /// The current channel list + List get channels => _channelsController.value; + + /// The current channel list as a stream + Stream> get channelsStream => _channelsController.stream; + + final BehaviorSubject _queryChannelsLoadingController = + BehaviorSubject.seeded(false); + + final BehaviorSubject> _channelsController = + BehaviorSubject.seeded([]); + + /// The stream notifying the state of queryChannel call + Stream get queryChannelsLoading => + _queryChannelsLoadingController.stream; + + /// Calls [client.queryChannels] updating [queryChannelsLoading] stream + Future queryChannels({ + Map filter, + List sortOptions, + PaginationParams paginationParams, + Map options, + bool onlyOffline = false, + }) async { + if (_queryChannelsLoadingController.value == true) { + return; + } + _queryChannelsLoadingController.sink.add(true); + + try { + final clear = paginationParams == null || + paginationParams.offset == null || + paginationParams.offset == 0; + final oldChannels = List.from(channels); + client + .queryChannels( + filter: filter, + sort: sortOptions, + options: options, + paginationParams: paginationParams, + onlyOffline: onlyOffline, + ) + .listen((channels) { + if (clear) { + _channelsController.add(channels); + } else { + final l = oldChannels + channels; + _channelsController.add(l); + } + }, onDone: () { + _queryChannelsLoadingController.sink.add(false); + }, onError: (err, stackTrace) { + print(err); + print(stackTrace); + _queryChannelsLoadingController.addError(err, stackTrace); + }); + } catch (err, stackTrace) { + _queryChannelsLoadingController.addError(err, stackTrace); + } + } + + StreamSubscription _newMessagesSubscription; + @override void initState() { super.initState(); + + _newMessagesSubscription = client.on(EventType.messageNew).listen((e) { + final newChannels = List.from(channels ?? []); + final index = newChannels.indexWhere((c) => c.cid == e.cid); + if (index > 0) { + final channel = newChannels.removeAt(index); + newChannels.insert(0, channel); + _channelsController.add(newChannels); + } + }); + WidgetsBinding.instance.addObserver(this); } @@ -196,6 +274,16 @@ class StreamChatState extends State with WidgetsBindingObserver { @override void dispose() { WidgetsBinding.instance.removeObserver(this); + + print('disposeeeeeeeeee'); + + _channelsController.close(); + _queryChannelsLoadingController.close(); + _newMessagesSubscription.cancel(); + super.dispose(); } + + @override + bool get wantKeepAlive => true; } From fafafb8c44b7fda14fb4995e854c2d9590b49f68 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 7 Apr 2020 12:49:31 +0200 Subject: [PATCH 035/133] refactor ios --- .../Notifications/NotificationService.swift | 84 +++++++++++++------ example/ios/Runner/AppDelegate.swift | 8 +- lib/src/stream_chat.dart | 2 - 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/example/ios/Notifications/NotificationService.swift b/example/ios/Notifications/NotificationService.swift index c9d32623..e7418cdb 100644 --- a/example/ios/Notifications/NotificationService.swift +++ b/example/ios/Notifications/NotificationService.swift @@ -9,8 +9,8 @@ import UserNotifications import StreamChatClient -class NotificationService: UNNotificationServiceExtension { - +final class NotificationService: UNNotificationServiceExtension { + var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? @@ -18,31 +18,34 @@ class NotificationService: UNNotificationServiceExtension { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) - let sharedDefaults = UserDefaults(suiteName: "group.io.stream.flutter") - let apiKey = sharedDefaults!.string(forKey: "KEY_API_KEY")! - let userId = sharedDefaults!.string(forKey: "KEY_USER_ID")! - let token = sharedDefaults!.string(forKey: "KEY_TOKEN")! - - let messageId = bestAttemptContent?.userInfo["message_id"] as! String + guard let sharedDefaults = UserDefaults(suiteName: "group.io.stream.flutter"), + let apiKey = sharedDefaults.string(forKey: "KEY_API_KEY"), + let userId = sharedDefaults.string(forKey: "KEY_USER_ID"), + let token = sharedDefaults.string(forKey: "KEY_TOKEN"), + let messageId = bestAttemptContent?.userInfo["message_id"] as? String else { + return + } Client.config = .init(apiKey: apiKey, logOptions: .error) Client.shared.set(user: User(id: userId), token: token) { res in - if res.isConnected { - Client.shared.message(withId: messageId) { res in - if let message = res.value?.message, - let channel = res.value?.channel { - let messageWrapper = MessageWrapper(id: message.id, channel: ChannelWrapper(id: channel.id, cid: channel.cid, type: channel.type, name: channel.name, imageURL: channel.imageURL, lastMessageDate: channel.lastMessageDate, created: channel.created, deleted: channel.deleted, createdBy: channel.createdBy, config: channel.config, frozen: channel.frozen, extraData: channel.extraData), type: message.type, user: message.user, created: message.created, updated: message.updated, text: message.text, command: message.command, args: message.args, attachments: message.attachments, parentId: message.parentId, showReplyInChannel: message.showReplyInChannel, mentionedUsers: message.mentionedUsers, extraData: message.extraData) - if let encodedData = try? JSONEncoder.stream.encode(messageWrapper) { - let storedMessages = sharedDefaults?.stringArray(forKey: "messageQueue") ?? [] - let encodedString = String(data: encodedData, encoding: .utf8)! - sharedDefaults?.setValue(storedMessages + [encodedString], forKey: "messageQueue") - - // Modify the notification content here... - self.bestAttemptContent?.title = "[modified] \(self.bestAttemptContent?.title ?? "")" - contentHandler(self.bestAttemptContent ?? request.content) - } - Client.shared.disconnect() + guard res.isConnected else { + return + } + + Client.shared.message(withId: messageId) { res in + if let message = res.value?.message, + let channel = res.value?.channel { + let messageWrapper = MessageWrapper(channel: channel, message: message) + if let encodedData = try? JSONEncoder.stream.encode(messageWrapper), + let encodedString = String(data: encodedData, encoding: .utf8) { + let storedMessages = sharedDefaults.stringArray(forKey: "messageQueue") ?? [] + sharedDefaults.setValue(storedMessages + [encodedString], forKey: "messageQueue") + + // Modify the notification content here... + self.bestAttemptContent?.title = "[modified] \(self.bestAttemptContent?.title ?? "")" + contentHandler(self.bestAttemptContent ?? request.content) } + Client.shared.disconnect() } } } @@ -72,6 +75,23 @@ public struct MessageWrapper: Encodable { case mentionedUsers = "mentioned_users" } + init(channel: Channel, message: Message) { + id = message.id + type = message.type + user = message.user + created = message.created + updated = message.updated + text = message.text + command = message.command + args = message.args + attachments = message.attachments + parentId = message.parentId + showReplyInChannel = message.showReplyInChannel + mentionedUsers = message.mentionedUsers + extraData = message.extraData + self.channel = ChannelWrapper(channel: channel) + } + /// A message id. public let id: String /// The channel cid. @@ -115,8 +135,24 @@ public struct ChannelWrapper: Encodable { case createdBy = "created_by" case created = "created_at" case deleted = "deleted_at" + case frozen } - + + init(channel: Channel) { + id = channel.id + cid = channel.cid + type = channel.type + name = channel.name + imageURL = channel.imageURL + lastMessageDate = channel.lastMessageDate + created = channel.created + deleted = channel.deleted + createdBy = channel.createdBy + config = channel.config + frozen = channel.frozen + extraData = channel.extraData + } + /// A channel id. public let id: String /// A channel type + id. diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index fd5f82d2..3f549155 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -9,8 +9,8 @@ import Flutter _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - if let jsonMessage = sharedDefaults?.stringArray(forKey: "messageQueue") { - UserDefaults.standard.setValue(jsonMessage, forKey: "flutter.messageQueue") + if let messageQueue = sharedDefaults?.stringArray(forKey: "messageQueue") { + UserDefaults.standard.setValue(messageQueue, forKey: "flutter.messageQueue") sharedDefaults?.removeObject(forKey: "messageQueue") } @@ -33,8 +33,8 @@ import Flutter } override func applicationWillEnterForeground(_ application: UIApplication) { - if let jsonMessage = sharedDefaults?.stringArray(forKey: "messageQueue") { - UserDefaults.standard.setValue(jsonMessage, forKey: "flutter.messageQueue") + if let messageQueue = sharedDefaults?.stringArray(forKey: "messageQueue") { + UserDefaults.standard.setValue(messageQueue, forKey: "flutter.messageQueue") sharedDefaults?.removeObject(forKey: "messageQueue") } } diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 24412d52..3d0ffa50 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -275,8 +275,6 @@ class StreamChatState extends State void dispose() { WidgetsBinding.instance.removeObserver(this); - print('disposeeeeeeeeee'); - _channelsController.close(); _queryChannelsLoadingController.close(); _newMessagesSubscription.cancel(); From e0fde25b2480bd38965bf22d6b098d08c67590b4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 7 Apr 2020 15:23:23 +0200 Subject: [PATCH 036/133] add channels_bloc --- example/lib/main.dart | 50 ++++--------- lib/src/channel_list_view.dart | 37 +++++----- lib/src/channels_bloc.dart | 130 +++++++++++++++++++++++++++++++++ lib/src/stream_chat.dart | 88 +--------------------- lib/stream_chat_flutter.dart | 1 + 5 files changed, 166 insertions(+), 140 deletions(-) create mode 100644 lib/src/channels_bloc.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 38de0083..4319e839 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -50,50 +50,32 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: Container( - child: ChannelListPage(client), + child: StreamChat( + client: client, + child: ChannelListPage(), + ), ), ); } } class ChannelListPage extends StatelessWidget { - final Client client; - - ChannelListPage(this.client); - @override Widget build(BuildContext context) { return Scaffold( - body: PageView( - children: [ - StreamChat( - client: client, - child: Builder( - builder: (context) => ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, - sort: [SortOption('last_message_at')], - pagination: PaginationParams( - limit: 20, - ), - channelWidget: ChannelPage(), - ), - ), + body: ChannelsBloc( + child: ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, ), - StreamChat( - client: client, - child: ChannelListView( - sort: [SortOption('last_message_at')], - pagination: PaginationParams( - limit: 20, - ), - channelWidget: ChannelPage(), - ), - ), - ], + channelWidget: ChannelPage(), + ), ), ); } diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 580d4d84..b18b60d3 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/channels_bloc.dart'; import '../stream_chat_flutter.dart'; import 'channel_preview.dart'; @@ -104,11 +105,11 @@ class _ChannelListViewState extends State @override Widget build(BuildContext context) { - final streamChat = StreamChat.of(context); + final channelsProvider = ChannelsBloc.of(context); return RefreshIndicator( onRefresh: () async { - return streamChat.queryChannels( + return channelsProvider.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -116,7 +117,7 @@ class _ChannelListViewState extends State ); }, child: StreamBuilder>( - stream: streamChat.channelsStream, + stream: channelsProvider.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { if (snapshot.error is Error) { @@ -164,7 +165,7 @@ class _ChannelListViewState extends State ), FlatButton( onPressed: () { - streamChat.queryChannels( + channelsProvider.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -212,11 +213,11 @@ class _ChannelListViewState extends State i = i ~/ 2; - final streamChat = StreamChat.of(context); + final channelsProvider = ChannelsBloc.of(context); if (i < channels.length) { final channel = channels[i]; - final channelClient = streamChat.channels.firstWhere( + final channelClient = channelsProvider.channels.firstWhere( (c) => c.cid == channel.cid, orElse: () => null, ); @@ -280,13 +281,14 @@ class _ChannelListViewState extends State ), ); } else { - return _buildQueryProgressIndicator(context, streamChat); + return _buildQueryProgressIndicator(context, channelsProvider); } } - Widget _buildQueryProgressIndicator(context, StreamChatState streamChat) { + Widget _buildQueryProgressIndicator( + context, ChannelsBlocState channelsProvider) { return StreamBuilder( - stream: streamChat.queryChannelsLoading, + stream: channelsProvider.queryChannelsLoading, initialData: false, builder: (context, snapshot) { if (snapshot.hasError) { @@ -318,14 +320,14 @@ class _ChannelListViewState extends State ); } - void _listenChannelPagination(StreamChatState streamChat) { + void _listenChannelPagination(ChannelsBlocState channelsProvider) { if (_scrollController.position.maxScrollExtent == _scrollController.offset) { - streamChat.queryChannels( + channelsProvider.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination.copyWith( - offset: streamChat.channels.length, + offset: channelsProvider.channels.length, ), options: widget.options, ); @@ -336,11 +338,11 @@ class _ChannelListViewState extends State void initState() { super.initState(); - final streamChat = StreamChat.of(context); + final channelsProvider = ChannelsBloc.of(context); WidgetsBinding.instance.addObserver(this); - streamChat.queryChannels( + channelsProvider.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -348,14 +350,14 @@ class _ChannelListViewState extends State ); _scrollController.addListener(() { - _listenChannelPagination(streamChat); + _listenChannelPagination(channelsProvider); }); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - StreamChat.of(context).queryChannels( + ChannelsBloc.of(context).queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -370,7 +372,4 @@ class _ChannelListViewState extends State WidgetsBinding.instance.removeObserver(this); super.dispose(); } - - @override - bool get wantKeepAlive => true; } diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart new file mode 100644 index 00000000..b7dec76d --- /dev/null +++ b/lib/src/channels_bloc.dart @@ -0,0 +1,130 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/stream_chat.dart'; + +class ChannelsBloc extends StatefulWidget { + final Widget child; + + const ChannelsBloc({ + Key key, + this.child, + }) : super(key: key); + + @override + ChannelsBlocState createState() => ChannelsBlocState(); + + static ChannelsBlocState of(BuildContext context) { + ChannelsBlocState streamChatState; + + streamChatState = context.findAncestorStateOfType(); + + if (streamChatState == null) { + throw Exception('You must have a ChannelsProvider widget as anchestor'); + } + + return streamChatState; + } +} + +class ChannelsBlocState extends State + with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return widget.child; + } + + /// The current channel list + List get channels => _channelsController.value; + + /// The current channel list as a stream + Stream> get channelsStream => _channelsController.stream; + + final BehaviorSubject _queryChannelsLoadingController = + BehaviorSubject.seeded(false); + + final BehaviorSubject> _channelsController = + BehaviorSubject.seeded([]); + + /// The stream notifying the state of queryChannel call + Stream get queryChannelsLoading => + _queryChannelsLoadingController.stream; + + /// Calls [client.queryChannels] updating [queryChannelsLoading] stream + Future queryChannels({ + Map filter, + List sortOptions, + PaginationParams paginationParams, + Map options, + bool onlyOffline = false, + }) async { + if (_queryChannelsLoadingController.value == true) { + return; + } + _queryChannelsLoadingController.sink.add(true); + + try { + final clear = paginationParams == null || + paginationParams.offset == null || + paginationParams.offset == 0; + final oldChannels = List.from(channels); + StreamChat.of(context) + .client + .queryChannels( + filter: filter, + sort: sortOptions, + options: options, + paginationParams: paginationParams, + onlyOffline: onlyOffline, + ) + .listen((channels) { + if (clear) { + _channelsController.add(channels); + } else { + final l = oldChannels + channels; + _channelsController.add(l); + } + }, onDone: () { + _queryChannelsLoadingController.sink.add(false); + }, onError: (err, stackTrace) { + print(err); + print(stackTrace); + _queryChannelsLoadingController.addError(err, stackTrace); + }); + } catch (err, stackTrace) { + _queryChannelsLoadingController.addError(err, stackTrace); + } + } + + StreamSubscription _newMessagesSubscription; + + @override + void initState() { + super.initState(); + + _newMessagesSubscription = + StreamChat.of(context).client.on(EventType.messageNew).listen((e) { + final newChannels = List.from(channels ?? []); + final index = newChannels.indexWhere((c) => c.cid == e.cid); + if (index > 0) { + final channel = newChannels.removeAt(index); + newChannels.insert(0, channel); + _channelsController.add(newChannels); + } + }); + } + + @override + void dispose() { + _channelsController.close(); + _queryChannelsLoadingController.close(); + _newMessagesSubscription.cancel(); + super.dispose(); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 3d0ffa50..915562f8 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; @@ -59,15 +58,12 @@ class StreamChat extends StatefulWidget { } } -class StreamChatState extends State - with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { +class StreamChatState extends State with WidgetsBindingObserver { Client get client => widget.client; final GlobalKey _navigatorKey = GlobalKey(); @override Widget build(BuildContext context) { - super.build(context); - final theme = _getTheme(context, widget.streamChatThemeData); return StreamChatTheme( data: theme, @@ -179,83 +175,9 @@ class StreamChatState extends State /// The current user as a stream Stream get userStream => widget.client.state.userStream; - /// The current channel list - List get channels => _channelsController.value; - - /// The current channel list as a stream - Stream> get channelsStream => _channelsController.stream; - - final BehaviorSubject _queryChannelsLoadingController = - BehaviorSubject.seeded(false); - - final BehaviorSubject> _channelsController = - BehaviorSubject.seeded([]); - - /// The stream notifying the state of queryChannel call - Stream get queryChannelsLoading => - _queryChannelsLoadingController.stream; - - /// Calls [client.queryChannels] updating [queryChannelsLoading] stream - Future queryChannels({ - Map filter, - List sortOptions, - PaginationParams paginationParams, - Map options, - bool onlyOffline = false, - }) async { - if (_queryChannelsLoadingController.value == true) { - return; - } - _queryChannelsLoadingController.sink.add(true); - - try { - final clear = paginationParams == null || - paginationParams.offset == null || - paginationParams.offset == 0; - final oldChannels = List.from(channels); - client - .queryChannels( - filter: filter, - sort: sortOptions, - options: options, - paginationParams: paginationParams, - onlyOffline: onlyOffline, - ) - .listen((channels) { - if (clear) { - _channelsController.add(channels); - } else { - final l = oldChannels + channels; - _channelsController.add(l); - } - }, onDone: () { - _queryChannelsLoadingController.sink.add(false); - }, onError: (err, stackTrace) { - print(err); - print(stackTrace); - _queryChannelsLoadingController.addError(err, stackTrace); - }); - } catch (err, stackTrace) { - _queryChannelsLoadingController.addError(err, stackTrace); - } - } - - StreamSubscription _newMessagesSubscription; - @override void initState() { super.initState(); - - _newMessagesSubscription = client.on(EventType.messageNew).listen((e) { - final newChannels = List.from(channels ?? []); - final index = newChannels.indexWhere((c) => c.cid == e.cid); - if (index > 0) { - final channel = newChannels.removeAt(index); - newChannels.insert(0, channel); - _channelsController.add(newChannels); - } - }); - WidgetsBinding.instance.addObserver(this); } @@ -274,14 +196,6 @@ class StreamChatState extends State @override void dispose() { WidgetsBinding.instance.removeObserver(this); - - _channelsController.close(); - _queryChannelsLoadingController.close(); - _newMessagesSubscription.cancel(); - super.dispose(); } - - @override - bool get wantKeepAlive => true; } diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 857336ce..d777ffff 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -5,6 +5,7 @@ export 'src/channel_image.dart'; export 'src/channel_list_view.dart'; export 'src/channel_name.dart'; export 'src/channel_preview.dart'; +export 'src/channels_bloc.dart'; export 'src/message_input.dart'; export 'src/message_list_view.dart'; export 'src/message_widget.dart'; From cd46dc8a333c312cf745452b2f0cd9395f3baeb7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 7 Apr 2020 16:02:49 +0200 Subject: [PATCH 037/133] remove memory leak on ios --- example/ios/Notifications/NotificationService.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/example/ios/Notifications/NotificationService.swift b/example/ios/Notifications/NotificationService.swift index e7418cdb..c264de5d 100644 --- a/example/ios/Notifications/NotificationService.swift +++ b/example/ios/Notifications/NotificationService.swift @@ -32,7 +32,7 @@ final class NotificationService: UNNotificationServiceExtension { return } - Client.shared.message(withId: messageId) { res in + Client.shared.message(withId: messageId) { [weak self] res in if let message = res.value?.message, let channel = res.value?.channel { let messageWrapper = MessageWrapper(channel: channel, message: message) @@ -42,8 +42,8 @@ final class NotificationService: UNNotificationServiceExtension { sharedDefaults.setValue(storedMessages + [encodedString], forKey: "messageQueue") // Modify the notification content here... - self.bestAttemptContent?.title = "[modified] \(self.bestAttemptContent?.title ?? "")" - contentHandler(self.bestAttemptContent ?? request.content) + self.bestAttemptContent?.title = "[modified] \(self?.bestAttemptContent?.title ?? "")" + contentHandler(self?.bestAttemptContent ?? request.content) } Client.shared.disconnect() } From 5a38908bfbde1eea573ac5107dd3c0fc2f3d9418 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 9 Apr 2020 10:16:57 +0200 Subject: [PATCH 038/133] fix message widget --- example/lib/main.dart | 4 +- lib/src/message_widget.dart | 73 +++++++++---------------------------- 2 files changed, 20 insertions(+), 57 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index baf7ae0b..e82580ee 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,7 +30,7 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - notificationHandler: _handleBackgroundNotification, + customAndroidNotificationHandler: _handleBackgroundNotification, ); await client.setUser( @@ -51,7 +51,7 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.system, + themeMode: ThemeMode.dark, home: Container( child: StreamChat( client: client, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 394c43ad..f7ffbad2 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -481,7 +481,9 @@ class _MessageWidgetState extends State Text( 'MESSAGE FAILED · CLICK TO TRY AGAIN', style: _messageTheme.messageText.copyWith( - color: Colors.black.withOpacity(.5), + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withOpacity(.5) + : Colors.black.withOpacity(.5), fontSize: 11, ), ), @@ -489,7 +491,9 @@ class _MessageWidgetState extends State Text( 'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN', style: _messageTheme.messageText.copyWith( - color: Colors.black.withOpacity(.5), + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withOpacity(.5) + : Colors.black.withOpacity(.5), fontSize: 11, ), ), @@ -497,7 +501,9 @@ class _MessageWidgetState extends State Text( 'MESSAGE DELETE FAILED · CLICK TO TRY AGAIN', style: _messageTheme.messageText.copyWith( - color: Colors.black.withOpacity(.5), + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withOpacity(.5) + : Colors.black.withOpacity(.5), fontSize: 11, ), ), @@ -521,7 +527,6 @@ class _MessageWidgetState extends State _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0), padding: EdgeInsets.all(10), constraints: BoxConstraints.loose( -<<<<<<< HEAD Size.fromWidth(MediaQuery.of(context).size.width * 0.7), ), child: _buildSendingError( @@ -536,38 +541,6 @@ class _MessageWidgetState extends State if (widget.onMentionTap != null) { widget.onMentionTap(mentionedUser); -======= - Size.fromWidth(MediaQuery.of(context).size.width * 0.7)), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.message.status == MessageSendingStatus.FAILED) - Text( - 'MESSAGE FAILED · CLICK TO TRY AGAIN', - style: _messageTheme.messageText.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), - fontSize: 11, - ), - ), - MarkdownBody( - data: text, - onTapLink: (link) { - if (link.startsWith('@')) { - final mentionedUser = - widget.message.mentionedUsers.firstWhere( - (u) => '@${u.name.replaceAll(' ', '')}' == link, - orElse: () => null, - ); - - if (widget.onMentionTap != null) { - widget.onMentionTap(mentionedUser); - } else { - print('tap on ${mentionedUser.name}'); - } ->>>>>>> master } else { print('tap on ${mentionedUser.name}'); } @@ -987,7 +960,12 @@ class _MessageWidgetState extends State Widget _buildImage( Attachment attachment, ) { -<<<<<<< HEAD + if (attachment.thumbUrl == null && + attachment.imageUrl == null && + attachment.assetUrl == null) { + return _buildErrorImage(attachment); + } + return Hero( tag: attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl, child: CachedNetworkImage( @@ -1015,17 +993,6 @@ class _MessageWidgetState extends State attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, errorWidget: (context, url, error) => _buildErrorImage(attachment), fit: BoxFit.cover, -======= - final errorWidget = Container( - width: 200, - height: 140, - color: Color(0xffd0021B).withOpacity(.1), - child: Center( - child: Icon( - Icons.error_outline, - color: Colors.white, - ), ->>>>>>> master ), ); } @@ -1040,7 +1007,7 @@ class _MessageWidgetState extends State child: Container( width: 200, height: 140, - color: Color(0xffd0021B).withAlpha(26), + color: Color(0xffd0021B).withOpacity(.1), child: Center( child: Icon( Icons.error_outline, @@ -1154,22 +1121,18 @@ class _MessageWidgetState extends State : Border.all( color: Theme.of(context).brightness == Brightness.dark ? Colors.white.withAlpha(24) - : Colors.black.withAlpha(24)), + : Colors.black.withAlpha(24), + ), borderRadius: BorderRadius.only( topLeft: Radius.circular((_isMyMessage || !rectBorders) ? 16 : 2), bottomLeft: Radius.circular(_isMyMessage ? 16 : 2), topRight: Radius.circular((_isMyMessage && rectBorders) ? 2 : 16), bottomRight: Radius.circular(_isMyMessage ? 2 : 16), ), -<<<<<<< HEAD color: (widget.message.status == MessageSendingStatus.FAILED || widget.message.status == MessageSendingStatus.FAILED_UPDATE || widget.message.status == MessageSendingStatus.FAILED_DELETE) - ? Color(0xffd0021B).withAlpha(26) -======= - color: widget.message.status == MessageSendingStatus.FAILED ? Color(0xffd0021B).withOpacity(.1) ->>>>>>> master : _messageTheme.messageBackgroundColor, ); } From d6033ec2b6ce9049b084c510cb24693e9eea6067 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 9 Apr 2020 16:57:48 +0200 Subject: [PATCH 039/133] do not disconnect android when the app is in background --- example/lib/main.dart | 4 ++-- lib/src/stream_chat.dart | 31 +++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index e82580ee..0fed4c63 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -Future _handleBackgroundNotification( +Future _handleAndroidNotification( Map notification, ) async { final notificationData = @@ -30,7 +30,7 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - customAndroidNotificationHandler: _handleBackgroundNotification, + androidNotificationHandler: _handleAndroidNotification, ); await client.setUser( diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index debd67ce..842a0699 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -61,6 +62,7 @@ class StreamChat extends StatefulWidget { class StreamChatState extends State with WidgetsBindingObserver { Client get client => widget.client; final GlobalKey _navigatorKey = GlobalKey(); + Timer _disconnectTimer; @override Widget build(BuildContext context) { @@ -185,14 +187,35 @@ class StreamChatState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); } + StreamSubscription _newMessageSubscription; + @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { - client.disconnect(); + if (Platform.isAndroid) { + _newMessageSubscription = + client.on(EventType.messageNew).listen((event) { + client.androidNotificationHandler({ + 'data': { + 'message_id': event.message.id, + }, + }); + }); + _disconnectTimer = Timer(Duration(minutes: 1), () { + client.disconnect(); + }); + } else { + client.disconnect(); + } } else if (state == AppLifecycleState.resumed) { - if (client.wsConnectionStatus.value == ConnectionStatus.disconnected) { - NotificationService.handleIosMessageQueue(client); - client.connect(); + _newMessageSubscription?.cancel(); + if (_disconnectTimer?.isActive == true) { + _disconnectTimer.cancel(); + } else { + if (client.wsConnectionStatus.value == ConnectionStatus.disconnected) { + NotificationService.handleIosMessageQueue(client); + client.connect(); + } } } } From 88ec33d798dfe3892d2c237f6e9c19643964af42 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 9 Apr 2020 18:01:44 +0200 Subject: [PATCH 040/133] add docs --- analysis_options.yaml | 2 +- example/lib/main.dart | 2 +- lib/src/channel_image.dart | 1 + lib/src/channel_list_view.dart | 2 ++ lib/src/channel_name.dart | 1 + lib/src/channel_preview.dart | 1 + lib/src/channels_bloc.dart | 5 +++++ lib/src/full_screen_image.dart | 3 +++ lib/src/message_input.dart | 15 ++++++++------- lib/src/message_list_view.dart | 1 + lib/src/message_widget.dart | 1 + lib/src/reaction_picker.dart | 2 ++ lib/src/stream_channel.dart | 1 + lib/src/stream_chat.dart | 1 + lib/src/thread_header.dart | 1 + lib/src/typing_indicator.dart | 1 + 16 files changed, 31 insertions(+), 9 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index fedac90d..7feb4342 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -44,7 +44,7 @@ linter: - package_prefixed_library_names - prefer_is_not_empty # - prefer_mixin # https://github.com/dart-lang/language/issues/32 -# - public_member_api_docs + - public_member_api_docs - slash_for_doc_comments # - sort_constructors_first # - sort_unnamed_constructors_first diff --git a/example/lib/main.dart b/example/lib/main.dart index 0fed4c63..75e2388d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -23,7 +23,7 @@ Future _handleAndroidNotification( body: notificationData.message.text, ); - await NotificationService.sendNotification(androidNotificationOptions); + await NotificationService.showNotification(androidNotificationOptions); } void main() async { diff --git a/lib/src/channel_image.dart b/lib/src/channel_image.dart index 8f624bf0..7c07bf3e 100644 --- a/lib/src/channel_image.dart +++ b/lib/src/channel_image.dart @@ -43,6 +43,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// The widget renders the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. class ChannelImage extends StatelessWidget { + /// Instantiate a new ChannelImage const ChannelImage({ Key key, this.channel, diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index f9cdc278..9bcccdd5 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -47,6 +47,7 @@ typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. class ChannelListView extends StatefulWidget { + /// Instantiate a new ChannelListView ChannelListView({ Key key, this.filter, @@ -59,6 +60,7 @@ class ChannelListView extends StatefulWidget { this.errorBuilder, }) : super(key: key); + /// The builder that will be used in case of error final Widget Function(Error error) errorBuilder; /// The query filters to use. diff --git a/lib/src/channel_name.dart b/lib/src/channel_name.dart index f2f27244..45193001 100644 --- a/lib/src/channel_name.dart +++ b/lib/src/channel_name.dart @@ -7,6 +7,7 @@ import 'stream_channel.dart'; /// /// The widget uses a [StreamBuilder] to render the channel information image as soon as it updates. class ChannelName extends StatelessWidget { + /// Instantiate a new ChannelName const ChannelName({ Key key, this.channel, diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 0723f338..c4377ebc 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -25,6 +25,7 @@ class ChannelPreview extends StatelessWidget { /// Channel displayed final Channel channel; + /// Instantiate a new ChannelPreview ChannelPreview({ @required this.channel, Key key, diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart index b7dec76d..7337c24c 100644 --- a/lib/src/channels_bloc.dart +++ b/lib/src/channels_bloc.dart @@ -5,9 +5,12 @@ import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; +/// Widget dedicated to the management of a channel list with pagination class ChannelsBloc extends StatefulWidget { + /// The widget child final Widget child; + /// Instantiate a new ChannelsBloc const ChannelsBloc({ Key key, this.child, @@ -16,6 +19,7 @@ class ChannelsBloc extends StatefulWidget { @override ChannelsBlocState createState() => ChannelsBlocState(); + /// Use this method to get the current [ChannelsBlocState] instance static ChannelsBlocState of(BuildContext context) { ChannelsBlocState streamChatState; @@ -29,6 +33,7 @@ class ChannelsBloc extends StatefulWidget { } } +/// The current state of the [ChannelsBloc] class ChannelsBlocState extends State with AutomaticKeepAliveClientMixin { @override diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index 37413322..c6f5804f 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -2,9 +2,12 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; +/// A full screen image widget class FullScreenImage extends StatelessWidget { + /// The url of the image final String url; + /// Instantiate a new FullScreenImage const FullScreenImage({ Key key, @required this.url, diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index c2e1feae..d3eff1b6 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -56,13 +56,14 @@ import 'stream_channel.dart'; /// The widget renders the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. class MessageInput extends StatefulWidget { - MessageInput( - {Key key, - this.onMessageSent, - this.parentMessage, - this.editMessage, - this.maxHeight = 150}) - : super(key: key); + /// Instantiate a new MessageInput + MessageInput({ + Key key, + this.onMessageSent, + this.parentMessage, + this.editMessage, + this.maxHeight = 150, + }) : super(key: key); /// Message to edit final Message editMessage; diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 9119cbaf..96853632 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -55,6 +55,7 @@ typedef ThreadTapCallback = void Function(Message, Widget); /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. class MessageListView extends StatefulWidget { + /// Instantiate a new MessageListView MessageListView({ Key key, this.messageBuilder, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index f7ffbad2..e3fe6f92 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -32,6 +32,7 @@ import 'stream_chat.dart'; /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. class MessageWidget extends StatefulWidget { + /// Instantiate a new MessageWidget const MessageWidget({ Key key, @required this.previousMessage, diff --git a/lib/src/reaction_picker.dart b/lib/src/reaction_picker.dart index 221dd3d8..da32857f 100644 --- a/lib/src/reaction_picker.dart +++ b/lib/src/reaction_picker.dart @@ -67,11 +67,13 @@ class ReactionPicker extends StatelessWidget { ); } + /// Add a reaction to the message void sendReaction(BuildContext context, String reactionType) { channel.sendReaction(message.id, reactionType); Navigator.of(context).pop(); } + /// Remove a reaction from the message void removeReaction(BuildContext context, String reactionType) { channel.deleteReaction(message.id, reactionType); Navigator.of(context).pop(); diff --git a/lib/src/stream_channel.dart b/lib/src/stream_channel.dart index 70357b65..eb7f4aea 100644 --- a/lib/src/stream_channel.dart +++ b/lib/src/stream_channel.dart @@ -101,6 +101,7 @@ class StreamChannelState extends State { }); } + /// Query the channel members and watchers Future queryMembersAndWatchers() async { await widget.channel.query( membersPagination: PaginationParams( diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 842a0699..876f76ee 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -59,6 +59,7 @@ class StreamChat extends StatefulWidget { } } +/// The current state of the StreamChat widget class StreamChatState extends State with WidgetsBindingObserver { Client get client => widget.client; final GlobalKey _navigatorKey = GlobalKey(); diff --git a/lib/src/thread_header.dart b/lib/src/thread_header.dart index 07f10e60..19a576c8 100644 --- a/lib/src/thread_header.dart +++ b/lib/src/thread_header.dart @@ -62,6 +62,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { /// The message parent of this thread final Message parent; + /// Instantiate a new ThreadHeader ThreadHeader({ Key key, @required this.parent, diff --git a/lib/src/typing_indicator.dart b/lib/src/typing_indicator.dart index a759ecde..57427da6 100644 --- a/lib/src/typing_indicator.dart +++ b/lib/src/typing_indicator.dart @@ -4,6 +4,7 @@ import 'package:stream_chat_flutter/src/stream_channel.dart'; /// Widget to show the current list of typing users class TypingIndicator extends StatelessWidget { + /// Instantiate a new TypingIndicator const TypingIndicator({ Key key, this.channel, From 77354334b3c81d6c911f3f721839694665eb5032 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 12:17:03 +0200 Subject: [PATCH 041/133] fix ios service --- example/ios/Notifications/NotificationService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/ios/Notifications/NotificationService.swift b/example/ios/Notifications/NotificationService.swift index c264de5d..7d085536 100644 --- a/example/ios/Notifications/NotificationService.swift +++ b/example/ios/Notifications/NotificationService.swift @@ -42,7 +42,7 @@ final class NotificationService: UNNotificationServiceExtension { sharedDefaults.setValue(storedMessages + [encodedString], forKey: "messageQueue") // Modify the notification content here... - self.bestAttemptContent?.title = "[modified] \(self?.bestAttemptContent?.title ?? "")" + self?.bestAttemptContent?.title = "[modified] \(self?.bestAttemptContent?.title ?? "")" contentHandler(self?.bestAttemptContent ?? request.content) } Client.shared.disconnect() From 552f4ee60085b968e80bdedaa8e480b18e8746a3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 14:47:57 +0200 Subject: [PATCH 042/133] fix channels query --- example/lib/main.dart | 3 ++- lib/src/channel_list_view.dart | 13 +++++++++---- lib/src/stream_chat.dart | 20 ++++++++++++-------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 75e2388d..d61d25f9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -51,7 +51,7 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.dark, + themeMode: ThemeMode.system, home: Container( child: StreamChat( client: client, @@ -99,6 +99,7 @@ class ChannelPage extends StatelessWidget { child: Stack( children: [ MessageListView( + showVideoFullScreen: false, threadBuilder: (_, parentMessage) { return ThreadPage( parent: parentMessage, diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 9bcccdd5..ce338fe4 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -326,7 +326,8 @@ class _ChannelListViewState extends State void _listenChannelPagination(ChannelsBlocState channelsProvider) { if (_scrollController.position.maxScrollExtent == - _scrollController.offset) { + _scrollController.offset && + _scrollController.offset != 0) { channelsProvider.queryChannels( filter: widget.filter, sortOptions: widget.sort, @@ -342,11 +343,11 @@ class _ChannelListViewState extends State void initState() { super.initState(); - final channelsProvider = ChannelsBloc.of(context); + final channelsBloc = ChannelsBloc.of(context); WidgetsBinding.instance.addObserver(this); - channelsProvider.queryChannels( + channelsBloc.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -354,7 +355,11 @@ class _ChannelListViewState extends State ); _scrollController.addListener(() { - _listenChannelPagination(channelsProvider); + channelsBloc.queryChannelsLoading.first.then((loading) { + if (!loading) { + _listenChannelPagination(channelsBloc); + } + }); }); } diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 876f76ee..d18df0f5 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -194,14 +194,16 @@ class StreamChatState extends State with WidgetsBindingObserver { void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { if (Platform.isAndroid) { - _newMessageSubscription = - client.on(EventType.messageNew).listen((event) { - client.androidNotificationHandler({ - 'data': { - 'message_id': event.message.id, - }, + if (client.pushNotificationsEnabled) { + _newMessageSubscription = + client.on(EventType.messageNew).listen((event) { + client.androidNotificationHandler({ + 'data': { + 'message_id': event.message.id, + }, + }); }); - }); + } _disconnectTimer = Timer(Duration(minutes: 1), () { client.disconnect(); }); @@ -214,7 +216,9 @@ class StreamChatState extends State with WidgetsBindingObserver { _disconnectTimer.cancel(); } else { if (client.wsConnectionStatus.value == ConnectionStatus.disconnected) { - NotificationService.handleIosMessageQueue(client); + if (client.pushNotificationsEnabled) { + NotificationService.handleIosMessageQueue(client); + } client.connect(); } } From 02f62c2a2bfe3ddb674f158b5b8d9a8edc8a0f63 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 16:12:31 +0200 Subject: [PATCH 043/133] Update README.md --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/README.md b/README.md index 1c1c6b4a..3ac3f1a6 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,69 @@ Out of the box all chat widgets use their own default styling, there are two way } } ``` + +### Offline storage + +By default the library saves information about channels and messages in a SQLite DB. + +Set the property `persistenceEnabled` to false if you don't want to use the offline storage. + +### Push notifications + +To enable push notifications set the property `pushNotificationsEnabled` to `true`. + +#### Android + +Follow the guide at (this link)[https://pub.dev/packages/firebase_messaging#android-integration] to setup Firebase for Android. + +Set the notification template on your GetStream dashboard to be like this: +```json +template = {} + +data template = { + "message_id": "{{ message.id }}" +} +``` + +Create a Application.kt file to be like this: +```kotlin +class Application : FlutterApplication(), PluginRegistrantCallback { + override fun onCreate() { + super.onCreate() + FlutterFirebaseMessagingService.setPluginRegistrant(this) + } + + override fun registerWith(registry: PluginRegistry?) { + PathProviderPlugin.registerWith(registry?.registrarFor( + "io.flutter.plugins.pathprovider.PathProviderPlugin")) + SharedPreferencesPlugin.registerWith(registry?.registrarFor( + "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")) + FlutterLocalNotificationsPlugin.registerWith(registry?.registrarFor( + "com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin")) + FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")) + } +} +``` + +Update the `AndroidManifest.xml` file to set the application class: +```xml +... + + + Date: Fri, 10 Apr 2020 16:24:10 +0200 Subject: [PATCH 044/133] version bump --- CHANGELOG.md | 8 ++++++++ pubspec.yaml | 5 ++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3354e42..a7b4c24e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.2.0-alpha + +- Offline storage + +- Push notifications + +- Minor bug fixes + ## 0.1.20 - Add message configuration properties to MessageListView diff --git a/pubspec.yaml b/pubspec.yaml index 1b3a8d02..d051bcee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.1.20 +version: 0.2.0-alpha environment: sdk: ">=2.3.0 <3.0.0" @@ -20,8 +20,7 @@ dependencies: file_picker: ^1.6.0 image_picker: ^0.6.4 flutter_keyboard_visibility: ^0.8.0 - stream_chat: - path: ../stream_chat_dart + stream_chat: ^0.2.0-alpha visibility_detector: ^0.1.4 dev_dependencies: From 802cf58de71f3aceb4a1300909f8098c4d872dd1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 16:27:44 +0200 Subject: [PATCH 045/133] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 3ac3f1a6..1c84aab3 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,14 @@ dependencies: You should then run `flutter packages get` +### Alpha version + +Use version `^0.2.0-alpha` to use the latest available version. + +Note that this is still an alpha version. There may be some bugs and the api can change in breaking ways. + +Thanks to whoever tries these versions and reports bugs or suggestions. + ### Android All set ✅ From 86e4e142c0ec2b75854c7cc18e7c67e130006521 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 16:28:25 +0200 Subject: [PATCH 046/133] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c84aab3..422b56f2 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ To enable push notifications set the property `pushNotificationsEnabled` to `tru #### Android -Follow the guide at (this link)[https://pub.dev/packages/firebase_messaging#android-integration] to setup Firebase for Android. +Follow the guide at [this link](https://pub.dev/packages/firebase_messaging#android-integration) to setup Firebase for Android. Set the notification template on your GetStream dashboard to be like this: ```json From 793ae5f93aefcd64935f42141291b0ebc61cebca Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 17:11:19 +0200 Subject: [PATCH 047/133] Update README.md --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 422b56f2..e62e3f77 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,31 @@ Update the `AndroidManifest.xml` file to set the application class: Make sure you have correctly configured your app to support push notifications, and that you have generated certificate/token for sending pushes. -To enable offline notification support on iOS a guide will be released soon on our website. +##### Offline support for push notifications + +- open the XCode project +- create a new target of type `Notification service extension` +- add `App Groups` capability to the `Runner` target and the just created one +- add the line ` pod 'StreamChatClient', :git => 'https://github.com/GetStream/stream-chat-swift.git', :branch => 'release/2.0'` to the Podfile +- run `pod install` +- substitute the code in the `Notification service` with [this one](https://gist.github.com/imtoori/d37611faefef036e1a6c017b1a09e91f) and substitute APPGROUP with the just created one +- do the same with `AppDelegate.swift` using [this template](https://gist.github.com/imtoori/f95b30f25b745c5f777bfff1085176ef) +- set the notification template on your GetStream dashboard to be like this: +```handlebars +template = { + "aps" : { + "alert" : { + "title" : "{{ sender.name }} @ {{ channel.name }}", + "body" : "{{ message.text }}" + }, + "badge": {{ unread_count }}, + "apns-priority": 10, + "mutable-content" : 1 + }, + "message_id": "{{ message.id }}" +} +``` +Of course you can change the `alert` object as you want. Just make sure it has the last three lines. ## Contributing From e58f2a1f35e4b41124fe6ae953728923ca067e9c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 17:13:16 +0200 Subject: [PATCH 048/133] fix video loading and error --- lib/src/message_widget.dart | 47 ++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index d2c84efb..cbdf131c 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1038,8 +1038,12 @@ class _MessageWidgetState extends State future: videoController.initialize(), builder: (_, snapshot) { if (snapshot.connectionState != ConnectionState.done) { - return Center( - child: CircularProgressIndicator(), + return Container( + height: 100, + width: 100, + child: Center( + child: CircularProgressIndicator(), + ), ); } ChewieController chewieController; @@ -1052,26 +1056,31 @@ class _MessageWidgetState extends State autoInitialize: false, aspectRatio: videoController.value.aspectRatio, errorBuilder: (_, e) { - return Stack( - children: [ - Container( - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - image: CachedNetworkImageProvider( - attachment.thumbUrl, + if (attachment.thumbUrl != null) { + return Stack( + children: [ + Container( + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.cover, + image: CachedNetworkImageProvider( + attachment.thumbUrl, + ), ), ), ), - ), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => _launchURL(attachment.titleLink), - ), - ), - ], - ); + if (attachment.titleLink != null) + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => _launchURL(attachment.titleLink), + ), + ), + ], + ); + } + + return _buildErrorImage(attachment); }); _chuwieControllers[attachment.assetUrl] = chewieController; } From 4964940200617f5794aacbe738590f74ab77109f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 17:14:28 +0200 Subject: [PATCH 049/133] version bump --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b4c24e..a4243671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0-alpha.1 + +- fix video loading and error + ## 0.2.0-alpha - Offline storage diff --git a/pubspec.yaml b/pubspec.yaml index d051bcee..998c8794 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha +version: 0.2.0-alpha+1 environment: sdk: ">=2.3.0 <3.0.0" From 082b2683e868d92c2548f86ff974809424a16185 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 17:15:27 +0200 Subject: [PATCH 050/133] version bump --- CHANGELOG.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4243671..22f5c4ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.2.0-alpha.1 +## 0.2.0-alpha+1 - fix video loading and error diff --git a/README.md b/README.md index e62e3f77..58df5181 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ You should then run `flutter packages get` ### Alpha version -Use version `^0.2.0-alpha` to use the latest available version. +Use version `^0.2.0-alpha+1` to use the latest available version. Note that this is still an alpha version. There may be some bugs and the api can change in breaking ways. From 479d140fefc865209a22cf772572c76cf7aecdd6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 18:09:36 +0200 Subject: [PATCH 051/133] add better mime detection --- lib/src/message_input.dart | 9 ++++++--- lib/src/message_widget.dart | 4 +++- pubspec.yaml | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index b7ada2e7..cbd5e6ec 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:mime/mime.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; @@ -636,13 +637,14 @@ class _MessageInputState extends State { }); String url; + final filename = file.path.split('/').last; if (type == FileType.image) { final res = await channel.sendImage( MultipartFile.fromBytes( bytes, - filename: file.path.split('/').last, - contentType: MediaType.parse('image/jpeg'), + filename: filename, + contentType: MediaType.parse(lookupMimeType(filename)), ), ); url = res.file; @@ -650,7 +652,8 @@ class _MessageInputState extends State { final res = await channel.sendFile( MultipartFile.fromBytes( bytes, - filename: file.path.split('/').last, + filename: filename, + contentType: MediaType.parse(lookupMimeType(filename)), ), ); url = res.file; diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index cbdf131c..e95118e9 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1035,7 +1035,9 @@ class _MessageWidgetState extends State } return FutureBuilder( - future: videoController.initialize(), + future: videoController.value.initialized + ? Future.value(true) + : videoController.initialize(), builder: (_, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return Container( diff --git a/pubspec.yaml b/pubspec.yaml index 998c8794..52a56c27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: image_picker: ^0.6.4 flutter_keyboard_visibility: ^0.8.0 stream_chat: ^0.2.0-alpha + mime: ^0.9.6+3 visibility_detector: ^0.1.4 dev_dependencies: From bd4fc46eb3f0d15e0146b23fe35564142442cd5d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 18:19:31 +0200 Subject: [PATCH 052/133] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58df5181..475fb258 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ Make sure you have correctly configured your app to support push notifications, - open the XCode project - create a new target of type `Notification service extension` - add `App Groups` capability to the `Runner` target and the just created one -- add the line ` pod 'StreamChatClient', :git => 'https://github.com/GetStream/stream-chat-swift.git', :branch => 'release/2.0'` to the Podfile +- add the line `pod 'StreamChatClient'` to the Podfile - run `pod install` - substitute the code in the `Notification service` with [this one](https://gist.github.com/imtoori/d37611faefef036e1a6c017b1a09e91f) and substitute APPGROUP with the just created one - do the same with `AppDelegate.swift` using [this template](https://gist.github.com/imtoori/f95b30f25b745c5f777bfff1085176ef) From ad5bf992f80e94e76d40bde31360e3c0ea605def Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 18:20:23 +0200 Subject: [PATCH 053/133] version bump --- CHANGELOG.md | 6 +++++- README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f5c4ac..33781b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ +## 0.2.0-alpha+2 + +- Add better mime detection + ## 0.2.0-alpha+1 -- fix video loading and error +- Fix video loading and error ## 0.2.0-alpha diff --git a/README.md b/README.md index 58df5181..f8aa891a 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ You should then run `flutter packages get` ### Alpha version -Use version `^0.2.0-alpha+1` to use the latest available version. +Use version `^0.2.0-alpha+2` to use the latest available version. Note that this is still an alpha version. There may be some bugs and the api can change in breaking ways. diff --git a/pubspec.yaml b/pubspec.yaml index 52a56c27..7dc0e9ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+1 +version: 0.2.0-alpha+2 environment: sdk: ">=2.3.0 <3.0.0" From 02f75b30406c73e9d1c67fc74916ef73663dcd96 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Apr 2020 18:24:35 +0200 Subject: [PATCH 054/133] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7093e2b7..5c04ad0d 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ Make sure you have correctly configured your app to support push notifications, - open the XCode project - create a new target of type `Notification service extension` -- add `App Groups` capability to the `Runner` target and the just created one +- add `App Groups` capability to the `Runner` target and the just created one and create one common group - add the line `pod 'StreamChatClient'` to the Podfile - run `pod install` - substitute the code in the `Notification service` with [this one](https://gist.github.com/imtoori/d37611faefef036e1a6c017b1a09e91f) and substitute APPGROUP with the just created one From 4b9e298a9169c2155a95f8a76146dd2418e98f3b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 11 Apr 2020 13:23:26 +0200 Subject: [PATCH 055/133] update dependencies --- pubspec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 7dc0e9ad..fd25c934 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,13 +12,13 @@ dependencies: photo_view: ^0.9.2 rxdart: ^0.23.1 jiffy: ^3.0.1 - cached_network_image: ^2.0.0 + cached_network_image: ^2.1.0+1 flutter_markdown: ^0.3.4 url_launcher: ^5.4.2 video_player: ^0.10.8+1 chewie: ^0.9.10 - file_picker: ^1.6.0 - image_picker: ^0.6.4 + file_picker: ^1.6.2 + image_picker: ^0.6.5 flutter_keyboard_visibility: ^0.8.0 stream_chat: ^0.2.0-alpha mime: ^0.9.6+3 From 14eba411b6e88dd940bb45b61bc5f38740ed8a46 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 11 Apr 2020 13:35:34 +0200 Subject: [PATCH 056/133] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c04ad0d..9b495747 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,11 @@ Follow [these instructions](https://pub.dev/packages/image_picker#ios) to check ### Business logic components -We provide 2 Widgets dedicated to business logic and state management: +We provide 3 Widgets dedicated to business logic and state management: - [StreamChat](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat-class.html) - [StreamChannel](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChannel-class.html) +- [ChannelsBloc](https://pub.dev/documentation/stream_chat_flutter/0.2.0-alpha+2/stream_chat_flutter/ChannelsBloc-class.html) ### UI Components From b7ad70f6cd310af8b8e61058c054989d977bb376 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 14 Apr 2020 15:24:08 +0200 Subject: [PATCH 057/133] Update README.md --- README.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b495747..47894c46 100644 --- a/README.md +++ b/README.md @@ -213,11 +213,55 @@ Update the `AndroidManifest.xml` file to set the application class: ... ``` +##### Customizing notifications + +To customize notifications pass a function as the named parameter `androidNotificationHandler` to the `Client` constructor. + +It has to be a top-level function or a static method. + +The class `NotificationService` provides some helper methods to use to handle the notification. + +You can start from this template to write the `androidNotificationHandler` function: + +```dart +Future _handleAndroidNotification( + Map notification, +) async { + // get message information from the backend and store it in the offline storage + final notificationData = await NotificationService.getAndStoreMessage(notification); + + // define the android channel specifics + final androidPlatformChannelSpecifics = AndroidNotificationDetails( + 'Message notifications', + 'Message notifications', + 'Channel dedicated to message notifications', + importance: Importance.Max, + priority: Priority.High, + ); + + // define the appearance of the notification + final androidNotificationOptions = AndroidNotificationOptions( + androidNotificationDetails: androidPlatformChannelSpecifics, + id: notificationData.message.id.hashCode, + title: + 'CUSTOM ${notificationData.message.user.name} @ ${notificationData.channel.cid}', + body: notificationData.message.text, + ); + + // actually show the notification + await NotificationService.showNotification(androidNotificationOptions); +} +``` + #### iOS Make sure you have correctly configured your app to support push notifications, and that you have generated certificate/token for sending pushes. -##### Offline support for push notifications +##### Offline support and customizing notifications + +On iOS we need to create a notification service extension. + +Follow these points to configure it - open the XCode project - create a new target of type `Notification service extension` @@ -243,6 +287,10 @@ template = { ``` Of course you can change the `alert` object as you want. Just make sure it has the last three lines. +To customize notifications on iOS you need to do it in the Notification service. + +There is no way of doing it using Dart code at the moment because of framework restrictions. + ## Contributing We welcome code changes that improve this library or fix a problem, From f1908f288952962648eef58f3922829d6885928e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 16 Apr 2020 09:40:31 +0200 Subject: [PATCH 058/133] version bump --- CHANGELOG.md | 4 ++++ lib/src/message_widget.dart | 16 ---------------- pubspec.yaml | 3 ++- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33781b93..b8ab829e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0-alpha+3 + +- Fix overflow in mentions overlay + ## 0.2.0-alpha+2 - Add better mime detection diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 493cacd0..e95118e9 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1023,22 +1023,6 @@ class _MessageWidgetState extends State ); } - Widget _buildErrorImage(Attachment attachment) { - return Center( - child: Container( - width: 200, - height: 140, - color: Color(0xffd0021B).withOpacity(.1), - child: Center( - child: Icon( - Icons.error_outline, - color: Colors.white, - ), - ), - ), - ); - } - Widget _buildVideo( Attachment attachment, ) { diff --git a/pubspec.yaml b/pubspec.yaml index fd25c934..2e78410e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+2 +version: 0.2.0-alpha+3 environment: sdk: ">=2.3.0 <3.0.0" @@ -23,6 +23,7 @@ dependencies: stream_chat: ^0.2.0-alpha mime: ^0.9.6+3 visibility_detector: ^0.1.4 + http_parser: ^3.1.4 dev_dependencies: pedantic: ^1.9.0 From 167e1adca24e0f5f808534761604c289ceb1dafd Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 16 Apr 2020 15:39:57 +0200 Subject: [PATCH 059/133] remove all notifications dependencies --- example/lib/main.dart | 79 ++++++++++++++++++++++++++++---------- example/pubspec.yaml | 2 + lib/src/channels_bloc.dart | 2 +- lib/src/message_input.dart | 1 + lib/src/stream_chat.dart | 52 +++++++++++++++++-------- pubspec.yaml | 4 +- 6 files changed, 100 insertions(+), 40 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d61d25f9..34830ac5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,36 +1,58 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_apns/apns.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart' + hide Message; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -Future _handleAndroidNotification( - Map notification, -) async { +void showLocalNotification(Message message, ChannelModel channel) async { + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + final initializationSettingsAndroid = + AndroidInitializationSettings('launch_background'); + final initializationSettingsIOS = IOSInitializationSettings(); + final initializationSettings = InitializationSettings( + initializationSettingsAndroid, + initializationSettingsIOS, + ); + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + flutterLocalNotificationsPlugin.show( + message.id.hashCode, + '${message.user.name} @ ${channel.name}', + message.text, + NotificationDetails( + AndroidNotificationDetails( + 'message channel', + 'Message channel', + 'Channel used for showing messages', + priority: Priority.High, + importance: Importance.High, + ), + IOSNotificationDetails(), + ), + ); +} + +Future backgroundHandler(Map notification) async { + final messageId = notification['data']['message_id']; + + print('messageId: ${messageId}'); + final notificationData = - await NotificationService.getAndStoreMessage(notification); + await NotificationService.getAndStoreMessage(messageId); - final androidPlatformChannelSpecifics = AndroidNotificationDetails( - 'Message notifications', - 'Message notifications', - 'Channel dedicated to message notifications', - importance: Importance.Max, - priority: Priority.High, + showLocalNotification( + notificationData.message, + notificationData.channel, ); - - final androidNotificationOptions = AndroidNotificationOptions( - androidNotificationDetails: androidPlatformChannelSpecifics, - id: notificationData.message.id.hashCode, - title: - 'CUSTOM ${notificationData.message.user.name} @ ${notificationData.channel.cid}', - body: notificationData.message.text, - ); - - await NotificationService.showNotification(androidNotificationOptions); } void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - androidNotificationHandler: _handleAndroidNotification, + showFakeNotification: Platform.isAndroid ? showLocalNotification : null, ); await client.setUser( @@ -38,6 +60,21 @@ void main() async { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcjEifQ.NGZPyPMx7KSVisJmh4tJhOIv7ZjCaMQpOh4gTINvCaU', ); + final connector = createPushConnector(); + connector.configure( + onBackgroundMessage: backgroundHandler, + ); + + connector.requestNotificationPermissions(); + connector.token.addListener(() { + if (connector.token.value != null) { + client.addDevice( + connector.token.value, + Platform.isAndroid ? 'firebase' : 'apn', + ); + } + }); + runApp(MyApp(client)); } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2968dbca..9ceff34f 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -11,6 +11,8 @@ dependencies: sdk: flutter stream_chat_flutter: path: ../ + flutter_apns: ^1.1.0 + flutter_local_notifications: ^1.4.1 dev_dependencies: flutter_test: diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart index 7337c24c..9dfafbc2 100644 --- a/lib/src/channels_bloc.dart +++ b/lib/src/channels_bloc.dart @@ -26,7 +26,7 @@ class ChannelsBloc extends StatefulWidget { streamChatState = context.findAncestorStateOfType(); if (streamChatState == null) { - throw Exception('You must have a ChannelsProvider widget as anchestor'); + throw Exception('You must have a ChannelsBloc widget as anchestor'); } return streamChatState; diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index f0b2f2f3..7f76dbf1 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -4,6 +4,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:http_parser/http_parser.dart'; import 'package:image_picker/image_picker.dart'; import 'package:mime/mime.dart'; import 'package:stream_chat/stream_chat.dart'; diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index d18df0f5..6f27182e 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -193,18 +192,41 @@ class StreamChatState extends State with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { - if (Platform.isAndroid) { - if (client.pushNotificationsEnabled) { - _newMessageSubscription = - client.on(EventType.messageNew).listen((event) { - client.androidNotificationHandler({ - 'data': { - 'message_id': event.message.id, - }, - }); - }); - } - _disconnectTimer = Timer(Duration(minutes: 1), () { + if (client.showFakeNotification != null) { + _newMessageSubscription = client + .on(EventType.messageNew) + .where((e) => e.user.id != user.id) + .listen((event) async { + var channel = client.state.channels[event.cid]; + + if (channel == null) { + channel = client.channel( + event.type, + id: event.cid.split(':')[1], + ); + await channel.query(); + } + + client.showFakeNotification( + event.message, + ChannelModel( + id: channel.id, + createdAt: channel.createdAt, + extraData: channel.extraData, + type: channel.type, + members: channel.state.members, + memberCount: channel.memberCount, + frozen: channel.frozen, + cid: channel.cid, + deletedAt: channel.deletedAt, + config: channel.config, + createdBy: channel.createdBy, + updatedAt: channel.updatedAt, + lastMessageAt: channel.lastMessageAt, + ), + ); + }); + _disconnectTimer = Timer(client.backgroundKeepAlive, () { client.disconnect(); }); } else { @@ -216,9 +238,7 @@ class StreamChatState extends State with WidgetsBindingObserver { _disconnectTimer.cancel(); } else { if (client.wsConnectionStatus.value == ConnectionStatus.disconnected) { - if (client.pushNotificationsEnabled) { - NotificationService.handleIosMessageQueue(client); - } + NotificationService.handleIosMessageQueue(client); client.connect(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 2e78410e..07482e91 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,6 @@ dependencies: flutter: sdk: flutter photo_view: ^0.9.2 - rxdart: ^0.23.1 jiffy: ^3.0.1 cached_network_image: ^2.1.0+1 flutter_markdown: ^0.3.4 @@ -20,7 +19,8 @@ dependencies: file_picker: ^1.6.2 image_picker: ^0.6.5 flutter_keyboard_visibility: ^0.8.0 - stream_chat: ^0.2.0-alpha + stream_chat: + path: ../stream_chat_dart mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From 02f34a6066b974e13aab0a14a423435ce38a0e65 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 16 Apr 2020 16:31:36 +0200 Subject: [PATCH 060/133] version bump --- CHANGELOG.md | 6 ++++++ pubspec.yaml | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8ab829e..9689a473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.0-alpha+4 + +- Remove dependencies on notification service + +- Expose some helping method for integrate offline storage with push notifications + ## 0.2.0-alpha+3 - Fix overflow in mentions overlay diff --git a/pubspec.yaml b/pubspec.yaml index 07482e91..d40cb941 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+3 +version: 0.2.0-alpha+4 environment: sdk: ">=2.3.0 <3.0.0" @@ -19,8 +19,7 @@ dependencies: file_picker: ^1.6.2 image_picker: ^0.6.5 flutter_keyboard_visibility: ^0.8.0 - stream_chat: - path: ../stream_chat_dart + stream_chat: ^0.2.0-alpha+3 mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From ed2124cf740b65ef81c8e7dc40c53abfaf159062 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 16 Apr 2020 16:32:36 +0200 Subject: [PATCH 061/133] update dependencies --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index d40cb941..d62212e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: url_launcher: ^5.4.2 video_player: ^0.10.8+1 chewie: ^0.9.10 + rxdart: ^0.23.0 file_picker: ^1.6.2 image_picker: ^0.6.5 flutter_keyboard_visibility: ^0.8.0 From d1452f22d56352d4395572a72e6679ae9dcc306c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 16 Apr 2020 16:35:12 +0200 Subject: [PATCH 062/133] update readme --- README.md | 129 ------------------------------------------------------ 1 file changed, 129 deletions(-) diff --git a/README.md b/README.md index 47894c46..52c6ef76 100644 --- a/README.md +++ b/README.md @@ -162,135 +162,6 @@ By default the library saves information about channels and messages in a SQLite Set the property `persistenceEnabled` to false if you don't want to use the offline storage. -### Push notifications - -To enable push notifications set the property `pushNotificationsEnabled` to `true`. - -#### Android - -Follow the guide at [this link](https://pub.dev/packages/firebase_messaging#android-integration) to setup Firebase for Android. - -Set the notification template on your GetStream dashboard to be like this: -```json -template = {} - -data template = { - "message_id": "{{ message.id }}" -} -``` - -Create a Application.kt file to be like this: -```kotlin -class Application : FlutterApplication(), PluginRegistrantCallback { - override fun onCreate() { - super.onCreate() - FlutterFirebaseMessagingService.setPluginRegistrant(this) - } - - override fun registerWith(registry: PluginRegistry?) { - PathProviderPlugin.registerWith(registry?.registrarFor( - "io.flutter.plugins.pathprovider.PathProviderPlugin")) - SharedPreferencesPlugin.registerWith(registry?.registrarFor( - "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")) - FlutterLocalNotificationsPlugin.registerWith(registry?.registrarFor( - "com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin")) - FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")) - } -} -``` - -Update the `AndroidManifest.xml` file to set the application class: -```xml -... - - - _handleAndroidNotification( - Map notification, -) async { - // get message information from the backend and store it in the offline storage - final notificationData = await NotificationService.getAndStoreMessage(notification); - - // define the android channel specifics - final androidPlatformChannelSpecifics = AndroidNotificationDetails( - 'Message notifications', - 'Message notifications', - 'Channel dedicated to message notifications', - importance: Importance.Max, - priority: Priority.High, - ); - - // define the appearance of the notification - final androidNotificationOptions = AndroidNotificationOptions( - androidNotificationDetails: androidPlatformChannelSpecifics, - id: notificationData.message.id.hashCode, - title: - 'CUSTOM ${notificationData.message.user.name} @ ${notificationData.channel.cid}', - body: notificationData.message.text, - ); - - // actually show the notification - await NotificationService.showNotification(androidNotificationOptions); -} -``` - -#### iOS - -Make sure you have correctly configured your app to support push notifications, and that you have generated certificate/token for sending pushes. - -##### Offline support and customizing notifications - -On iOS we need to create a notification service extension. - -Follow these points to configure it - -- open the XCode project -- create a new target of type `Notification service extension` -- add `App Groups` capability to the `Runner` target and the just created one and create one common group -- add the line `pod 'StreamChatClient'` to the Podfile -- run `pod install` -- substitute the code in the `Notification service` with [this one](https://gist.github.com/imtoori/d37611faefef036e1a6c017b1a09e91f) and substitute APPGROUP with the just created one -- do the same with `AppDelegate.swift` using [this template](https://gist.github.com/imtoori/f95b30f25b745c5f777bfff1085176ef) -- set the notification template on your GetStream dashboard to be like this: -```handlebars -template = { - "aps" : { - "alert" : { - "title" : "{{ sender.name }} @ {{ channel.name }}", - "body" : "{{ message.text }}" - }, - "badge": {{ unread_count }}, - "apns-priority": 10, - "mutable-content" : 1 - }, - "message_id": "{{ message.id }}" -} -``` -Of course you can change the `alert` object as you want. Just make sure it has the last three lines. - -To customize notifications on iOS you need to do it in the Notification service. - -There is no way of doing it using Dart code at the moment because of framework restrictions. - ## Contributing We welcome code changes that improve this library or fix a problem, From 8d3dcbac42f8865bd79a7f521ff6d7cc65f7aa10 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 16 Apr 2020 16:35:25 +0200 Subject: [PATCH 063/133] version bump --- CHANGELOG.md | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9689a473..eff4dcb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.2.0-alpha+4 +## 0.2.0-alpha+5 - Remove dependencies on notification service diff --git a/pubspec.yaml b/pubspec.yaml index d62212e4..48587097 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+4 +version: 0.2.0-alpha+5 environment: sdk: ">=2.3.0 <3.0.0" From 7c2d607a3a77335d9a120eeb2ae26ad1938d1a86 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 17 Apr 2020 16:05:32 +0200 Subject: [PATCH 064/133] version bump --- CHANGELOG.md | 4 +++ example/ios/Runner.xcodeproj/project.pbxproj | 12 ++++---- example/lib/main.dart | 32 +++++++++++--------- lib/src/stream_chat.dart | 4 +-- pubspec.yaml | 4 +-- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eff4dcb2..38b7ddcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0-alpha+6 + +- Update llc dependency + ## 0.2.0-alpha+5 - Remove dependencies on notification service diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 54ee7f7f..7d0e7c88 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -433,7 +433,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter.Notifications; + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -460,7 +460,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 13.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter.Notifications; + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; @@ -485,7 +485,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 13.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter.Notifications; + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; @@ -568,7 +568,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter; + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -709,7 +709,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter; + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -743,7 +743,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = io.stream.flutter; + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/example/lib/main.dart b/example/lib/main.dart index 34830ac5..c2f5336a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -17,7 +17,7 @@ void showLocalNotification(Message message, ChannelModel channel) async { initializationSettingsIOS, ); await flutterLocalNotificationsPlugin.initialize(initializationSettings); - flutterLocalNotificationsPlugin.show( + await flutterLocalNotificationsPlugin.show( message.id.hashCode, '${message.user.name} @ ${channel.name}', message.text, @@ -37,8 +37,6 @@ void showLocalNotification(Message message, ChannelModel channel) async { Future backgroundHandler(Map notification) async { final messageId = notification['data']['message_id']; - print('messageId: ${messageId}'); - final notificationData = await NotificationService.getAndStoreMessage(messageId); @@ -48,18 +46,7 @@ Future backgroundHandler(Map notification) async { ); } -void main() async { - final client = Client( - 's2dxdhpxd94g', - logLevel: Level.INFO, - showFakeNotification: Platform.isAndroid ? showLocalNotification : null, - ); - - await client.setUser( - User(id: 'user1'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcjEifQ.NGZPyPMx7KSVisJmh4tJhOIv7ZjCaMQpOh4gTINvCaU', - ); - +void _initNotifications(Client client) { final connector = createPushConnector(); connector.configure( onBackgroundMessage: backgroundHandler, @@ -74,6 +61,21 @@ void main() async { ); } }); +} + +void main() async { + final client = Client( + 's2dxdhpxd94g', + logLevel: Level.INFO, + showLocalNotification: Platform.isAndroid ? showLocalNotification : null, + ); + + await client.setUser( + User(id: 'user1'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcjEifQ.NGZPyPMx7KSVisJmh4tJhOIv7ZjCaMQpOh4gTINvCaU', + ); + + _initNotifications(client); runApp(MyApp(client)); } diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 6f27182e..b5db1f72 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -192,7 +192,7 @@ class StreamChatState extends State with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { - if (client.showFakeNotification != null) { + if (client.showLocalNotification != null) { _newMessageSubscription = client .on(EventType.messageNew) .where((e) => e.user.id != user.id) @@ -207,7 +207,7 @@ class StreamChatState extends State with WidgetsBindingObserver { await channel.query(); } - client.showFakeNotification( + client.showLocalNotification( event.message, ChannelModel( id: channel.id, diff --git a/pubspec.yaml b/pubspec.yaml index 48587097..ead788f8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+5 +version: 0.2.0-alpha+6 environment: sdk: ">=2.3.0 <3.0.0" @@ -20,7 +20,7 @@ dependencies: file_picker: ^1.6.2 image_picker: ^0.6.5 flutter_keyboard_visibility: ^0.8.0 - stream_chat: ^0.2.0-alpha+3 + stream_chat: ^0.2.0-alpha+4 mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From b20ba2e30629fcf10869dc3b14088d44153683b0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Apr 2020 09:43:54 +0200 Subject: [PATCH 065/133] update dependencies --- CHANGELOG.md | 2 +- lib/src/channel_image.dart | 29 ++++++++++++++++++++--------- lib/src/channel_name.dart | 16 +++++++++++++--- lib/src/message_input.dart | 28 ++++++++++++++-------------- lib/src/stream_chat.dart | 1 - lib/src/user_avatar.dart | 6 +++--- pubspec.yaml | 12 ++++++------ 7 files changed, 57 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b7ddcf..894a5de8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.2.0-alpha+6 +## 0.2.0-alpha+7 - Update llc dependency diff --git a/lib/src/channel_image.dart b/lib/src/channel_image.dart index 7c07bf3e..4f6c3bf9 100644 --- a/lib/src/channel_image.dart +++ b/lib/src/channel_image.dart @@ -38,7 +38,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// The widget uses a [StreamBuilder] to render the channel information image as soon as it updates. /// /// By default the widget radius size is 40x40 pixels. -/// Set the property [size] to set a custom dimension. +/// Set the property [constraints] to set a custom dimension. /// /// The widget renders the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. @@ -47,38 +47,49 @@ class ChannelImage extends StatelessWidget { const ChannelImage({ Key key, this.channel, - this.size = 40, + this.constraints, }) : super(key: key); /// The channel to show the image of final Channel channel; /// The diameter of the image - final double size; + final BoxConstraints constraints; @override Widget build(BuildContext context) { + final client = StreamChat.of(context); final channel = this.channel ?? StreamChannel.of(context).channel; return StreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, builder: (context, snapshot) { + String image; + if (snapshot.data?.containsKey('image') == true) { + image = snapshot.data['image']; + } else if (channel.state.members.length == 2) { + final otherMember = channel.state.members + .firstWhere((member) => member.user.id != client.user.id); + image = otherMember.user.extraData['image']; + } + return ClipRRect( borderRadius: StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme .borderRadius, child: Container( - constraints: StreamChatTheme.of(context) - .channelPreviewTheme - .avatarTheme - .constraints, + constraints: constraints ?? + StreamChatTheme.of(context) + .channelPreviewTheme + .avatarTheme + .constraints, decoration: BoxDecoration( color: StreamChatTheme.of(context).accentColor, ), - child: snapshot.data?.containsKey('image') ?? false + child: image != null ? CachedNetworkImage( - imageUrl: snapshot.data['image'], + imageUrl: image, errorWidget: (_, __, ___) { return Center( child: Text( diff --git a/lib/src/channel_name.dart b/lib/src/channel_name.dart index 45193001..ef7599b7 100644 --- a/lib/src/channel_name.dart +++ b/lib/src/channel_name.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import '../stream_chat_flutter.dart'; import 'stream_channel.dart'; /// It shows the current [Channel] name using a [Text] widget. @@ -22,15 +23,24 @@ class ChannelName extends StatelessWidget { @override Widget build(BuildContext context) { + final client = StreamChat.of(context); final channel = this.channel ?? StreamChannel.of(context).channel; return StreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, builder: (context, snapshot) { + String title; + if (snapshot.data['name'] == null && + channel.state.members.length == 2) { + final otherMember = channel.state.members + .firstWhere((member) => member.user.id != client.user.id); + title = otherMember.user.name; + } else { + title = snapshot.data['name'] ?? channel.id; + } + return Text( - snapshot.data != null - ? (snapshot.data['name'] ?? channel.cid) - : channel.cid, + title, style: textStyle, ); }, diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 7f76dbf1..d3d6482e 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:file_picker/file_picker.dart'; @@ -765,7 +766,7 @@ class _MessageInputState extends State { }); } - int _keyboardListener; + StreamSubscription _keyboardListener; @override void initState() { @@ -773,16 +774,8 @@ class _MessageInputState extends State { StreamChannel.of(context).queryMembersAndWatchers(); - _keyboardListener = KeyboardVisibilityNotification().addNewListener( - onHide: () { - if (_commandsOverlay != null) { - _commandsOverlay.remove(); - } - if (_mentionsOverlay != null) { - _mentionsOverlay.remove(); - } - }, - onShow: () { + _keyboardListener = KeyboardVisibility.onChange.listen((visible) { + if (visible) { if (_commandsOverlay != null) { if (_textController.text.startsWith('/')) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -800,8 +793,15 @@ class _MessageInputState extends State { }); } } - }, - ); + } else { + if (_commandsOverlay != null) { + _commandsOverlay.remove(); + } + if (_mentionsOverlay != null) { + _mentionsOverlay.remove(); + } + } + }); if (widget.editMessage != null) { _textController = TextEditingController(text: widget.editMessage.text); @@ -842,7 +842,7 @@ class _MessageInputState extends State { void dispose() { _commandsOverlay?.remove(); _mentionsOverlay?.remove(); - KeyboardVisibilityNotification().removeListener(_keyboardListener); + _keyboardListener.cancel(); super.dispose(); } diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index b5db1f72..e2551dab 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -214,7 +214,6 @@ class StreamChatState extends State with WidgetsBindingObserver { createdAt: channel.createdAt, extraData: channel.extraData, type: channel.type, - members: channel.state.members, memberCount: channel.memberCount, frozen: channel.frozen, cid: channel.cid, diff --git a/lib/src/user_avatar.dart b/lib/src/user_avatar.dart index 478ae3b3..aaaa6a27 100644 --- a/lib/src/user_avatar.dart +++ b/lib/src/user_avatar.dart @@ -8,11 +8,11 @@ class UserAvatar extends StatelessWidget { const UserAvatar({ Key key, @required this.user, - this.radius = 16, + this.constraints, }) : super(key: key); final User user; - final double radius; + final BoxConstraints constraints; @override Widget build(BuildContext context) { @@ -20,7 +20,7 @@ class UserAvatar extends StatelessWidget { borderRadius: StreamChatTheme.of(context).ownMessageTheme.avatarTheme.borderRadius, child: Container( - constraints: + constraints: constraints ?? StreamChatTheme.of(context).ownMessageTheme.avatarTheme.constraints, decoration: BoxDecoration( color: StreamChatTheme.of(context).accentColor, diff --git a/pubspec.yaml b/pubspec.yaml index ead788f8..e53248ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+6 +version: 0.2.0-alpha+7 environment: sdk: ">=2.3.0 <3.0.0" @@ -10,17 +10,17 @@ dependencies: flutter: sdk: flutter photo_view: ^0.9.2 + rxdart: ^0.24.0 jiffy: ^3.0.1 cached_network_image: ^2.1.0+1 - flutter_markdown: ^0.3.4 + flutter_markdown: ^0.3.5 url_launcher: ^5.4.2 video_player: ^0.10.8+1 chewie: ^0.9.10 - rxdart: ^0.23.0 - file_picker: ^1.6.2 + file_picker: ^1.6.3+2 image_picker: ^0.6.5 - flutter_keyboard_visibility: ^0.8.0 - stream_chat: ^0.2.0-alpha+4 + flutter_keyboard_visibility: ^2.0.0 + stream_chat: ^0.2.0-alpha+5 mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From ba9ca6d9a12dc3291dfe5b97983070b18879e365 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Apr 2020 23:38:28 +0200 Subject: [PATCH 066/133] update llc --- example/ios/Runner.xcodeproj/project.pbxproj | 34 ++++++++++++-------- lib/src/message_widget.dart | 4 +-- lib/src/reaction_picker.dart | 9 +++--- pubspec.yaml | 3 +- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 7d0e7c88..141e283a 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -249,13 +249,13 @@ 0BC14C4C242B5A7A0028DE94 = { CreatedOnToolsVersion = 11.4; DevelopmentTeam = EHV7XZLAHA; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = EHV7XZLAHA; LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; }; }; @@ -424,7 +424,8 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -435,6 +436,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "flutter example notifications"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -452,7 +454,8 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -462,6 +465,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "flutter example notifications"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -477,7 +481,8 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -487,6 +492,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "flutter example notifications"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -552,8 +558,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; @@ -570,7 +576,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "flutter example"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -693,8 +699,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; @@ -711,7 +717,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "flutter example"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -727,8 +733,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; @@ -745,7 +751,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "flutter example"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index e95118e9..2d37a05a 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -599,7 +599,7 @@ class _MessageWidgetState extends State : null, textColor: Colors.white, onPressed: () { - _streamChannel.channel.sendAction(widget.message.id, { + _streamChannel.channel.sendAction(widget.message, { action.name: action.value, }); }, @@ -615,7 +615,7 @@ class _MessageWidgetState extends State child: Text('${action.text}'), color: StreamChatTheme.of(context).accentColor, onPressed: () { - _streamChannel.channel.sendAction(widget.message.id, { + _streamChannel.channel.sendAction(widget.message, { action.name: action.value, }); }, diff --git a/lib/src/reaction_picker.dart b/lib/src/reaction_picker.dart index da32857f..d24d4e18 100644 --- a/lib/src/reaction_picker.dart +++ b/lib/src/reaction_picker.dart @@ -46,7 +46,8 @@ class ReactionPicker extends StatelessWidget { ), onPressed: () { if (ownReactionIndex != -1) { - removeReaction(context, reactionType); + removeReaction( + context, message.ownReactions[ownReactionIndex]); } else { sendReaction(context, reactionType); } @@ -69,13 +70,13 @@ class ReactionPicker extends StatelessWidget { /// Add a reaction to the message void sendReaction(BuildContext context, String reactionType) { - channel.sendReaction(message.id, reactionType); + channel.sendReaction(message, reactionType); Navigator.of(context).pop(); } /// Remove a reaction from the message - void removeReaction(BuildContext context, String reactionType) { - channel.deleteReaction(message.id, reactionType); + void removeReaction(BuildContext context, Reaction reaction) { + channel.deleteReaction(message, reaction); Navigator.of(context).pop(); } } diff --git a/pubspec.yaml b/pubspec.yaml index e53248ad..09a7c642 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,8 @@ dependencies: file_picker: ^1.6.3+2 image_picker: ^0.6.5 flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+5 + stream_chat: + path: ../stream_chat_dart mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From 47776520386a99bf4cf8959367c37793e1e76dc5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 21 Apr 2020 10:24:15 +0200 Subject: [PATCH 067/133] fix message divider for week and years --- lib/src/message_list_view.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 739194e4..8f8fdab2 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -242,8 +242,18 @@ class _MessageListViewState extends State { } else if (Jiffy(createdAt) .isSame(now.subtract(Duration(days: 1)), Units.DAY)) { dayInfo = 'YESTERDAY'; - } else { + } else if (Jiffy(createdAt).isAfter( + now.subtract(Duration(days: 7)), + Units.DAY, + )) { dayInfo = createdAt.format('EEEE').toUpperCase(); + } else if (Jiffy(createdAt).isAfter( + Jiffy(now).subtract(years: 1), + Units.DAY, + )) { + dayInfo = createdAt.format('dd/MM').toUpperCase(); + } else { + dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase(); } return Column( From 746b9b622c549fd2ddee1f81797d51194a9861ce Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 21 Apr 2020 11:36:34 +0200 Subject: [PATCH 068/133] update examples --- example/ios/Podfile.lock | 2 +- example/lib/custom_message.dart | 22 ++++++++++++---------- example/lib/custom_theme.dart | 22 ++++++++++++---------- example/lib/customize_channel_preview.dart | 22 ++++++++++++---------- example/lib/main.dart | 7 +++---- example/lib/multiple_conversation.dart | 22 ++++++++++++---------- example/lib/threads.dart | 22 ++++++++++++---------- 7 files changed, 64 insertions(+), 55 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 7a4583ed..6e34cefa 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -267,7 +267,7 @@ SPEC CHECKSUMS: PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b - shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 + shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 diff --git a/example/lib/custom_message.dart b/example/lib/custom_message.dart index 8d9e726a..b084baa2 100644 --- a/example/lib/custom_message.dart +++ b/example/lib/custom_message.dart @@ -50,17 +50,19 @@ 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, + body: ChannelsBloc( + child: ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), ), - channelWidget: ChannelPage(), ), ); } diff --git a/example/lib/custom_theme.dart b/example/lib/custom_theme.dart index 078faeb8..afd32ccb 100644 --- a/example/lib/custom_theme.dart +++ b/example/lib/custom_theme.dart @@ -70,17 +70,19 @@ 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, + body: ChannelsBloc( + child: ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), ), - channelWidget: ChannelPage(), ), ); } diff --git a/example/lib/customize_channel_preview.dart b/example/lib/customize_channel_preview.dart index b6603f06..6fcb5765 100644 --- a/example/lib/customize_channel_preview.dart +++ b/example/lib/customize_channel_preview.dart @@ -55,18 +55,20 @@ class ChannelListPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], + body: ChannelsBloc( + child: ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + }, }, - }, - channelPreviewBuilder: _channelPreviewBuilder, - sort: [SortOption('last_message_at')], - pagination: PaginationParams( - limit: 20, + channelPreviewBuilder: _channelPreviewBuilder, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), ), - channelWidget: ChannelPage(), ), ); } diff --git a/example/lib/main.dart b/example/lib/main.dart index c2f5336a..d7b905d0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -65,16 +65,15 @@ void _initNotifications(Client client) { void main() async { final client = Client( - 's2dxdhpxd94g', + 'b67pax5b2wdq', logLevel: Level.INFO, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, ); await client.setUser( - User(id: 'user1'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcjEifQ.NGZPyPMx7KSVisJmh4tJhOIv7ZjCaMQpOh4gTINvCaU', + User(id: 'falling-mountain-7'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', ); - _initNotifications(client); runApp(MyApp(client)); diff --git a/example/lib/multiple_conversation.dart b/example/lib/multiple_conversation.dart index 56e5ed1e..8bffa449 100644 --- a/example/lib/multiple_conversation.dart +++ b/example/lib/multiple_conversation.dart @@ -54,17 +54,19 @@ 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, + body: ChannelsBloc( + child: ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), ), - channelWidget: ChannelPage(), ), ); } diff --git a/example/lib/threads.dart b/example/lib/threads.dart index 092a5c7f..90e605f6 100644 --- a/example/lib/threads.dart +++ b/example/lib/threads.dart @@ -45,17 +45,19 @@ 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, + body: ChannelsBloc( + child: ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), ), - channelWidget: ChannelPage(), ), ); } From 43c71e0c7ecb7108afca5c138fa90e029205a494 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 21 Apr 2020 21:46:34 +0200 Subject: [PATCH 069/133] add sending indicator widget --- lib/src/message_widget.dart | 56 ++------------------------- lib/src/sending_indicator.dart | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 52 deletions(-) create mode 100644 lib/src/sending_indicator.dart diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 2d37a05a..56579f79 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -13,6 +13,7 @@ import 'package:stream_chat_flutter/src/full_screen_image.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; +import 'package:stream_chat_flutter/src/sending_indicator.dart'; import 'package:stream_chat_flutter/src/stream_channel.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; @@ -157,58 +158,9 @@ class _MessageWidgetState extends State child: Row( children: [ UserAvatar(user: widget.message.user), - if (_isMyMessage && - widget.nextMessage == null && - (widget.message.status == MessageSendingStatus.SENT || - widget.message.status == null)) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 1.0, - ), - child: CircleAvatar( - radius: 4, - backgroundColor: StreamChatTheme.of(context).accentColor, - child: Icon( - Icons.done, - color: Colors.white, - size: 4, - ), - ), - ), - if (_isMyMessage && - (widget.message.status == MessageSendingStatus.SENDING || - widget.message.status == MessageSendingStatus.UPDATING)) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 1.0, - ), - child: CircleAvatar( - radius: 4, - backgroundColor: Colors.grey, - child: Icon( - Icons.access_time, - size: 4, - color: Colors.white, - ), - ), - ), - if (_isMyMessage && - (widget.message.status == MessageSendingStatus.FAILED || - widget.message.status == MessageSendingStatus.FAILED_UPDATE || - widget.message.status == MessageSendingStatus.FAILED_DELETE)) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 1.0, - ), - child: CircleAvatar( - radius: 4, - backgroundColor: Color(0xffd0021B).withOpacity(.1), - child: Icon( - Icons.error_outline, - size: 4, - color: Colors.white, - ), - ), + if (_isMyMessage && widget.nextMessage == null) + SendingIndicator( + message: widget.message, ), ], ), diff --git a/lib/src/sending_indicator.dart b/lib/src/sending_indicator.dart new file mode 100644 index 00000000..6a36eae4 --- /dev/null +++ b/lib/src/sending_indicator.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Used to show the sending status of the message +class SendingIndicator extends StatelessWidget { + final Message message; + + const SendingIndicator({ + Key key, + this.message, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (message.status == MessageSendingStatus.SENT || message.status == null) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 1.0, + ), + child: CircleAvatar( + radius: 4, + backgroundColor: Theme.of(context).accentColor, + child: Icon( + Icons.done, + color: Colors.white, + size: 4, + ), + ), + ); + } + if (message.status == MessageSendingStatus.SENDING || + message.status == MessageSendingStatus.UPDATING) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 1.0, + ), + child: CircleAvatar( + radius: 4, + backgroundColor: Colors.grey, + child: Icon( + Icons.access_time, + size: 4, + color: Colors.white, + ), + ), + ); + } + if (message.status == MessageSendingStatus.FAILED || + message.status == MessageSendingStatus.FAILED_UPDATE || + message.status == MessageSendingStatus.FAILED_DELETE) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 1.0, + ), + child: CircleAvatar( + radius: 4, + backgroundColor: Color(0xffd0021B).withOpacity(.1), + child: Icon( + Icons.error_outline, + size: 4, + color: Colors.white, + ), + ), + ); + } + + return SizedBox(); + } +} From 8b5d1de37744142957d2b096f4c31b049249c603 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 21 Apr 2020 22:05:38 +0200 Subject: [PATCH 070/133] extract date_divider and reply_indicator widgets --- lib/src/date_divider.dart | 91 ++++++++++++++++++++++++++++++++++ lib/src/message_list_view.dart | 78 ++--------------------------- lib/src/message_widget.dart | 50 +++++-------------- lib/src/reply_indicator.dart | 54 ++++++++++++++++++++ lib/src/sending_indicator.dart | 2 +- lib/stream_chat_flutter.dart | 1 + 6 files changed, 163 insertions(+), 113 deletions(-) create mode 100644 lib/src/date_divider.dart create mode 100644 lib/src/reply_indicator.dart diff --git a/lib/src/date_divider.dart b/lib/src/date_divider.dart new file mode 100644 index 00000000..f1d074d1 --- /dev/null +++ b/lib/src/date_divider.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// It shows a date divider depending on the date difference +class DateDivider extends StatelessWidget { + const DateDivider({ + Key key, + @required this.nextMessage, + @required this.messageWidget, + }) : super(key: key); + + final Message nextMessage; + final Widget messageWidget; + + @override + Widget build(BuildContext context) { + final divider = Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Divider(), + ), + ); + + final createdAt = Jiffy(nextMessage.createdAt.toLocal()); + final now = DateTime.now(); + final hourInfo = createdAt.format('h:mm a'); + + String dayInfo; + if (Jiffy(createdAt).isSame(now, Units.DAY)) { + dayInfo = 'TODAY'; + } else if (Jiffy(createdAt) + .isSame(now.subtract(Duration(days: 1)), Units.DAY)) { + dayInfo = 'YESTERDAY'; + } else if (Jiffy(createdAt).isAfter( + now.subtract(Duration(days: 7)), + Units.DAY, + )) { + dayInfo = createdAt.format('EEEE').toUpperCase(); + } else if (Jiffy(createdAt).isAfter( + Jiffy(now).subtract(years: 1), + Units.DAY, + )) { + dayInfo = createdAt.format('dd/MM').toUpperCase(); + } else { + dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + messageWidget, + Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + divider, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: dayInfo, + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + TextSpan(text: ' AT'), + TextSpan(text: ' $hourInfo'), + ], + style: TextStyle( + fontWeight: FontWeight.normal, + ), + ), + style: TextStyle( + fontSize: 10, + color: + Theme.of(context).textTheme.title.color.withOpacity(.5), + ), + ), + ), + divider, + ], + ), + ), + ], + ); + } +} diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 8f8fdab2..a6f2dba1 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -6,6 +6,7 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:visibility_detector/visibility_detector.dart'; import '../stream_chat_flutter.dart'; +import 'date_divider.dart'; import 'message_widget.dart'; import 'stream_channel.dart'; @@ -225,80 +226,9 @@ class _MessageListViewState extends State { if (nextMessage != null && !Jiffy(message.createdAt.toLocal()) .isSame(nextMessage.createdAt.toLocal(), Units.DAY)) { - final divider = Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Divider(), - ), - ); - - final createdAt = Jiffy(nextMessage.createdAt.toLocal()); - final now = DateTime.now(); - final hourInfo = createdAt.format('h:mm a'); - - String dayInfo; - if (Jiffy(createdAt).isSame(now, Units.DAY)) { - dayInfo = 'TODAY'; - } else if (Jiffy(createdAt) - .isSame(now.subtract(Duration(days: 1)), Units.DAY)) { - dayInfo = 'YESTERDAY'; - } else if (Jiffy(createdAt).isAfter( - now.subtract(Duration(days: 7)), - Units.DAY, - )) { - dayInfo = createdAt.format('EEEE').toUpperCase(); - } else if (Jiffy(createdAt).isAfter( - Jiffy(now).subtract(years: 1), - Units.DAY, - )) { - dayInfo = createdAt.format('dd/MM').toUpperCase(); - } else { - dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase(); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - messageWidget, - Padding( - padding: const EdgeInsets.only(top: 24.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - divider, - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: Text.rich( - TextSpan( - children: [ - TextSpan( - text: dayInfo, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - TextSpan(text: ' AT'), - TextSpan(text: ' $hourInfo'), - ], - style: TextStyle( - fontWeight: FontWeight.normal, - ), - ), - style: TextStyle( - fontSize: 10, - color: Theme.of(context) - .textTheme - .title - .color - .withOpacity(.5), - ), - ), - ), - divider, - ], - ), - ), - ], + return DateDivider( + messageWidget: messageWidget, + nextMessage: nextMessage, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 56579f79..1e0fd789 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:chewie/chewie.dart'; @@ -13,6 +12,7 @@ import 'package:stream_chat_flutter/src/full_screen_image.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; +import 'package:stream_chat_flutter/src/reply_indicator.dart'; import 'package:stream_chat_flutter/src/sending_indicator.dart'; import 'package:stream_chat_flutter/src/stream_channel.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; @@ -206,45 +206,19 @@ class _MessageWidgetState extends State } Widget _buildThreadIndicator(BuildContext context) { - var row = [ - Text( - 'Replies: ${widget.message.replyCount}', - style: _messageTheme.replies, - ), - Transform( - transform: Matrix4.rotationY(_isMyMessage ? 0 : pi), - alignment: Alignment.center, - child: Icon( - Icons.subdirectory_arrow_left, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white12 - : Colors.black12, - ), - ), - ]; - - if (!_isMyMessage) { - row = row.reversed.toList(); - } - - return (widget.message.replyCount ?? 0) > 0 - ? GestureDetector( - onTap: () { - if (widget.isParent) { - return; - } - if (widget.onThreadTap != null) { + if (widget.message?.replyCount != null && widget.message.replyCount > 0) { + return ReplyIndicator( + onTap: widget.isParent + ? () { widget.onThreadTap(widget.message); } - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Row( - children: row, - ), - ), - ) - : SizedBox(); + : null, + message: widget.message, + messageTheme: _messageTheme, + reversed: _isMyMessage, + ); + } + return SizedBox(); } Widget _buildBubble( diff --git a/lib/src/reply_indicator.dart b/lib/src/reply_indicator.dart new file mode 100644 index 00000000..6ed5f83a --- /dev/null +++ b/lib/src/reply_indicator.dart @@ -0,0 +1,54 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class ReplyIndicator extends StatelessWidget { + final Message message; + final VoidCallback onTap; + final bool reversed; + final MessageTheme messageTheme; + + const ReplyIndicator({ + Key key, + this.message, + this.onTap, + this.reversed = false, + this.messageTheme, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + var row = [ + Text( + 'Replies: ${message.replyCount}', + style: messageTheme?.replies, + ), + Transform( + transform: Matrix4.rotationY(reversed ? 0 : pi), + alignment: Alignment.center, + child: Icon( + Icons.subdirectory_arrow_left, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white12 + : Colors.black12, + ), + ), + ]; + + if (!reversed) { + row = row.reversed.toList(); + } + + return GestureDetector( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + children: row, + ), + ), + ); + } +} diff --git a/lib/src/sending_indicator.dart b/lib/src/sending_indicator.dart index 6a36eae4..9c7ebb57 100644 --- a/lib/src/sending_indicator.dart +++ b/lib/src/sending_indicator.dart @@ -19,7 +19,7 @@ class SendingIndicator extends StatelessWidget { ), child: CircleAvatar( radius: 4, - backgroundColor: Theme.of(context).accentColor, + backgroundColor: StreamChatTheme.of(context).accentColor, child: Icon( Icons.done, color: Colors.white, diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index d777ffff..38e0dac3 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -6,6 +6,7 @@ export 'src/channel_list_view.dart'; export 'src/channel_name.dart'; export 'src/channel_preview.dart'; export 'src/channels_bloc.dart'; +export 'src/date_divider.dart'; export 'src/message_input.dart'; export 'src/message_list_view.dart'; export 'src/message_widget.dart'; From 3698bff086241584c28dc19916c109b8eaf74235 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Apr 2020 10:35:34 +0200 Subject: [PATCH 071/133] extract deleted message widget --- example/lib/main.dart | 1 + lib/src/deleted_message.dart | 35 +++++++++++++++++++++++++++++++++++ lib/src/message_widget.dart | 21 ++++----------------- lib/src/reply_indicator.dart | 1 + 4 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 lib/src/deleted_message.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index d7b905d0..7de283d0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -137,6 +137,7 @@ class ChannelPage extends StatelessWidget { child: Stack( children: [ MessageListView( + showOtherMessageUsername: true, showVideoFullScreen: false, threadBuilder: (_, parentMessage) { return ThreadPage( diff --git a/lib/src/deleted_message.dart b/lib/src/deleted_message.dart new file mode 100644 index 00000000..6b63176d --- /dev/null +++ b/lib/src/deleted_message.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; + +class DeletedMessage extends StatelessWidget { + const DeletedMessage({ + Key key, + @required this.messageTheme, + this.alignment, + }) : super(key: key); + + final MessageTheme messageTheme; + final Alignment alignment; + + @override + Widget build(BuildContext context) { + return Align( + alignment: alignment, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 14, + ), + child: Text( + 'This message was deleted...', + style: messageTheme.messageText.copyWith( + fontStyle: FontStyle.italic, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black, + ), + ), + ), + ); + } +} diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 1e0fd789..7df86b4b 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -21,6 +21,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:video_player/video_player.dart'; +import 'deleted_message.dart'; import 'stream_chat.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget.png) @@ -184,24 +185,10 @@ class _MessageWidgetState extends State _isMyMessage = _messageUserId == _currentUserId; } - Align _buildDeletedMessage(Alignment alignment) { - return Align( + Widget _buildDeletedMessage(Alignment alignment) { + return DeletedMessage( + messageTheme: _messageTheme, alignment: alignment, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 14, - ), - child: Text( - 'This message was deleted...', - style: _messageTheme.messageText.copyWith( - fontStyle: FontStyle.italic, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, - ), - ), - ), ); } diff --git a/lib/src/reply_indicator.dart b/lib/src/reply_indicator.dart index 6ed5f83a..e7a8f3c5 100644 --- a/lib/src/reply_indicator.dart +++ b/lib/src/reply_indicator.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// A reply button indicator class ReplyIndicator extends StatelessWidget { final Message message; final VoidCallback onTap; From 17c171f98478612f20c2fac5a55c27978d908e23 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Apr 2020 11:55:05 +0200 Subject: [PATCH 072/133] extract videoattachment --- example/lib/main.dart | 1 + lib/src/attachment_error.dart | 35 +++++++++ lib/src/message_widget.dart | 140 +++++----------------------------- lib/src/utils.dart | 14 ++++ lib/src/video_attachment.dart | 99 ++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 123 deletions(-) create mode 100644 lib/src/attachment_error.dart create mode 100644 lib/src/utils.dart create mode 100644 lib/src/video_attachment.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 7de283d0..0cb0f098 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -67,6 +67,7 @@ void main() async { final client = Client( 'b67pax5b2wdq', logLevel: Level.INFO, + persistenceEnabled: false, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, ); diff --git a/lib/src/attachment_error.dart b/lib/src/attachment_error.dart new file mode 100644 index 00000000..e8f87ab4 --- /dev/null +++ b/lib/src/attachment_error.dart @@ -0,0 +1,35 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; + +class AttachmentError extends StatelessWidget { + final Attachment attachment; + + const AttachmentError({ + Key key, + @required this.attachment, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (attachment.localUri != null) { + return Image.file( + File(attachment.localUri.path), + ); + } + return Center( + child: Container( + width: 200, + height: 140, + color: Color(0xffd0021B).withOpacity(.1), + child: Center( + child: Icon( + Icons.error_outline, + color: Colors.white, + ), + ), + ), + ); + } +} diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 7289678d..8ff582f6 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1,13 +1,11 @@ -import 'dart:io'; - import 'package:cached_network_image/cached_network_image.dart'; -import 'package:chewie/chewie.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/attachment_error.dart'; import 'package:stream_chat_flutter/src/full_screen_image.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; @@ -17,9 +15,9 @@ import 'package:stream_chat_flutter/src/sending_indicator.dart'; import 'package:stream_chat_flutter/src/stream_channel.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; +import 'package:stream_chat_flutter/src/utils.dart'; +import 'package:stream_chat_flutter/src/video_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:video_player/video_player.dart'; import 'deleted_message.dart'; import 'stream_chat.dart'; @@ -81,9 +79,6 @@ class MessageWidget extends StatefulWidget { class _MessageWidgetState extends State with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { - final Map _videoControllers = {}; - final Map _chuwieControllers = {}; - MessageTheme _messageTheme; StreamChatState _streamChat; StreamChannelState _streamChannel; @@ -219,7 +214,10 @@ class _MessageWidgetState extends State Widget attachmentWidget; if (attachment.type == 'video') { - attachmentWidget = _buildVideo(attachment); + attachmentWidget = VideoAttachment( + attachment: attachment, + enableFullScreen: widget.showVideoFullScreen, + ); } else if (attachment.type == 'image' || attachment.type == 'giphy') { attachmentWidget = _buildImage(attachment); @@ -227,7 +225,7 @@ class _MessageWidgetState extends State attachmentWidget = Material( child: InkWell( onTap: () { - _launchURL(attachment.assetUrl); + launchURL(context, attachment.assetUrl); }, child: Container( width: 100, @@ -478,7 +476,7 @@ class _MessageWidgetState extends State print('tap on ${mentionedUser.name}'); } } else { - _launchURL(link); + launchURL(context, link); } }, styleSheet: MarkdownStyleSheet.fromTheme( @@ -558,7 +556,7 @@ class _MessageWidgetState extends State child: Material( color: Colors.transparent, child: InkWell( - onTap: () => _launchURL(attachment.titleLink), + onTap: () => launchURL(context, attachment.titleLink), ), ), ); @@ -568,7 +566,7 @@ class _MessageWidgetState extends State return GestureDetector( onTap: () { if (attachment.titleLink != null) { - _launchURL(attachment.titleLink); + launchURL(context, attachment.titleLink); } }, child: Container( @@ -896,7 +894,9 @@ class _MessageWidgetState extends State if (attachment.thumbUrl == null && attachment.imageUrl == null && attachment.assetUrl == null) { - return _buildErrorImage(attachment); + return AttachmentError( + attachment: attachment, + ); } return Hero( @@ -924,122 +924,16 @@ class _MessageWidgetState extends State }, imageUrl: attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, - errorWidget: (context, url, error) => _buildErrorImage(attachment), + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + ), fit: BoxFit.cover, ), ); } - Widget _buildErrorImage(Attachment attachment) { - if (attachment.localUri != null) { - return Image.file( - File(attachment.localUri.path), - ); - } - return Center( - child: Container( - width: 200, - height: 140, - color: Color(0xffd0021B).withOpacity(.1), - child: Center( - child: Icon( - Icons.error_outline, - color: Colors.white, - ), - ), - ), - ); - } - - Widget _buildVideo( - Attachment attachment, - ) { - VideoPlayerController videoController; - if (_videoControllers.containsKey(attachment.assetUrl)) { - videoController = _videoControllers[attachment.assetUrl]; - } else { - videoController = VideoPlayerController.network(attachment.assetUrl); - _videoControllers[attachment.assetUrl] = videoController; - } - - return FutureBuilder( - future: videoController.value.initialized - ? Future.value(true) - : videoController.initialize(), - builder: (_, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Container( - height: 100, - width: 100, - child: Center( - child: CircularProgressIndicator(), - ), - ); - } - ChewieController chewieController; - if (_chuwieControllers.containsKey(attachment.assetUrl)) { - chewieController = _chuwieControllers[attachment.assetUrl]; - } else { - chewieController = ChewieController( - allowFullScreen: widget.showVideoFullScreen, - videoPlayerController: videoController, - autoInitialize: false, - aspectRatio: videoController.value.aspectRatio, - errorBuilder: (_, e) { - if (attachment.thumbUrl != null) { - return Stack( - children: [ - Container( - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - image: CachedNetworkImageProvider( - attachment.thumbUrl, - ), - ), - ), - ), - if (attachment.titleLink != null) - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => _launchURL(attachment.titleLink), - ), - ), - ], - ); - } - - return _buildErrorImage(attachment); - }); - _chuwieControllers[attachment.assetUrl] = chewieController; - } - return Chewie( - key: ValueKey( - 'ATTACHMENT-${attachment.title}-${widget.message.id}'), - controller: chewieController, - ); - }, - ); - } - - Future _launchURL(String url) async { - if (await canLaunch(url)) { - await launch(url); - } else { - Scaffold.of(context).showSnackBar( - SnackBar( - content: Text('Cannot launch the url'), - ), - ); - } - } - @override void dispose() { - _videoControllers.values.forEach((element) { - element.dispose(); - }); super.dispose(); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart new file mode 100644 index 00000000..c427099b --- /dev/null +++ b/lib/src/utils.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +Future launchURL(BuildContext context, String url) async { + if (await canLaunch(url)) { + await launch(url); + } else { + Scaffold.of(context).showSnackBar( + SnackBar( + content: Text('Cannot launch the url'), + ), + ); + } +} diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart new file mode 100644 index 00000000..60967184 --- /dev/null +++ b/lib/src/video_attachment.dart @@ -0,0 +1,99 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:chewie/chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/utils.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:video_player/video_player.dart'; + +import 'attachment_error.dart'; + +class VideoAttachment extends StatefulWidget { + final Attachment attachment; + final bool enableFullScreen; + + VideoAttachment({ + Key key, + @required this.attachment, + this.enableFullScreen = true, + }) : super(key: key); + + @override + _VideoAttachmentState createState() => _VideoAttachmentState(); +} + +class _VideoAttachmentState extends State { + ChewieController _chewieController; + VideoPlayerController _videoPlayerController; + bool initialized = false; + + @override + Widget build(BuildContext context) { + if (!initialized) { + return Container( + height: 100, + width: 100, + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + _chewieController = ChewieController( + allowFullScreen: widget.enableFullScreen, + videoPlayerController: _videoPlayerController, + autoInitialize: false, + aspectRatio: _videoPlayerController.value.aspectRatio, + errorBuilder: (_, e) { + if (widget.attachment.thumbUrl != null) { + return Stack( + children: [ + Container( + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.cover, + image: CachedNetworkImageProvider( + widget.attachment.thumbUrl, + ), + ), + ), + ), + if (widget.attachment.titleLink != null) + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => + launchURL(context, widget.attachment.titleLink), + ), + ), + ], + ); + } + return AttachmentError( + attachment: widget.attachment, + ); + }); + + return Chewie( + controller: _chewieController, + ); + } + + @override + void initState() { + super.initState(); + _videoPlayerController = VideoPlayerController.network( + widget.attachment.localUri ?? widget.attachment.assetUrl); + _videoPlayerController.initialize().then((_) { + if (_videoPlayerController.value.initialized) { + setState(() { + initialized = true; + }); + } + }); + } + + @override + void dispose() { + _videoPlayerController.dispose(); + super.dispose(); + } +} From 493ebe33f446b616964b0e485529010833bff2b9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Apr 2020 15:39:06 +0200 Subject: [PATCH 073/133] extract attachment widgets --- example/lib/main.dart | 1 - lib/src/attachment_actions.dart | 58 +++++++++ lib/src/attachment_title.dart | 66 +++++++++++ lib/src/file_attachment.dart | 30 +++++ lib/src/giphy_attachment.dart | 100 ++++++++++++++++ lib/src/image_attachment.dart | 92 +++++++++++++++ lib/src/message_widget.dart | 202 +++----------------------------- lib/src/video_attachment.dart | 28 +++-- 8 files changed, 384 insertions(+), 193 deletions(-) create mode 100644 lib/src/attachment_actions.dart create mode 100644 lib/src/attachment_title.dart create mode 100644 lib/src/file_attachment.dart create mode 100644 lib/src/giphy_attachment.dart create mode 100644 lib/src/image_attachment.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 0cb0f098..7de283d0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -67,7 +67,6 @@ void main() async { final client = Client( 'b67pax5b2wdq', logLevel: Level.INFO, - persistenceEnabled: false, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, ); diff --git a/lib/src/attachment_actions.dart b/lib/src/attachment_actions.dart new file mode 100644 index 00000000..15d4665f --- /dev/null +++ b/lib/src/attachment_actions.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class AttachmentActions extends StatelessWidget { + final Attachment attachment; + final Message message; + + const AttachmentActions({ + Key key, + this.attachment, + this.message, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final streamChannel = StreamChannel.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: attachment.actions?.map((action) { + if (action.style == 'primary') { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: FlatButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Text('${action.text}'), + color: action.style == 'primary' + ? StreamChatTheme.of(context).accentColor + : null, + textColor: Colors.white, + onPressed: () { + streamChannel.channel.sendAction(message, { + action.name: action.value, + }); + }, + ), + ); + } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: OutlineButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Text('${action.text}'), + color: StreamChatTheme.of(context).accentColor, + onPressed: () { + streamChannel.channel.sendAction(message, { + action.name: action.value, + }); + }, + ), + ); + })?.toList(), + ); + } +} diff --git a/lib/src/attachment_title.dart b/lib/src/attachment_title.dart new file mode 100644 index 00000000..d23c4bf8 --- /dev/null +++ b/lib/src/attachment_title.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; + +import 'stream_chat_theme.dart'; +import 'utils.dart'; + +class AttachmentTitle extends StatelessWidget { + const AttachmentTitle({ + Key key, + @required this.attachment, + @required this.messageTheme, + }) : super(key: key); + + final MessageTheme messageTheme; + final Attachment attachment; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + if (attachment.titleLink != null) { + launchURL(context, attachment.titleLink); + } + }, + child: Container( + constraints: BoxConstraints.loose( + Size( + MediaQuery.of(context).size.width * 0.7, + 500, + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + attachment.title, + overflow: TextOverflow.ellipsis, + style: messageTheme.messageText.copyWith( + color: StreamChatTheme.of(context).accentColor, + fontWeight: FontWeight.bold, + ), + ), + if (attachment.titleLink != null || + attachment.ogScrapeUrl != null) + Text( + Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl) + .authority + .split('.') + .reversed + .take(2) + .toList() + .reversed + .join('.'), + overflow: TextOverflow.ellipsis, + style: messageTheme.createdAt, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart new file mode 100644 index 00000000..d41cd13c --- /dev/null +++ b/lib/src/file_attachment.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/utils.dart'; + +class FileAttachment extends StatelessWidget { + final Attachment attachment; + + const FileAttachment({ + Key key, + @required this.attachment, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + child: InkWell( + onTap: () { + launchURL(context, attachment.assetUrl); + }, + child: Container( + width: 100, + height: 100, + child: Center( + child: Icon(Icons.attach_file), + ), + ), + ), + ); + } +} diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart new file mode 100644 index 00000000..4ad9e7ab --- /dev/null +++ b/lib/src/giphy_attachment.dart @@ -0,0 +1,100 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/attachment_actions.dart'; + +import '../stream_chat_flutter.dart'; +import 'attachment_error.dart'; +import 'attachment_title.dart'; +import 'full_screen_image.dart'; +import 'utils.dart'; + +class GiphyAttachment extends StatelessWidget { + final Attachment attachment; + final MessageTheme messageTheme; + final Message message; + + const GiphyAttachment({ + Key key, + this.attachment, + this.messageTheme, + this.message, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (attachment.thumbUrl == null && + attachment.imageUrl == null && + attachment.assetUrl == null) { + return AttachmentError( + attachment: attachment, + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Stack( + children: [ + Hero( + tag: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + child: CachedNetworkImage( + imageBuilder: (context, provider) { + return GestureDetector( + child: Image(image: provider), + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + ); + })); + }, + ); + }, + placeholder: (_, __) { + return Container( + width: 200, + height: 140, + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + ), + fit: BoxFit.cover, + ), + ), + if (attachment.titleLink != null || attachment.ogScrapeUrl != null) + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => launchURL( + context, + attachment.titleLink ?? attachment.ogScrapeUrl, + ), + ), + ), + ), + ], + ), + if (attachment.title != null) + AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, + ), + if (attachment.actions != null) + AttachmentActions( + attachment: attachment, + message: message, + ), + ], + ); + } +} diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart new file mode 100644 index 00000000..6ff653ff --- /dev/null +++ b/lib/src/image_attachment.dart @@ -0,0 +1,92 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +import '../stream_chat_flutter.dart'; +import 'attachment_error.dart'; +import 'attachment_title.dart'; +import 'full_screen_image.dart'; +import 'utils.dart'; + +class ImageAttachment extends StatelessWidget { + final Attachment attachment; + final MessageTheme messageTheme; + + const ImageAttachment({ + Key key, + this.attachment, + this.messageTheme, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (attachment.thumbUrl == null && + attachment.imageUrl == null && + attachment.assetUrl == null) { + return AttachmentError( + attachment: attachment, + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Stack( + children: [ + Hero( + tag: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + child: CachedNetworkImage( + imageBuilder: (context, provider) { + return GestureDetector( + child: Image(image: provider), + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + ); + })); + }, + ); + }, + placeholder: (_, __) { + return Container( + width: 200, + height: 140, + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + ), + fit: BoxFit.cover, + ), + ), + if (attachment.titleLink != null || attachment.ogScrapeUrl != null) + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => launchURL( + context, + attachment.titleLink ?? attachment.ogScrapeUrl, + ), + ), + ), + ), + ], + ), + if (attachment.title != null) + AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, + ), + ], + ); + } +} diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 8ff582f6..d5603370 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1,12 +1,11 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/attachment_error.dart'; -import 'package:stream_chat_flutter/src/full_screen_image.dart'; +import 'package:stream_chat_flutter/src/giphy_attachment.dart'; +import 'package:stream_chat_flutter/src/image_attachment.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; @@ -20,6 +19,7 @@ import 'package:stream_chat_flutter/src/video_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'deleted_message.dart'; +import 'file_attachment.dart'; import 'stream_chat.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget.png) @@ -216,25 +216,23 @@ class _MessageWidgetState extends State if (attachment.type == 'video') { attachmentWidget = VideoAttachment( attachment: attachment, + messageTheme: _messageTheme, enableFullScreen: widget.showVideoFullScreen, ); - } else if (attachment.type == 'image' || - attachment.type == 'giphy') { - attachmentWidget = _buildImage(attachment); + } else if (attachment.type == 'giphy') { + attachmentWidget = GiphyAttachment( + attachment: attachment, + message: widget.message, + messageTheme: _messageTheme, + ); + } else if (attachment.type == 'image') { + attachmentWidget = ImageAttachment( + attachment: attachment, + messageTheme: _messageTheme, + ); } else if (attachment.type == 'file') { - attachmentWidget = Material( - child: InkWell( - onTap: () { - launchURL(context, attachment.assetUrl); - }, - child: Container( - width: 100, - height: 100, - child: Center( - child: Icon(Icons.attach_file), - ), - ), - ), + attachmentWidget = FileAttachment( + attachment: attachment, ); } @@ -375,29 +373,12 @@ class _MessageWidgetState extends State constraints: BoxConstraints.loose( Size.fromWidth(MediaQuery.of(context).size.width * 0.7), ), - child: Stack( - children: [ - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - attachmentWidget, - if (attachment.title != null) - _buildAttachmentTitle(attachment), - ], - ), - if (attachment.type == 'image' && - attachment.titleLink != null) - _buildPreviewInkwell(attachment), - ], - ), + child: attachmentWidget, margin: EdgeInsets.only( top: nOfAttachmentWidgets > 1 ? 5 : 0, ), ), ), - if (attachment.actions != null) - _buildAttachmentActions(attachment, context), ], ), ); @@ -508,109 +489,6 @@ class _MessageWidgetState extends State return text; } - Row _buildAttachmentActions(Attachment attachment, BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.end, - children: attachment.actions?.map((action) { - if (action.style == 'primary') { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: FlatButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Text('${action.text}'), - color: action.style == 'primary' - ? StreamChatTheme.of(context).accentColor - : null, - textColor: Colors.white, - onPressed: () { - _streamChannel.channel.sendAction(widget.message, { - action.name: action.value, - }); - }, - ), - ); - } - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: OutlineButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Text('${action.text}'), - color: StreamChatTheme.of(context).accentColor, - onPressed: () { - _streamChannel.channel.sendAction(widget.message, { - action.name: action.value, - }); - }, - ), - ); - })?.toList(), - ); - } - - Positioned _buildPreviewInkwell(Attachment attachment) { - return Positioned.fill( - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => launchURL(context, attachment.titleLink), - ), - ), - ); - } - - GestureDetector _buildAttachmentTitle(Attachment attachment) { - return GestureDetector( - onTap: () { - if (attachment.titleLink != null) { - launchURL(context, attachment.titleLink); - } - }, - child: Container( - constraints: BoxConstraints.loose( - Size( - MediaQuery.of(context).size.width * 0.7, - 500, - ), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - attachment.title, - overflow: TextOverflow.ellipsis, - style: _messageTheme.messageText.copyWith( - color: StreamChatTheme.of(context).accentColor, - fontWeight: FontWeight.bold, - ), - ), - if (attachment.titleLink != null || - attachment.ogScrapeUrl != null) - Text( - Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl) - .authority - .split('.') - .reversed - .take(2) - .toList() - .reversed - .join('.'), - overflow: TextOverflow.ellipsis, - style: _messageTheme.createdAt, - ), - ], - ), - ), - ), - ); - } - Widget _buildReactionPaint() { return widget.message.reactionCounts?.isNotEmpty == true ? Positioned( @@ -888,50 +766,6 @@ class _MessageWidgetState extends State 'wow': '😲', }; - Widget _buildImage( - Attachment attachment, - ) { - if (attachment.thumbUrl == null && - attachment.imageUrl == null && - attachment.assetUrl == null) { - return AttachmentError( - attachment: attachment, - ); - } - - return Hero( - tag: attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl, - child: CachedNetworkImage( - imageBuilder: (context, provider) { - return GestureDetector( - child: Image(image: provider), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - url: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - ); - })); - }, - ); - }, - placeholder: (_, __) { - return Container( - width: 200, - height: 140, - ); - }, - imageUrl: - attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, - errorWidget: (context, url, error) => AttachmentError( - attachment: attachment, - ), - fit: BoxFit.cover, - ), - ); - } - @override void dispose() { super.dispose(); diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index 60967184..0d3339bb 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -6,14 +6,17 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; import 'attachment_error.dart'; +import 'attachment_title.dart'; class VideoAttachment extends StatefulWidget { final Attachment attachment; final bool enableFullScreen; + final MessageTheme messageTheme; VideoAttachment({ Key key, @required this.attachment, + @required this.messageTheme, this.enableFullScreen = true, }) : super(key: key); @@ -72,8 +75,19 @@ class _VideoAttachmentState extends State { ); }); - return Chewie( - controller: _chewieController, + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Chewie( + controller: _chewieController, + ), + if (widget.attachment.title != null) + AttachmentTitle( + messageTheme: widget.messageTheme, + attachment: widget.attachment, + ), + ], ); } @@ -82,12 +96,10 @@ class _VideoAttachmentState extends State { super.initState(); _videoPlayerController = VideoPlayerController.network( widget.attachment.localUri ?? widget.attachment.assetUrl); - _videoPlayerController.initialize().then((_) { - if (_videoPlayerController.value.initialized) { - setState(() { - initialized = true; - }); - } + _videoPlayerController.initialize().whenComplete(() { + setState(() { + initialized = true; + }); }); } From ad9709a2c9a1ca684346e87ac566e0ab1dc04138 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Apr 2020 16:13:12 +0200 Subject: [PATCH 074/133] add attachment builders parameter --- lib/src/message_list_view.dart | 8 +++ lib/src/message_widget.dart | 93 +++++++++++++++++++++++++--------- 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index a6f2dba1..da6a9c01 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -68,6 +68,7 @@ class MessageListView extends StatefulWidget { this.showVideoFullScreen = true, this.onMentionTap, this.onMessageActions, + this.attachmentBuilders, }) : super(key: key); /// Function used to build a custom message widget @@ -98,6 +99,9 @@ class MessageListView extends StatefulWidget { /// Function called on message long press final Function(BuildContext, Message) onMessageActions; + /// Map that defines a builder for an attachment type + final Map attachmentBuilders; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -157,6 +161,7 @@ class _MessageListViewState extends State { widget.showOtherMessageUsername, onMentionTap: widget.onMentionTap, onMessageActions: widget.onMessageActions, + attachmentBuilders: widget.attachmentBuilders, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), @@ -219,6 +224,7 @@ class _MessageListViewState extends State { showVideoFullScreen: widget.showVideoFullScreen, onMentionTap: widget.onMentionTap, onMessageActions: widget.onMessageActions, + attachmentBuilders: widget.attachmentBuilders, ); } } @@ -297,6 +303,7 @@ class _MessageListViewState extends State { showOtherMessageUsername: widget.showOtherMessageUsername, onMentionTap: widget.onMentionTap, onMessageActions: widget.onMessageActions, + attachmentBuilders: widget.attachmentBuilders, ); } @@ -336,6 +343,7 @@ class _MessageListViewState extends State { showOtherMessageUsername: widget.showOtherMessageUsername, onMentionTap: widget.onMentionTap, onMessageActions: widget.onMessageActions, + attachmentBuilders: widget.attachmentBuilders, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index d5603370..47d43440 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/file_attachment.dart'; import 'package:stream_chat_flutter/src/giphy_attachment.dart'; import 'package:stream_chat_flutter/src/image_attachment.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; @@ -19,9 +20,10 @@ import 'package:stream_chat_flutter/src/video_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'deleted_message.dart'; -import 'file_attachment.dart'; import 'stream_chat.dart'; +typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment); + /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget_paint.png) /// @@ -44,6 +46,7 @@ class MessageWidget extends StatefulWidget { this.onMentionTap, this.showOtherMessageUsername = false, this.showVideoFullScreen = true, + this.attachmentBuilders, }) : super(key: key); /// Function called on mention tap @@ -73,8 +76,11 @@ class MessageWidget extends StatefulWidget { /// True if the video player will allow fullscreen mode final bool showVideoFullScreen; + /// Map that defines a builder for an attachment type + final Map attachmentBuilders; + @override - _MessageWidgetState createState() => _MessageWidgetState(); + _MessageWidgetState createState() => _MessageWidgetState(attachmentBuilders); } class _MessageWidgetState extends State @@ -91,6 +97,38 @@ class _MessageWidgetState extends State bool _isLastUser; bool _isNextUser; + Map _attachmentBuilders; + + _MessageWidgetState(Map attachmentBuilders) { + _attachmentBuilders = { + 'image': (context, message, attachment) { + return ImageAttachment( + attachment: attachment, + messageTheme: _messageTheme, + ); + }, + 'video': (context, message, attachment) { + return VideoAttachment( + enableFullScreen: widget.showVideoFullScreen, + attachment: attachment, + messageTheme: _messageTheme, + ); + }, + 'giphy': (context, message, attachment) { + return GiphyAttachment( + attachment: attachment, + messageTheme: _messageTheme, + message: message, + ); + }, + 'file': (context, message, attachment) { + return FileAttachment( + attachment: attachment, + ); + }, + }..addAll(attachmentBuilders ?? {}); + } + @override Widget build(BuildContext context) { super.build(context); @@ -213,28 +251,37 @@ class _MessageWidgetState extends State nOfAttachmentWidgets++; Widget attachmentWidget; - if (attachment.type == 'video') { - attachmentWidget = VideoAttachment( - attachment: attachment, - messageTheme: _messageTheme, - enableFullScreen: widget.showVideoFullScreen, - ); - } else if (attachment.type == 'giphy') { - attachmentWidget = GiphyAttachment( - attachment: attachment, - message: widget.message, - messageTheme: _messageTheme, - ); - } else if (attachment.type == 'image') { - attachmentWidget = ImageAttachment( - attachment: attachment, - messageTheme: _messageTheme, - ); - } else if (attachment.type == 'file') { - attachmentWidget = FileAttachment( - attachment: attachment, - ); + final attachmentBuilder = _attachmentBuilders[attachment.type]; + if (attachmentBuilder == null) { + return SizedBox(); } + attachmentWidget = attachmentBuilder( + context, + widget.message, + attachment, + ); +// if (attachment.type == 'video') { +// attachmentWidget = VideoAttachment( +// attachment: attachment, +// messageTheme: _messageTheme, +// enableFullScreen: widget.showVideoFullScreen, +// ); +// } else if (attachment.type == 'giphy') { +// attachmentWidget = GiphyAttachment( +// attachment: attachment, +// message: widget.message, +// messageTheme: _messageTheme, +// ); +// } else if (attachment.type == 'image') { +// attachmentWidget = ImageAttachment( +// attachment: attachment, +// messageTheme: _messageTheme, +// ); +// } else if (attachment.type == 'file') { +// attachmentWidget = FileAttachment( +// attachment: attachment, +// ); +// } if (attachmentWidget != null) { return _buildAttachment( From 16f727f9e2b38c3aa21837b09ef1991d49cc7047 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 23 Apr 2020 10:40:50 +0200 Subject: [PATCH 075/133] cleanup comments --- lib/src/message_widget.dart | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 47d43440..32689aa6 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -255,33 +255,12 @@ class _MessageWidgetState extends State if (attachmentBuilder == null) { return SizedBox(); } + attachmentWidget = attachmentBuilder( context, widget.message, attachment, ); -// if (attachment.type == 'video') { -// attachmentWidget = VideoAttachment( -// attachment: attachment, -// messageTheme: _messageTheme, -// enableFullScreen: widget.showVideoFullScreen, -// ); -// } else if (attachment.type == 'giphy') { -// attachmentWidget = GiphyAttachment( -// attachment: attachment, -// message: widget.message, -// messageTheme: _messageTheme, -// ); -// } else if (attachment.type == 'image') { -// attachmentWidget = ImageAttachment( -// attachment: attachment, -// messageTheme: _messageTheme, -// ); -// } else if (attachment.type == 'file') { -// attachmentWidget = FileAttachment( -// attachment: attachment, -// ); -// } if (attachmentWidget != null) { return _buildAttachment( From 706c3d70b4934832aed1c62058a8d2195a0f93cb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 23 Apr 2020 11:42:06 +0200 Subject: [PATCH 076/133] fix attachmentbuilders on hot reload --- lib/src/message_widget.dart | 77 +++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 32689aa6..d1dafff7 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -4,9 +4,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/file_attachment.dart'; -import 'package:stream_chat_flutter/src/giphy_attachment.dart'; -import 'package:stream_chat_flutter/src/image_attachment.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; @@ -20,6 +17,9 @@ import 'package:stream_chat_flutter/src/video_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'deleted_message.dart'; +import 'file_attachment.dart'; +import 'giphy_attachment.dart'; +import 'image_attachment.dart'; import 'stream_chat.dart'; typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment); @@ -80,7 +80,7 @@ class MessageWidget extends StatefulWidget { final Map attachmentBuilders; @override - _MessageWidgetState createState() => _MessageWidgetState(attachmentBuilders); + _MessageWidgetState createState() => _MessageWidgetState(); } class _MessageWidgetState extends State @@ -99,36 +99,6 @@ class _MessageWidgetState extends State Map _attachmentBuilders; - _MessageWidgetState(Map attachmentBuilders) { - _attachmentBuilders = { - 'image': (context, message, attachment) { - return ImageAttachment( - attachment: attachment, - messageTheme: _messageTheme, - ); - }, - 'video': (context, message, attachment) { - return VideoAttachment( - enableFullScreen: widget.showVideoFullScreen, - attachment: attachment, - messageTheme: _messageTheme, - ); - }, - 'giphy': (context, message, attachment) { - return GiphyAttachment( - attachment: attachment, - messageTheme: _messageTheme, - message: message, - ); - }, - 'file': (context, message, attachment) { - return FileAttachment( - attachment: attachment, - ); - }, - }..addAll(attachmentBuilders ?? {}); - } - @override Widget build(BuildContext context) { super.build(context); @@ -216,6 +186,45 @@ class _MessageWidgetState extends State _isNextUser = _nextUserId == _messageUserId; _isMyMessage = _messageUserId == _currentUserId; + + _mergeAttachmentBuilders(); + } + + @override + void didUpdateWidget(MessageWidget oldWidget) { + super.didUpdateWidget(oldWidget); + + _mergeAttachmentBuilders(); + } + + void _mergeAttachmentBuilders() { + _attachmentBuilders = { + 'image': (context, message, attachment) { + return ImageAttachment( + attachment: attachment, + messageTheme: _messageTheme, + ); + }, + 'video': (context, message, attachment) { + return VideoAttachment( + enableFullScreen: widget.showVideoFullScreen, + attachment: attachment, + messageTheme: _messageTheme, + ); + }, + 'giphy': (context, message, attachment) { + return GiphyAttachment( + attachment: attachment, + messageTheme: _messageTheme, + message: message, + ); + }, + 'file': (context, message, attachment) { + return FileAttachment( + attachment: attachment, + ); + }, + }..addAll(widget.attachmentBuilders ?? {}); } Widget _buildDeletedMessage(Alignment alignment) { From 81ec08337848169770dad64fe64c34ab3a84f00c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 23 Apr 2020 12:02:24 +0200 Subject: [PATCH 077/133] add default channel image to theme --- example/lib/main.dart | 7 +++++++ lib/src/channel_header.dart | 2 +- lib/src/channel_image.dart | 3 ++- lib/src/stream_chat.dart | 1 + lib/src/stream_chat_theme.dart | 8 ++++++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 7de283d0..e9c61712 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -92,6 +92,13 @@ class MyApp extends StatelessWidget { themeMode: ThemeMode.system, home: Container( child: StreamChat( + streamChatThemeData: StreamChatThemeData( + defaultChannelImage: (context, channel) { + return Center( + child: Text('AAA'), + ); + }, + ), client: client, child: ChannelListPage(), ), diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart index 659a24eb..5a7a146c 100644 --- a/lib/src/channel_header.dart +++ b/lib/src/channel_header.dart @@ -128,7 +128,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { .channelHeaderTheme .lastMessageAt, ) - : Container(); + : SizedBox(); }, ); } diff --git a/lib/src/channel_image.dart b/lib/src/channel_image.dart index a52d9f96..bef666db 100644 --- a/lib/src/channel_image.dart +++ b/lib/src/channel_image.dart @@ -112,7 +112,8 @@ class ChannelImage extends StatelessWidget { }, fit: BoxFit.cover, ) - : SizedBox(), + : StreamChatTheme.of(context) + .defaultChannelImage(context, channel), Material( color: Colors.transparent, child: InkWell( diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index e2551dab..6011a14c 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -112,6 +112,7 @@ class StreamChatState extends State with WidgetsBindingObserver { final defaultTheme = StreamChatThemeData.getDefaultTheme(Theme.of(context)); final theme = defaultTheme.copyWith( primaryColor: themeData?.primaryColor, + defaultChannelImage: themeData?.defaultChannelImage, channelTheme: defaultTheme.channelTheme.copyWith( channelHeaderTheme: defaultTheme.channelTheme.channelHeaderTheme.copyWith( diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index e6053939..84f8a8f2 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/channel_header.dart'; import 'package:stream_chat_flutter/src/channel_preview.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; @@ -50,6 +51,9 @@ class StreamChatThemeData { /// Theme of other users messages final MessageTheme otherMessageTheme; + /// The widget that will be built when the channel image is unavailable + final Widget Function(BuildContext, Channel) defaultChannelImage; + /// Create a theme from scratch StreamChatThemeData({ this.primaryColor, @@ -59,6 +63,7 @@ class StreamChatThemeData { this.channelTheme, this.otherMessageTheme, this.ownMessageTheme, + this.defaultChannelImage, }); /// Create a theme from a Material [Theme] @@ -97,11 +102,13 @@ class StreamChatThemeData { ChannelTheme channelTheme, MessageTheme ownMessageTheme, MessageTheme otherMessageTheme, + Widget Function(BuildContext, Channel) defaultChannelImage, }) => StreamChatThemeData( primaryColor: primaryColor ?? this.primaryColor, secondaryColor: secondaryColor ?? this.secondaryColor, accentColor: accentColor ?? this.accentColor, + defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, channelPreviewTheme: channelPreviewTheme?.copyWith( title: channelPreviewTheme.title ?? this.channelPreviewTheme.title, @@ -170,6 +177,7 @@ class StreamChatThemeData { return StreamChatThemeData( accentColor: accentColor, primaryColor: isDark ? Colors.black : Colors.white, + defaultChannelImage: (context, channel) => SizedBox(), channelPreviewTheme: ChannelPreviewTheme( avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), From f4cedc2061781744de4ca9cbdeb8e064e5a45930 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 23 Apr 2020 15:26:55 +0200 Subject: [PATCH 078/133] add default user image to theme --- example/lib/main.dart | 7 ------- lib/src/stream_chat.dart | 1 + lib/src/stream_chat_theme.dart | 12 ++++++++++++ lib/src/user_avatar.dart | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index e9c61712..7de283d0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -92,13 +92,6 @@ class MyApp extends StatelessWidget { themeMode: ThemeMode.system, home: Container( child: StreamChat( - streamChatThemeData: StreamChatThemeData( - defaultChannelImage: (context, channel) { - return Center( - child: Text('AAA'), - ); - }, - ), client: client, child: ChannelListPage(), ), diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 6011a14c..be0afda7 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -113,6 +113,7 @@ class StreamChatState extends State with WidgetsBindingObserver { final theme = defaultTheme.copyWith( primaryColor: themeData?.primaryColor, defaultChannelImage: themeData?.defaultChannelImage, + defaultUserImage: themeData?.defaultUserImage, channelTheme: defaultTheme.channelTheme.copyWith( channelHeaderTheme: defaultTheme.channelTheme.channelHeaderTheme.copyWith( diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index 84f8a8f2..289f477d 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -54,6 +54,9 @@ class StreamChatThemeData { /// The widget that will be built when the channel image is unavailable final Widget Function(BuildContext, Channel) defaultChannelImage; + /// The widget that will be built when the user image is unavailable + final Widget Function(BuildContext, User) defaultUserImage; + /// Create a theme from scratch StreamChatThemeData({ this.primaryColor, @@ -64,6 +67,7 @@ class StreamChatThemeData { this.otherMessageTheme, this.ownMessageTheme, this.defaultChannelImage, + this.defaultUserImage, }); /// Create a theme from a Material [Theme] @@ -103,12 +107,14 @@ class StreamChatThemeData { MessageTheme ownMessageTheme, MessageTheme otherMessageTheme, Widget Function(BuildContext, Channel) defaultChannelImage, + Widget Function(BuildContext, User) defaultUserImage, }) => StreamChatThemeData( primaryColor: primaryColor ?? this.primaryColor, secondaryColor: secondaryColor ?? this.secondaryColor, accentColor: accentColor ?? this.accentColor, defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, + defaultUserImage: defaultUserImage ?? this.defaultUserImage, channelPreviewTheme: channelPreviewTheme?.copyWith( title: channelPreviewTheme.title ?? this.channelPreviewTheme.title, @@ -178,6 +184,12 @@ class StreamChatThemeData { accentColor: accentColor, primaryColor: isDark ? Colors.black : Colors.white, defaultChannelImage: (context, channel) => SizedBox(), + defaultUserImage: (context, user) => Center( + child: Text( + user.name?.substring(0, 1) ?? '', + style: TextStyle(color: Colors.white), + ), + ), channelPreviewTheme: ChannelPreviewTheme( avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), diff --git a/lib/src/user_avatar.dart b/lib/src/user_avatar.dart index aaaa6a27..2377411e 100644 --- a/lib/src/user_avatar.dart +++ b/lib/src/user_avatar.dart @@ -43,7 +43,7 @@ class UserAvatar extends StatelessWidget { }, fit: BoxFit.cover, ) - : SizedBox(), + : StreamChatTheme.of(context).defaultUserImage(context, user), ), ); } From 2737bf245c73383236006c228d113f3f1340fa28 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 23 Apr 2020 16:26:30 +0200 Subject: [PATCH 079/133] version bump --- CHANGELOG.md | 4 ++++ pubspec.yaml | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 894a5de8..26dd74cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0-alpha+8 + +- Add `attachmentBuilders` to `MessageWidget` and `MessageListView` + ## 0.2.0-alpha+7 - Update llc dependency diff --git a/pubspec.yaml b/pubspec.yaml index 09a7c642..7d5c6255 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+7 +version: 0.2.0-alpha+8 environment: sdk: ">=2.3.0 <3.0.0" @@ -20,8 +20,7 @@ dependencies: file_picker: ^1.6.3+2 image_picker: ^0.6.5 flutter_keyboard_visibility: ^2.0.0 - stream_chat: - path: ../stream_chat_dart + stream_chat: ^0.2.0-alpha+6 mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From 5df6c303ff7aaca521545c309f634635df239e3d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 23 Apr 2020 17:28:25 +0200 Subject: [PATCH 080/133] fix image rendering --- CHANGELOG.md | 2 +- example/lib/main.dart | 8 +++++--- lib/src/image_attachment.dart | 6 +++++- pubspec.yaml | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26dd74cd..76dcf368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.2.0-alpha+8 +## 0.2.0-alpha+9 - Add `attachmentBuilders` to `MessageWidget` and `MessageListView` diff --git a/example/lib/main.dart b/example/lib/main.dart index 7de283d0..ac895cfa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -65,15 +65,17 @@ void _initNotifications(Client client) { void main() async { final client = Client( - 'b67pax5b2wdq', + 's2dxdhpxd94g', logLevel: Level.INFO, + persistenceEnabled: false, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, ); await client.setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); + _initNotifications(client); runApp(MyApp(client)); diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index 6ff653ff..9fb7f05c 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -40,7 +40,11 @@ class ImageAttachment extends StatelessWidget { child: CachedNetworkImage( imageBuilder: (context, provider) { return GestureDetector( - child: Image(image: provider), + child: Image( + image: provider, + width: MediaQuery.of(context).size.width * 0.7, + fit: BoxFit.cover, + ), onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { return FullScreenImage( diff --git a/pubspec.yaml b/pubspec.yaml index 7d5c6255..f5755d39 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+8 +version: 0.2.0-alpha+9 environment: sdk: ">=2.3.0 <3.0.0" From c3a068a4b47de313eb7b454ef5fdc42a74de54d3 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 24 Apr 2020 16:12:11 +0200 Subject: [PATCH 081/133] add unread indicator --- lib/src/channel_preview.dart | 13 +++---------- lib/src/stream_chat.dart | 1 + lib/src/stream_chat_theme.dart | 5 +++++ lib/src/unread_indicator.dart | 28 ++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 lib/src/unread_indicator.dart diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 487df6bc..ff81cc7c 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/unread_indicator.dart'; import '../stream_chat_flutter.dart'; import 'channel_name.dart'; @@ -53,16 +54,8 @@ class ChannelPreview extends StatelessWidget { children: [ _buildDate(context), if (channel.state.unreadCount > 0) - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: CircleAvatar( - backgroundColor: Color(0xffd0021B), - radius: 6, - child: Text( - '${channel.state.unreadCount}', - style: TextStyle(fontSize: 8), - ), - ), + UnreadIndicator( + channel: channel, ), ], ), diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index be0afda7..1e92ad1e 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -172,6 +172,7 @@ class StreamChatState extends State with WidgetsBindingObserver { title: themeData?.channelPreviewTheme?.title, lastMessageAt: themeData?.channelPreviewTheme?.lastMessageAt, subtitle: themeData?.channelPreviewTheme?.subtitle, + unreadCounterColor: themeData?.channelPreviewTheme?.unreadCounterColor, ), ); return theme; diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index 289f477d..058d1baf 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -191,6 +191,7 @@ class StreamChatThemeData { ), ), channelPreviewTheme: ChannelPreviewTheme( + unreadCounterColor: Color(0xffd0021B), avatarTheme: AvatarTheme( borderRadius: BorderRadius.circular(20), constraints: BoxConstraints.tightFor( @@ -415,12 +416,14 @@ class ChannelPreviewTheme { final TextStyle subtitle; final TextStyle lastMessageAt; final AvatarTheme avatarTheme; + final Color unreadCounterColor; const ChannelPreviewTheme({ this.title, this.subtitle, this.lastMessageAt, this.avatarTheme, + this.unreadCounterColor, }); ChannelPreviewTheme copyWith({ @@ -428,12 +431,14 @@ class ChannelPreviewTheme { TextStyle subtitle, TextStyle lastMessageAt, AvatarTheme avatarTheme, + Color unreadCounterColor, }) => ChannelPreviewTheme( title: title ?? this.title, subtitle: subtitle ?? this.subtitle, lastMessageAt: lastMessageAt ?? this.lastMessageAt, avatarTheme: avatarTheme ?? this.avatarTheme, + unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor, ); } diff --git a/lib/src/unread_indicator.dart b/lib/src/unread_indicator.dart new file mode 100644 index 00000000..73f57d05 --- /dev/null +++ b/lib/src/unread_indicator.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; + +class UnreadIndicator extends StatelessWidget { + const UnreadIndicator({ + Key key, + @required this.channel, + }) : super(key: key); + + final Channel channel; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 8.0), + child: CircleAvatar( + backgroundColor: + StreamChatTheme.of(context).channelPreviewTheme.unreadCounterColor, + radius: 6, + child: Text( + '${channel.state.unreadCount}', + style: TextStyle(fontSize: 8), + ), + ), + ); + } +} From 9ec2b02a923ab1ee5041882adfaaf64ff4aa6604 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 24 Apr 2020 16:51:47 +0200 Subject: [PATCH 082/133] add datedivider builder --- lib/src/date_divider.dart | 70 +++++++++++++++------------------- lib/src/message_input.dart | 1 + lib/src/message_list_view.dart | 18 +++++++-- lib/src/message_widget.dart | 2 +- 4 files changed, 47 insertions(+), 44 deletions(-) diff --git a/lib/src/date_divider.dart b/lib/src/date_divider.dart index f1d074d1..1e00f2ae 100644 --- a/lib/src/date_divider.dart +++ b/lib/src/date_divider.dart @@ -1,18 +1,15 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// It shows a date divider depending on the date difference class DateDivider extends StatelessWidget { + final DateTime dateTime; + const DateDivider({ Key key, - @required this.nextMessage, - @required this.messageWidget, + @required this.dateTime, }) : super(key: key); - final Message nextMessage; - final Widget messageWidget; - @override Widget build(BuildContext context) { final divider = Expanded( @@ -22,7 +19,7 @@ class DateDivider extends StatelessWidget { ), ); - final createdAt = Jiffy(nextMessage.createdAt.toLocal()); + final createdAt = Jiffy(dateTime); final now = DateTime.now(); final hourInfo = createdAt.format('h:mm a'); @@ -46,46 +43,39 @@ class DateDivider extends StatelessWidget { dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase(); } - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - messageWidget, - Padding( - padding: const EdgeInsets.only(top: 24.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - divider, - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: Text.rich( + return Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + divider, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Text.rich( + TextSpan( + children: [ TextSpan( - children: [ - TextSpan( - text: dayInfo, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - TextSpan(text: ' AT'), - TextSpan(text: ' $hourInfo'), - ], + text: dayInfo, style: TextStyle( - fontWeight: FontWeight.normal, + fontWeight: FontWeight.bold, ), ), - style: TextStyle( - fontSize: 10, - color: - Theme.of(context).textTheme.title.color.withOpacity(.5), - ), + TextSpan(text: ' AT'), + TextSpan(text: ' $hourInfo'), + ], + style: TextStyle( + fontWeight: FontWeight.normal, ), ), - divider, - ], + style: TextStyle( + fontSize: 10, + color: Theme.of(context).textTheme.title.color.withOpacity(.5), + ), + ), ), - ), - ], + divider, + ], + ), ); } } diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 1c43c232..a2ef4abe 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -719,6 +719,7 @@ class _MessageInputState extends State { }, icon: Icon( Icons.send, + color: StreamChatTheme.of(context).accentColor, ), ), ), diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index da6a9c01..5c3e67d1 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -69,6 +69,7 @@ class MessageListView extends StatefulWidget { this.onMentionTap, this.onMessageActions, this.attachmentBuilders, + this.dateDividerBuilder, }) : super(key: key); /// Function used to build a custom message widget @@ -102,6 +103,9 @@ class MessageListView extends StatefulWidget { /// Map that defines a builder for an attachment type final Map attachmentBuilders; + /// Builder used to render date dividers + final Widget Function(DateTime) dateDividerBuilder; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -232,9 +236,17 @@ class _MessageListViewState extends State { if (nextMessage != null && !Jiffy(message.createdAt.toLocal()) .isSame(nextMessage.createdAt.toLocal(), Units.DAY)) { - return DateDivider( - messageWidget: messageWidget, - nextMessage: nextMessage, + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + messageWidget, + widget.dateDividerBuilder != null + ? widget + .dateDividerBuilder(nextMessage.createdAt.toLocal()) + : DateDivider( + dateTime: nextMessage.createdAt.toLocal(), + ), + ], ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index d1dafff7..49055937 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -237,7 +237,7 @@ class _MessageWidgetState extends State Widget _buildThreadIndicator(BuildContext context) { if (widget.message?.replyCount != null && widget.message.replyCount > 0) { return ReplyIndicator( - onTap: widget.isParent + onTap: !widget.isParent ? () { widget.onThreadTap(widget.message); } From ce34b6afa1acd99ff02631a3638ada71b01e5275 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 24 Apr 2020 15:26:56 +0200 Subject: [PATCH 083/133] Merge pull request #40 from nosmirck/feature/onTapUserMessage Added onUserAvatarTap --- lib/src/message_list_view.dart | 8 ++++ lib/src/message_widget.dart | 9 ++++- lib/src/user_avatar.dart | 68 ++++++++++++++++++++-------------- 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 5c3e67d1..b4373594 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -67,6 +67,7 @@ class MessageListView extends StatefulWidget { this.showOtherMessageUsername = false, this.showVideoFullScreen = true, this.onMentionTap, + this.onUserAvatarTap, this.onMessageActions, this.attachmentBuilders, this.dateDividerBuilder, @@ -97,6 +98,9 @@ class MessageListView extends StatefulWidget { /// Function called on message mention tap final void Function(User) onMentionTap; + /// Function called on User Avatar tap + final void Function(User) onUserAvatarTap; + /// Function called on message long press final Function(BuildContext, Message) onMessageActions; @@ -164,6 +168,7 @@ class _MessageListViewState extends State { showOtherMessageUsername: widget.showOtherMessageUsername, onMentionTap: widget.onMentionTap, + onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, ), @@ -227,6 +232,7 @@ class _MessageListViewState extends State { showOtherMessageUsername: widget.showOtherMessageUsername, showVideoFullScreen: widget.showVideoFullScreen, onMentionTap: widget.onMentionTap, + onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, ); @@ -314,6 +320,7 @@ class _MessageListViewState extends State { showVideoFullScreen: widget.showVideoFullScreen, showOtherMessageUsername: widget.showOtherMessageUsername, onMentionTap: widget.onMentionTap, + onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, ); @@ -354,6 +361,7 @@ class _MessageListViewState extends State { showVideoFullScreen: widget.showVideoFullScreen, showOtherMessageUsername: widget.showOtherMessageUsername, onMentionTap: widget.onMentionTap, + onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, ); diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 49055937..af5173d9 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -41,6 +41,7 @@ class MessageWidget extends StatefulWidget { @required this.message, @required this.nextMessage, this.onThreadTap, + this.onUserAvatarTap, this.onMessageActions, this.isParent = false, this.onMentionTap, @@ -70,6 +71,9 @@ class MessageWidget extends StatefulWidget { /// The function called when tapping on replies final void Function(Message) onThreadTap; + /// The function called when tapping on UserAvatar + final void Function(User) onUserAvatarTap; + /// True if this is the parent of the thread being showed final bool isParent; @@ -161,7 +165,10 @@ class _MessageWidgetState extends State ), child: Row( children: [ - UserAvatar(user: widget.message.user), + UserAvatar( + user: widget.message.user, + onTap: widget.onUserAvatarTap, + ), if (_isMyMessage && widget.nextMessage == null) SendingIndicator( message: widget.message, diff --git a/lib/src/user_avatar.dart b/lib/src/user_avatar.dart index 2377411e..8ea9a525 100644 --- a/lib/src/user_avatar.dart +++ b/lib/src/user_avatar.dart @@ -9,41 +9,55 @@ class UserAvatar extends StatelessWidget { Key key, @required this.user, this.constraints, + this.onTap, }) : super(key: key); final User user; final BoxConstraints constraints; + final void Function(User) onTap; @override Widget build(BuildContext context) { - return ClipRRect( - borderRadius: - StreamChatTheme.of(context).ownMessageTheme.avatarTheme.borderRadius, - child: Container( - constraints: constraints ?? - StreamChatTheme.of(context).ownMessageTheme.avatarTheme.constraints, - decoration: BoxDecoration( - color: StreamChatTheme.of(context).accentColor, - ), - child: user.extraData?.containsKey('image') ?? false - ? CachedNetworkImage( - imageUrl: user.extraData['image'], - errorWidget: (_, __, ___) { - return Center( - child: Text( - user.extraData?.containsKey('name') ?? false - ? user.extraData['name'][0] - : '', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, + return GestureDetector( + onTap: () { + if (onTap != null) { + onTap(user); + } + }, + child: ClipRRect( + borderRadius: StreamChatTheme.of(context) + .ownMessageTheme + .avatarTheme + .borderRadius, + child: Container( + constraints: constraints ?? + StreamChatTheme.of(context) + .ownMessageTheme + .avatarTheme + .constraints, + decoration: BoxDecoration( + color: StreamChatTheme.of(context).accentColor, + ), + child: user.extraData?.containsKey('image') ?? false + ? CachedNetworkImage( + imageUrl: user.extraData['image'], + errorWidget: (_, __, ___) { + return Center( + child: Text( + user.extraData?.containsKey('name') ?? false + ? user.extraData['name'][0] + : '', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), ), - ), - ); - }, - fit: BoxFit.cover, - ) - : StreamChatTheme.of(context).defaultUserImage(context, user), + ); + }, + fit: BoxFit.cover, + ) + : StreamChatTheme.of(context).defaultUserImage(context, user), + ), ), ); } From 3ea81c2519aeb0af23afe6f016d6d11b70381472 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 24 Apr 2020 16:54:24 +0200 Subject: [PATCH 084/133] bump version --- CHANGELOG.md | 6 ++++++ pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76dcf368..73a133d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.0-alpha+10 + +- Add date divider builder + +- Fix reply indicator tap + ## 0.2.0-alpha+9 - Add `attachmentBuilders` to `MessageWidget` and `MessageListView` diff --git a/pubspec.yaml b/pubspec.yaml index f5755d39..2b0ed83b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+9 +version: 0.2.0-alpha+10 environment: sdk: ">=2.3.0 <3.0.0" From 3d72ea699c0a8b5ac59e16e79b4999e69b9fe750 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 25 Apr 2020 14:10:16 +0200 Subject: [PATCH 085/133] add custom clipper to messagewidget --- example/lib/main.dart | 2 +- lib/src/channel_header.dart | 3 - lib/src/message_input.dart | 1 - lib/src/message_list_view.dart | 35 ++++++++++- lib/src/message_widget.dart | 105 ++++++++++++++++++++------------- lib/src/stream_chat.dart | 5 +- lib/src/stream_chat_theme.dart | 9 +++ 7 files changed, 111 insertions(+), 49 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index ac895cfa..012a6051 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -91,7 +91,7 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.system, + themeMode: ThemeMode.dark, home: Container( child: StreamChat( client: client, diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart index 5a7a146c..26668697 100644 --- a/lib/src/channel_header.dart +++ b/lib/src/channel_header.dart @@ -157,9 +157,6 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { child: Icon( Icons.arrow_back_ios, size: 15, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, ), ), ); diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index a2ef4abe..bb60d891 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -476,7 +476,6 @@ class _MessageInputState extends State { child: Icon( Icons.close, size: 15, - color: Colors.black, ), ), ), diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index b4373594..14366b1c 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -71,6 +71,8 @@ class MessageListView extends StatefulWidget { this.onMessageActions, this.attachmentBuilders, this.dateDividerBuilder, + this.showAvatar = true, + this.customClipperBuilder, }) : super(key: key); /// Function used to build a custom message widget @@ -110,6 +112,13 @@ class MessageListView extends StatefulWidget { /// Builder used to render date dividers final Widget Function(DateTime) dateDividerBuilder; + /// if true shows the user avatar + final bool showAvatar; + + /// Custom clipper applied to the message bubble + final CustomClipper Function(BuildContext, Message, int index) + customClipperBuilder; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -171,6 +180,14 @@ class _MessageListViewState extends State { onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, + showAvatar: widget.showAvatar, + customClipperBuilder: (message) { + return widget.customClipperBuilder( + context, + message, + i, + ); + }, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), @@ -235,6 +252,12 @@ class _MessageListViewState extends State { onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, + showAvatar: widget.showAvatar, + customClipperBuilder: (message) { + if (widget.customClipperBuilder != null) { + return widget.customClipperBuilder(context, message, i); + } + }, ); } } @@ -308,7 +331,8 @@ class _MessageListViewState extends State { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder(context, message, 0), + builder: (_) => + widget.messageBuilder(context, message, _messages.length - 1), ); } else { messageWidget = MessageWidget( @@ -323,6 +347,11 @@ class _MessageListViewState extends State { onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, + showAvatar: widget.showAvatar, + customClipperBuilder: (message) { + return widget.customClipperBuilder( + context, message, _messages.length - 1); + }, ); } @@ -364,6 +393,10 @@ class _MessageListViewState extends State { onUserAvatarTap: widget.onUserAvatarTap, onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, + showAvatar: widget.showAvatar, + customClipperBuilder: (message) { + return widget.customClipperBuilder(context, message, 0); + }, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index af5173d9..9ff08475 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -48,6 +48,8 @@ class MessageWidget extends StatefulWidget { this.showOtherMessageUsername = false, this.showVideoFullScreen = true, this.attachmentBuilders, + this.showAvatar = true, + this.customClipperBuilder, }) : super(key: key); /// Function called on mention tap @@ -83,6 +85,12 @@ class MessageWidget extends StatefulWidget { /// Map that defines a builder for an attachment type final Map attachmentBuilders; + /// if true shows the user avatar + final bool showAvatar; + + /// Custom clipper applied to the message bubble + final CustomClipper Function(Message) customClipperBuilder; + @override _MessageWidgetState createState() => _MessageWidgetState(); } @@ -130,7 +138,7 @@ class _MessageWidgetState extends State ), _isNextUser ? Container( - width: 40, + width: widget.showAvatar ? 40 : 8, ) : _buildUserAvatar(), ]); @@ -164,11 +172,13 @@ class _MessageWidgetState extends State right: _isMyMessage ? 0 : 8.0, ), child: Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - UserAvatar( - user: widget.message.user, - onTap: widget.onUserAvatarTap, - ), + if (widget.showAvatar) + UserAvatar( + user: widget.message.user, + onTap: widget.onUserAvatarTap, + ), if (_isMyMessage && widget.nextMessage == null) SendingIndicator( message: widget.message, @@ -466,7 +476,7 @@ class _MessageWidgetState extends State ); } - Padding _buildMessageText( + Widget _buildMessageText( int nOfAttachmentWidgets, String text, BuildContext context, @@ -476,46 +486,57 @@ class _MessageWidgetState extends State right: _isMyMessage ? 0.0 : 8.0, left: _isMyMessage ? 8.0 : 0.0, ), - child: Container( - decoration: - _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0), - padding: EdgeInsets.all(10), - constraints: BoxConstraints.loose( - Size.fromWidth(MediaQuery.of(context).size.width * 0.7), - ), - child: _buildSendingError( - MarkdownBody( - data: text, - onTapLink: (link) { - if (link.startsWith('@')) { - final mentionedUser = widget.message.mentionedUsers.firstWhere( - (u) => '@${u.name.replaceAll(' ', '')}' == link, - orElse: () => null, - ); + child: ClipPath( + clipper: widget.customClipperBuilder != null + ? widget.customClipperBuilder(widget.message) + : null, + clipBehavior: Clip.hardEdge, + child: Container( + decoration: + _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0) + .copyWith( + borderRadius: widget.customClipperBuilder != null + ? BorderRadius.circular(0) + : null), + padding: EdgeInsets.all(10), + constraints: BoxConstraints.loose( + Size.fromWidth(MediaQuery.of(context).size.width * 0.7), + ), + child: _buildSendingError( + MarkdownBody( + data: text, + onTapLink: (link) { + if (link.startsWith('@')) { + final mentionedUser = + widget.message.mentionedUsers.firstWhere( + (u) => '@${u.name.replaceAll(' ', '')}' == link, + orElse: () => null, + ); - if (widget.onMentionTap != null) { - widget.onMentionTap(mentionedUser); + if (widget.onMentionTap != null) { + widget.onMentionTap(mentionedUser); + } else { + print('tap on ${mentionedUser.name}'); + } } else { - print('tap on ${mentionedUser.name}'); + launchURL(context, link); } - } else { - launchURL(context, link); - } - }, - 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, - ), + }, + 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, + ), + ), + ).copyWith( + p: _messageTheme.messageText, ), - ).copyWith( - p: _messageTheme.messageText, ), ), ), diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 1e92ad1e..75a739a6 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -73,9 +73,11 @@ class StreamChatState extends State with WidgetsBindingObserver { builder: (context) { final materialTheme = Theme.of(context); final isDark = materialTheme.brightness == Brightness.dark; + final streamTheme = StreamChatTheme.of(context); return Theme( data: materialTheme.copyWith( - accentColor: StreamChatTheme.of(context).accentColor, + primaryIconTheme: streamTheme.primaryIconTheme, + accentColor: streamTheme.accentColor, scaffoldBackgroundColor: isDark ? Colors.black : Colors.white, backgroundColor: isDark ? Colors.black : Colors.white, ), @@ -113,6 +115,7 @@ class StreamChatState extends State with WidgetsBindingObserver { final theme = defaultTheme.copyWith( primaryColor: themeData?.primaryColor, defaultChannelImage: themeData?.defaultChannelImage, + primaryIconTheme: themeData?.primaryIconTheme, defaultUserImage: themeData?.defaultUserImage, channelTheme: defaultTheme.channelTheme.copyWith( channelHeaderTheme: diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index 058d1baf..acf7f4f0 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -57,6 +57,9 @@ class StreamChatThemeData { /// The widget that will be built when the user image is unavailable final Widget Function(BuildContext, User) defaultUserImage; + /// Primary icon theme + final IconThemeData primaryIconTheme; + /// Create a theme from scratch StreamChatThemeData({ this.primaryColor, @@ -68,6 +71,7 @@ class StreamChatThemeData { this.ownMessageTheme, this.defaultChannelImage, this.defaultUserImage, + this.primaryIconTheme, }); /// Create a theme from a Material [Theme] @@ -76,6 +80,7 @@ class StreamChatThemeData { return defaultTheme.copyWith( accentColor: theme.accentColor, + primaryIconTheme: theme.primaryIconTheme, primaryColor: theme.colorScheme.primary, secondaryColor: theme.colorScheme.secondary, channelTheme: ChannelTheme( @@ -108,10 +113,12 @@ class StreamChatThemeData { MessageTheme otherMessageTheme, Widget Function(BuildContext, Channel) defaultChannelImage, Widget Function(BuildContext, User) defaultUserImage, + IconThemeData primaryIconTheme, }) => StreamChatThemeData( primaryColor: primaryColor ?? this.primaryColor, secondaryColor: secondaryColor ?? this.secondaryColor, + primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme, accentColor: accentColor ?? this.accentColor, defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, defaultUserImage: defaultUserImage ?? this.defaultUserImage, @@ -183,6 +190,8 @@ class StreamChatThemeData { return StreamChatThemeData( accentColor: accentColor, primaryColor: isDark ? Colors.black : Colors.white, + primaryIconTheme: + IconThemeData(color: isDark ? Colors.white : Colors.black), defaultChannelImage: (context, channel) => SizedBox(), defaultUserImage: (context, user) => Center( child: Text( From 057d397d8ecd3c33c37f61a378330cf7ebc25593 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 28 Apr 2020 10:21:46 +0200 Subject: [PATCH 086/133] version bump --- CHANGELOG.md | 4 ++ example/lib/main.dart | 2 +- lib/src/message_list_view.dart | 40 ++++-------- lib/src/message_widget.dart | 115 ++++++++++++++++----------------- lib/src/thread_header.dart | 3 - lib/stream_chat_flutter.dart | 8 +++ pubspec.yaml | 2 +- 7 files changed, 79 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a133d1..ed391a1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0-alpha+11 + +- Fix message builder and add messageList to it + ## 0.2.0-alpha+10 - Add date divider builder diff --git a/example/lib/main.dart b/example/lib/main.dart index 012a6051..ac895cfa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -91,7 +91,7 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.dark, + themeMode: ThemeMode.system, home: Container( child: StreamChat( client: client, diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 14366b1c..077c5289 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -10,7 +10,8 @@ import 'date_divider.dart'; import 'message_widget.dart'; import 'stream_channel.dart'; -typedef MessageBuilder = Widget Function(BuildContext, Message, int index); +typedef MessageBuilder = Widget Function( + BuildContext, Message, List, int index); typedef ParentMessageBuilder = Widget Function(BuildContext, Message); typedef ThreadBuilder = Widget Function(BuildContext context, Message parent); typedef ThreadTapCallback = void Function(Message, Widget); @@ -72,7 +73,6 @@ class MessageListView extends StatefulWidget { this.attachmentBuilders, this.dateDividerBuilder, this.showAvatar = true, - this.customClipperBuilder, }) : super(key: key); /// Function used to build a custom message widget @@ -115,10 +115,6 @@ class MessageListView extends StatefulWidget { /// if true shows the user avatar final bool showAvatar; - /// Custom clipper applied to the message bubble - final CustomClipper Function(BuildContext, Message, int index) - customClipperBuilder; - @override _MessageListViewState createState() => _MessageListViewState(); } @@ -181,13 +177,6 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, - customClipperBuilder: (message) { - return widget.customClipperBuilder( - context, - message, - i, - ); - }, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), @@ -237,7 +226,8 @@ class _MessageListViewState extends State { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder(context, message, i), + builder: (_) => + widget.messageBuilder(context, message, _messages, i), ); } else { messageWidget = MessageWidget( @@ -253,11 +243,6 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, - customClipperBuilder: (message) { - if (widget.customClipperBuilder != null) { - return widget.customClipperBuilder(context, message, i); - } - }, ); } } @@ -331,8 +316,12 @@ class _MessageListViewState extends State { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => - widget.messageBuilder(context, message, _messages.length - 1), + builder: (_) => widget.messageBuilder( + context, + message, + _messages, + _messages.length - 1, + ), ); } else { messageWidget = MessageWidget( @@ -348,10 +337,6 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, - customClipperBuilder: (message) { - return widget.customClipperBuilder( - context, message, _messages.length - 1); - }, ); } @@ -378,7 +363,7 @@ class _MessageListViewState extends State { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder(context, message, 0), + builder: (_) => widget.messageBuilder(context, message, _messages, 0), ); } else { messageWidget = MessageWidget( @@ -394,9 +379,6 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, - customClipperBuilder: (message) { - return widget.customClipperBuilder(context, message, 0); - }, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 9ff08475..49b8fc10 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -49,7 +51,6 @@ class MessageWidget extends StatefulWidget { this.showVideoFullScreen = true, this.attachmentBuilders, this.showAvatar = true, - this.customClipperBuilder, }) : super(key: key); /// Function called on mention tap @@ -88,9 +89,6 @@ class MessageWidget extends StatefulWidget { /// if true shows the user avatar final bool showAvatar; - /// Custom clipper applied to the message bubble - final CustomClipper Function(Message) customClipperBuilder; - @override _MessageWidgetState createState() => _MessageWidgetState(); } @@ -486,57 +484,46 @@ class _MessageWidgetState extends State right: _isMyMessage ? 0.0 : 8.0, left: _isMyMessage ? 8.0 : 0.0, ), - child: ClipPath( - clipper: widget.customClipperBuilder != null - ? widget.customClipperBuilder(widget.message) - : null, - clipBehavior: Clip.hardEdge, - child: Container( - decoration: - _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0) - .copyWith( - borderRadius: widget.customClipperBuilder != null - ? BorderRadius.circular(0) - : null), - padding: EdgeInsets.all(10), - constraints: BoxConstraints.loose( - Size.fromWidth(MediaQuery.of(context).size.width * 0.7), - ), - child: _buildSendingError( - MarkdownBody( - data: text, - onTapLink: (link) { - if (link.startsWith('@')) { - final mentionedUser = - widget.message.mentionedUsers.firstWhere( - (u) => '@${u.name.replaceAll(' ', '')}' == link, - orElse: () => null, - ); + child: Container( + decoration: + _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0), + padding: EdgeInsets.all(10), + constraints: BoxConstraints.loose( + Size.fromWidth(MediaQuery.of(context).size.width * 0.7), + ), + child: _buildSendingError( + MarkdownBody( + data: text, + onTapLink: (link) { + if (link.startsWith('@')) { + final mentionedUser = widget.message.mentionedUsers.firstWhere( + (u) => '@${u.name.replaceAll(' ', '')}' == link, + orElse: () => null, + ); - if (widget.onMentionTap != null) { - widget.onMentionTap(mentionedUser); - } else { - print('tap on ${mentionedUser.name}'); - } + if (widget.onMentionTap != null) { + widget.onMentionTap(mentionedUser); } else { - launchURL(context, link); + print('tap on ${mentionedUser.name}'); } - }, - 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, - ), - ), - ).copyWith( - p: _messageTheme.messageText, + } else { + launchURL(context, link); + } + }, + 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, + ), ), + ).copyWith( + p: _messageTheme.messageText, ), ), ), @@ -555,14 +542,19 @@ class _MessageWidgetState extends State Widget _buildReactionPaint() { return widget.message.reactionCounts?.isNotEmpty == true ? Positioned( - left: _isMyMessage ? 8 : null, - right: !_isMyMessage ? 8 : null, + left: _isMyMessage ? 4 : null, + right: !_isMyMessage ? 4 : null, top: -6, - child: CustomPaint( - painter: _ReactionBubblePainter( - Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, + child: Transform( + transform: + !_isMyMessage ? Matrix4.rotationY(pi) : Matrix4.identity(), + alignment: Alignment.center, + child: CustomPaint( + painter: _ReactionBubblePainter( + Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black, + ), ), ), ) @@ -895,9 +887,10 @@ class _ReactionBubblePainter extends CustomPainter { void paint(Canvas canvas, Size size) { final paint = Paint()..color = color; final path = Path(); - path.arcToPoint(Offset(-6, -6)); - path.arcToPoint(Offset(0, 10)); - path.arcToPoint(Offset(6, -6)); + path.lineTo(-2, -6); + path.lineTo(0, 10); + path.lineTo(10, -6); + path.lineTo(-2, -6); canvas.drawPath(path, paint); } diff --git a/lib/src/thread_header.dart b/lib/src/thread_header.dart index 19a576c8..ebff1b02 100644 --- a/lib/src/thread_header.dart +++ b/lib/src/thread_header.dart @@ -130,9 +130,6 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { child: Icon( Icons.close, size: 15, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, ), ), ), diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 38e0dac3..09c67b6d 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -7,12 +7,20 @@ export 'src/channel_name.dart'; export 'src/channel_preview.dart'; export 'src/channels_bloc.dart'; export 'src/date_divider.dart'; +export 'src/deleted_message.dart'; +export 'src/file_attachment.dart'; +export 'src/giphy_attachment.dart'; +export 'src/image_attachment.dart'; export 'src/message_input.dart'; export 'src/message_list_view.dart'; export 'src/message_widget.dart'; export 'src/reaction_picker.dart'; +export 'src/reply_indicator.dart'; +export 'src/sending_indicator.dart'; export 'src/stream_channel.dart'; export 'src/stream_chat.dart'; export 'src/stream_chat_theme.dart'; export 'src/thread_header.dart'; export 'src/typing_indicator.dart'; +export 'src/user_avatar.dart'; +export 'src/video_attachment.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 2b0ed83b..9848410a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+10 +version: 0.2.0-alpha+11 environment: sdk: ">=2.3.0 <3.0.0" From aca29c10914e43ebf2b2f672058a6d9229fbc940 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 28 Apr 2020 11:15:46 +0200 Subject: [PATCH 087/133] handle channel.deleted --- lib/src/channels_bloc.dart | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart index 9dfafbc2..6acb04cc 100644 --- a/lib/src/channels_bloc.dart +++ b/lib/src/channels_bloc.dart @@ -104,14 +104,15 @@ class ChannelsBlocState extends State } } - StreamSubscription _newMessagesSubscription; + final List _subscriptions = []; @override void initState() { super.initState(); - _newMessagesSubscription = - StreamChat.of(context).client.on(EventType.messageNew).listen((e) { + final client = StreamChat.of(context).client; + + _subscriptions.add(client.on(EventType.messageNew).listen((e) { final newChannels = List.from(channels ?? []); final index = newChannels.indexWhere((c) => c.cid == e.cid); if (index > 0) { @@ -119,14 +120,20 @@ class ChannelsBlocState extends State newChannels.insert(0, channel); _channelsController.add(newChannels); } - }); + })); + + _subscriptions.add(client.on(EventType.channelDeleted).listen((e) { + final channel = e.channel; + _channelsController + .add(channels..removeWhere((c) => c.cid == channel.cid)); + })); } @override void dispose() { _channelsController.close(); _queryChannelsLoadingController.close(); - _newMessagesSubscription.cancel(); + _subscriptions.forEach((s) => s.cancel()); super.dispose(); } From 71577f884b38c17db141d19a87106a3942ac891b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 28 Apr 2020 16:38:07 +0200 Subject: [PATCH 088/133] add actions parameter to messageinput --- lib/src/message_input.dart | 61 +++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index bb60d891..87a67d30 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -18,6 +18,11 @@ import 'stream_channel.dart'; typedef FileUploader = Future Function(File, Channel); +enum ActionsLocation { + LEFT, + RIGHT, +} + /// Inactive state /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input_paint.png) @@ -73,6 +78,9 @@ class MessageInput extends StatefulWidget { this.doImageUploadRequest, this.doFileUploadRequest, this.initialMessage, + this.textEditingController, + this.actions, + this.actionsLocation = ActionsLocation.LEFT, }) : super(key: key); /// Message to edit @@ -102,6 +110,15 @@ class MessageInput extends StatefulWidget { /// Override file upload request final FileUploader doFileUploadRequest; + /// The text controller of the TextField + final TextEditingController textEditingController; + + /// List of action widgets + final List actions; + + /// The location of the custom actions + final ActionsLocation actionsLocation; + @override _MessageInputState createState() => _MessageInputState( doFileUploadRequest: doFileUploadRequest, @@ -116,7 +133,7 @@ class _MessageInputState extends State { FileUploader doImageUploadRequest; FileUploader doFileUploadRequest; - TextEditingController _textController; + TextEditingController textEditingController; bool _inputEnabled = true; bool _messageIsPresent = false; bool _typingStarted = false; @@ -165,8 +182,12 @@ class _MessageInputState extends State { crossAxisAlignment: CrossAxisAlignment.end, children: [ if (!widget.disableAttachments) _buildAttachmentButton(), + if (widget.actionsLocation == ActionsLocation.LEFT) + ...widget.actions ?? [], _buildTextInput(context), _animateSendButton(context), + if (widget.actionsLocation == ActionsLocation.RIGHT) + ...widget.actions ?? [], ], ); } @@ -197,7 +218,7 @@ class _MessageInputState extends State { _sendMessage(context); }, keyboardType: widget.keyboardType, - controller: _textController, + controller: textEditingController, focusNode: _focusNode, onChanged: (s) { StreamChannel.of(context).channel.keyStroke(); @@ -216,10 +237,10 @@ class _MessageInputState extends State { Overlay.of(context).insert(_commandsOverlay); } - if (_textController.selection.isCollapsed && - (s[_textController.selection.start - 1] == '@' || - _textController.text - .substring(0, _textController.selection.start) + if (textEditingController.selection.isCollapsed && + (s[textEditingController.selection.start - 1] == '@' || + textEditingController.text + .substring(0, textEditingController.selection.start) .split(' ') .last .contains('@'))) { @@ -279,7 +300,7 @@ class _MessageInputState extends State { } OverlayEntry _buildCommandsOverlayEntry() { - final text = _textController.text; + final text = textEditingController.text; final commands = StreamChannel.of(context) .channel .config @@ -344,8 +365,8 @@ class _MessageInputState extends State { } OverlayEntry _buildMentionsOverlayEntry() { - final splits = _textController.text - .substring(0, _textController.value.selection.start) + final splits = textEditingController.text + .substring(0, textEditingController.value.selection.start) .split('@'); final query = splits.last.toLowerCase(); @@ -391,10 +412,10 @@ class _MessageInputState extends State { splits[splits.length - 1] = m.user.name; final rejoin = splits.join('@'); - _textController.value = TextEditingValue( + textEditingController.value = TextEditingValue( text: rejoin + - _textController.text - .substring(_textController.selection.start), + textEditingController.text.substring( + textEditingController.selection.start), selection: TextSelection.collapsed( offset: rejoin.length, ), @@ -413,7 +434,7 @@ class _MessageInputState extends State { } void _setCommand(Command c) { - _textController.value = TextEditingValue( + textEditingController.value = TextEditingValue( text: '/${c.name} ', selection: TextSelection.collapsed( offset: c.name.length + 2, @@ -726,14 +747,14 @@ class _MessageInputState extends State { } void _sendMessage(BuildContext context) { - final text = _textController.text.trim(); + final text = textEditingController.text.trim(); if (text.isEmpty && _attachments.isEmpty) { return; } final attachments = List<_SendingAttachment>.from(_attachments); - _textController.clear(); + textEditingController.clear(); _attachments.clear(); setState(() { @@ -821,7 +842,7 @@ class _MessageInputState extends State { _keyboardListener = KeyboardVisibility.onChange.listen((visible) { if (visible) { if (_commandsOverlay != null) { - if (_textController.text.startsWith('/')) { + if (textEditingController.text.startsWith('/')) { WidgetsBinding.instance.addPostFrameCallback((_) { _commandsOverlay = _buildCommandsOverlayEntry(); Overlay.of(context).insert(_commandsOverlay); @@ -830,7 +851,7 @@ class _MessageInputState extends State { } if (_mentionsOverlay != null) { - if (_textController.text.contains('@')) { + if (textEditingController.text.contains('@')) { WidgetsBinding.instance.addPostFrameCallback((_) { _mentionsOverlay = _buildCommandsOverlayEntry(); Overlay.of(context).insert(_mentionsOverlay); @@ -847,17 +868,17 @@ class _MessageInputState extends State { } }); + textEditingController = + widget.textEditingController ?? TextEditingController(); if (widget.editMessage != null) { _parseExistingMessage(widget.editMessage); } else if (widget.initialMessage != null) { _parseExistingMessage(widget.initialMessage); - } else { - _textController = TextEditingController(); } } void _parseExistingMessage(Message message) { - _textController = TextEditingController(text: message.text); + textEditingController.text = message.text; _typingStarted = true; _messageIsPresent = true; From cd1ae7d2effa655b9db0f8c54cef1d2f5c2bee85 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 28 Apr 2020 16:59:21 +0200 Subject: [PATCH 089/133] version bump --- CHANGELOG.md | 4 ++++ pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed391a1a..1ca876f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0-alpha+13 + +- Handle channel deleted event + ## 0.2.0-alpha+11 - Fix message builder and add messageList to it diff --git a/pubspec.yaml b/pubspec.yaml index 9848410a..7644af24 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+11 +version: 0.2.0-alpha+13 environment: sdk: ">=2.3.0 <3.0.0" @@ -20,7 +20,7 @@ dependencies: file_picker: ^1.6.3+2 image_picker: ^0.6.5 flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+6 + stream_chat: ^0.2.0-alpha+7 mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From ff77d571c5a67028526bd9a5d3ea163fa3c1dfd5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 11:38:54 +0200 Subject: [PATCH 090/133] add AttachmentThumbnailBuilder and expose messageinputstate --- lib/src/message_input.dart | 248 +++++++++++++++++++++---------------- 1 file changed, 143 insertions(+), 105 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 87a67d30..a777c8b8 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -17,6 +17,8 @@ import '../stream_chat_flutter.dart'; import 'stream_channel.dart'; typedef FileUploader = Future Function(File, Channel); +typedef AttachmentThumbnailBuilder = Widget Function( + BuildContext, _SendingAttachment); enum ActionsLocation { LEFT, @@ -70,6 +72,7 @@ class MessageInput extends StatefulWidget { MessageInput({ Key key, this.onMessageSent, + this.preMessageSending, this.parentMessage, this.editMessage, this.maxHeight = 150, @@ -81,6 +84,7 @@ class MessageInput extends StatefulWidget { this.textEditingController, this.actions, this.actionsLocation = ActionsLocation.LEFT, + this.attachmentThumbnailBuilder, }) : super(key: key); /// Message to edit @@ -92,6 +96,10 @@ class MessageInput extends StatefulWidget { /// Function called after sending the message final void Function(Message) onMessageSent; + /// Function called right before sending the message + /// Use this to transform the message + final FutureOr Function(Message) preMessageSending; + /// Parent message in case of a thread final Message parentMessage; @@ -119,14 +127,31 @@ class MessageInput extends StatefulWidget { /// The location of the custom actions final ActionsLocation actionsLocation; + /// Map that defines a builder for an attachment type + final Map attachmentThumbnailBuilder; + @override - _MessageInputState createState() => _MessageInputState( + MessageInputState createState() => MessageInputState( doFileUploadRequest: doFileUploadRequest, doImageUploadRequest: doImageUploadRequest, ); + + /// Use this method to get the current [StreamChatState] instance + static MessageInputState of(BuildContext context) { + MessageInputState messageInputState; + + messageInputState = context.findAncestorStateOfType(); + + if (messageInputState == null) { + throw Exception( + 'You must have a MessageInput widget as anchestor of your widget tree'); + } + + return messageInputState; + } } -class _MessageInputState extends State { +class MessageInputState extends State { final List<_SendingAttachment> _attachments = []; final _focusNode = FocusNode(); final List _mentionedUsers = []; @@ -139,7 +164,7 @@ class _MessageInputState extends State { bool _typingStarted = false; OverlayEntry _commandsOverlay, _mentionsOverlay; - _MessageInputState({ + MessageInputState({ this.doImageUploadRequest, this.doFileUploadRequest, }) { @@ -215,7 +240,7 @@ class _MessageInputState extends State { minLines: null, maxLines: null, onSubmitted: (_) { - _sendMessage(context); + sendMessage(); }, keyboardType: widget.keyboardType, controller: textEditingController, @@ -473,34 +498,7 @@ class _MessageInputState extends State { width: 50, child: _buildAttachment(attachment), ), - Positioned( - height: 16, - width: 16, - top: 4, - right: 4, - child: RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - disabledElevation: 0, - hoverElevation: 0, - onPressed: () { - setState(() { - _attachments.remove(attachment); - }); - }, - fillColor: Colors.white.withOpacity(.5), - child: Center( - child: Icon( - Icons.close, - size: 15, - ), - ), - ), - ), + _buildRemoveButton(attachment), attachment.uploaded ? SizedBox() : Positioned.fill( @@ -520,20 +518,59 @@ class _MessageInputState extends State { ); } + Positioned _buildRemoveButton(_SendingAttachment attachment) { + return Positioned( + height: 16, + width: 16, + top: 4, + right: 4, + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + disabledElevation: 0, + hoverElevation: 0, + onPressed: () { + setState(() { + _attachments.remove(attachment); + }); + }, + fillColor: Colors.white.withOpacity(.5), + child: Center( + child: Icon( + Icons.close, + size: 15, + ), + ), + ), + ); + } + Widget _buildAttachment(_SendingAttachment attachment) { - switch (attachment.type) { - case FileType.image: + if (widget.attachmentThumbnailBuilder + ?.containsKey(attachment.attachment.type) == + true) { + return widget.attachmentThumbnailBuilder[attachment.attachment.type]( + context, + attachment, + ); + } + switch (attachment.attachment.type) { + case 'image': return attachment.file != null ? Image.file( attachment.file, fit: BoxFit.cover, ) : Image.network( - attachment.url, + attachment.attachment.imageUrl, fit: BoxFit.cover, ); break; - case FileType.video: + case 'video': return Container( child: Icon(Icons.videocam), color: Colors.black26, @@ -596,7 +633,7 @@ class _MessageInputState extends State { leading: Icon(Icons.image), title: Text('Upload a photo'), onTap: () { - _pickFile(FileType.image, false); + pickFile('image', false); Navigator.pop(context); }, ), @@ -604,7 +641,7 @@ class _MessageInputState extends State { leading: Icon(Icons.video_library), title: Text('Upload a video'), onTap: () { - _pickFile(FileType.video, false); + pickFile('video', false); Navigator.pop(context); }, ), @@ -612,7 +649,7 @@ class _MessageInputState extends State { leading: Icon(Icons.camera_alt), title: Text('Photo from camera'), onTap: () { - _pickFile(FileType.image, true); + pickFile('image', true); Navigator.pop(context); }, ), @@ -620,7 +657,7 @@ class _MessageInputState extends State { leading: Icon(Icons.videocam), title: Text('Video from camera'), onTap: () { - _pickFile(FileType.video, true); + pickFile('video', true); Navigator.pop(context); }, ), @@ -628,7 +665,7 @@ class _MessageInputState extends State { leading: Icon(Icons.insert_drive_file), title: Text('Upload a file'), onTap: () { - _pickFile(FileType.any, false); + pickFile('file', false); Navigator.pop(context); }, ), @@ -637,12 +674,34 @@ class _MessageInputState extends State { }); } - void _pickFile(FileType type, bool camera) async { + /// Add an attachment to the sending message + /// Use this to add custom type attachments + void addAttachment(Attachment attachment) { + setState(() { + _attachments.add(_SendingAttachment( + attachment: attachment, + uploaded: true, + )); + }); + } + + /// Pick a file from the device + /// The [attachmentType] should be one of 'image', 'video' or 'file' + /// If [camera] is true then the camera will open + void pickFile(String attachmentType, [bool camera = false]) async { setState(() { _inputEnabled = false; }); File file; + FileType type; + if (attachmentType == 'image') { + type = FileType.image; + } else if (attachmentType == 'video') { + type = FileType.video; + } else if (attachmentType == 'file') { + type = FileType.any; + } if (camera) { if (type == FileType.image) { @@ -666,7 +725,10 @@ class _MessageInputState extends State { final attachment = _SendingAttachment( file: file, - type: type, + attachment: Attachment( + localUri: file.uri, + type: attachmentType, + ), ); setState(() { @@ -675,7 +737,15 @@ class _MessageInputState extends State { final url = await _uploadAttachment(file, type, channel); - attachment.url = url; + if (attachmentType == 'image') { + attachment.attachment = attachment.attachment.copyWith( + imageUrl: url, + ); + } else { + attachment.attachment = attachment.attachment.copyWith( + assetUrl: url, + ); + } setState(() { attachment.uploaded = true; @@ -735,7 +805,7 @@ class _MessageInputState extends State { child: IconButton( key: Key('sendButton'), onPressed: () { - _sendMessage(context); + sendMessage(); }, icon: Icon( Icons.send, @@ -746,7 +816,8 @@ class _MessageInputState extends State { ); } - void _sendMessage(BuildContext context) { + /// Sends the current message + void sendMessage() async { final text = textEditingController.text.trim(); if (text.isEmpty && _attachments.isEmpty) { return; @@ -782,15 +853,6 @@ class _MessageInputState extends State { mentionedUsers: _mentionedUsers.where((u) => text.contains('@${u.name}')).toList(), ); - - if (widget.editMessage.status == MessageSendingStatus.FAILED) { - sendingFuture = channel.sendMessage(message); - } - - sendingFuture = StreamChat.of(context).client.updateMessage( - message, - channel.cid, - ); } else { message = (widget.initialMessage ?? Message()).copyWith( parentId: widget.parentMessage?.id, @@ -799,10 +861,23 @@ class _MessageInputState extends State { mentionedUsers: _mentionedUsers.where((u) => text.contains('@${u.name}')).toList(), ); - sendingFuture = channel.sendMessage(message); } - sendingFuture.whenComplete(() { + if (widget.preMessageSending != null) { + message = await widget.preMessageSending(message); + } + + if (widget.editMessage == null || + widget.editMessage.status == MessageSendingStatus.FAILED) { + sendingFuture = channel.sendMessage(message); + } else { + sendingFuture = StreamChat.of(context).client.updateMessage( + message, + channel.cid, + ); + } + + return sendingFuture.whenComplete(() { if (widget.onMessageSent != null) { widget.onMessageSent(message); } @@ -811,23 +886,7 @@ class _MessageInputState extends State { Iterable _getAttachments(List<_SendingAttachment> attachments) { return attachments.map((attachment) { - String type; - switch (attachment.type) { - case FileType.image: - type = 'image'; - break; - case FileType.video: - type = 'video'; - break; - default: - type = 'file'; - } - return Attachment( - imageUrl: attachment.type == FileType.image ? attachment.url : null, - assetUrl: attachment.url, - type: type, - localUri: attachment.file.uri, - ); + return attachment.attachment; }); } @@ -870,10 +929,9 @@ class _MessageInputState extends State { textEditingController = widget.textEditingController ?? TextEditingController(); - if (widget.editMessage != null) { - _parseExistingMessage(widget.editMessage); - } else if (widget.initialMessage != null) { - _parseExistingMessage(widget.initialMessage); + if (widget.editMessage != null || widget.initialMessage != null) { + _parseExistingMessage( + widget.editMessage ?? widget.initialMessage != null); } } @@ -884,28 +942,10 @@ class _MessageInputState extends State { _messageIsPresent = true; message.attachments?.forEach((attachment) { - if (attachment.type == 'image') { - _attachments.add(_SendingAttachment( - type: FileType.image, - url: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl ?? - attachment.ogScrapeUrl, - uploaded: true, - )); - } else if (attachment.type == 'video') { - _attachments.add(_SendingAttachment( - type: FileType.video, - url: attachment.assetUrl, - uploaded: true, - )); - } else if (attachment.type != 'giphy') { - _attachments.add(_SendingAttachment( - type: FileType.any, - url: attachment.assetUrl, - uploaded: true, - )); - } + _attachments.add(_SendingAttachment( + attachment: attachment, + uploaded: true, + )); }); } @@ -929,15 +969,13 @@ class _MessageInputState extends State { } class _SendingAttachment { - final File file; - final FileType type; - String url; + File file; + Attachment attachment; bool uploaded; _SendingAttachment({ - this.url, this.file, - this.type, + this.attachment, this.uploaded = false, }); } From cc876a9b2d62134bcf3be290c3b59795717b92b9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 12:17:22 +0200 Subject: [PATCH 091/133] add DefaultAttachmentTypes --- lib/src/message_input.dart | 78 +++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index a777c8b8..8caacedf 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -18,11 +18,19 @@ import 'stream_channel.dart'; typedef FileUploader = Future Function(File, Channel); typedef AttachmentThumbnailBuilder = Widget Function( - BuildContext, _SendingAttachment); + BuildContext, + _SendingAttachment, +); enum ActionsLocation { - LEFT, - RIGHT, + left, + right, +} + +enum DefaultAttachmentTypes { + image, + video, + file, } /// Inactive state @@ -83,7 +91,7 @@ class MessageInput extends StatefulWidget { this.initialMessage, this.textEditingController, this.actions, - this.actionsLocation = ActionsLocation.LEFT, + this.actionsLocation = ActionsLocation.left, this.attachmentThumbnailBuilder, }) : super(key: key); @@ -207,11 +215,11 @@ class MessageInputState extends State { crossAxisAlignment: CrossAxisAlignment.end, children: [ if (!widget.disableAttachments) _buildAttachmentButton(), - if (widget.actionsLocation == ActionsLocation.LEFT) + if (widget.actionsLocation == ActionsLocation.left) ...widget.actions ?? [], _buildTextInput(context), _animateSendButton(context), - if (widget.actionsLocation == ActionsLocation.RIGHT) + if (widget.actionsLocation == ActionsLocation.right) ...widget.actions ?? [], ], ); @@ -558,15 +566,18 @@ class MessageInputState extends State { attachment, ); } + switch (attachment.attachment.type) { case 'image': + case 'giphy': return attachment.file != null ? Image.file( attachment.file, fit: BoxFit.cover, ) : Image.network( - attachment.attachment.imageUrl, + attachment.attachment.imageUrl ?? + attachment.attachment.thumbUrl, fit: BoxFit.cover, ); break; @@ -633,7 +644,7 @@ class MessageInputState extends State { leading: Icon(Icons.image), title: Text('Upload a photo'), onTap: () { - pickFile('image', false); + pickFile(DefaultAttachmentTypes.image, false); Navigator.pop(context); }, ), @@ -641,7 +652,7 @@ class MessageInputState extends State { leading: Icon(Icons.video_library), title: Text('Upload a video'), onTap: () { - pickFile('video', false); + pickFile(DefaultAttachmentTypes.video, false); Navigator.pop(context); }, ), @@ -649,7 +660,7 @@ class MessageInputState extends State { leading: Icon(Icons.camera_alt), title: Text('Photo from camera'), onTap: () { - pickFile('image', true); + pickFile(DefaultAttachmentTypes.image, true); Navigator.pop(context); }, ), @@ -657,7 +668,7 @@ class MessageInputState extends State { leading: Icon(Icons.videocam), title: Text('Video from camera'), onTap: () { - pickFile('video', true); + pickFile(DefaultAttachmentTypes.video, true); Navigator.pop(context); }, ), @@ -665,7 +676,7 @@ class MessageInputState extends State { leading: Icon(Icons.insert_drive_file), title: Text('Upload a file'), onTap: () { - pickFile('file', false); + pickFile(DefaultAttachmentTypes.file, false); Navigator.pop(context); }, ), @@ -686,30 +697,38 @@ class MessageInputState extends State { } /// Pick a file from the device - /// The [attachmentType] should be one of 'image', 'video' or 'file' /// If [camera] is true then the camera will open - void pickFile(String attachmentType, [bool camera = false]) async { + void pickFile(DefaultAttachmentTypes fileType, [bool camera = false]) async { setState(() { _inputEnabled = false; }); File file; - FileType type; - if (attachmentType == 'image') { - type = FileType.image; - } else if (attachmentType == 'video') { - type = FileType.video; - } else if (attachmentType == 'file') { - type = FileType.any; + String attachmentType; + + if (fileType == DefaultAttachmentTypes.image) { + attachmentType = 'image'; + } else if (fileType == DefaultAttachmentTypes.video) { + attachmentType = 'video'; + } else if (fileType == DefaultAttachmentTypes.file) { + attachmentType = 'file'; } if (camera) { - if (type == FileType.image) { + if (fileType == DefaultAttachmentTypes.image) { file = await ImagePicker.pickImage(source: ImageSource.camera); - } else if (type == FileType.video) { + } else if (fileType == DefaultAttachmentTypes.video) { file = await ImagePicker.pickVideo(source: ImageSource.camera); } } else { + FileType type; + if (fileType == DefaultAttachmentTypes.image) { + type = FileType.image; + } else if (fileType == DefaultAttachmentTypes.video) { + type = FileType.video; + } else if (fileType == DefaultAttachmentTypes.file) { + type = FileType.any; + } file = await FilePicker.getFile(type: type); } @@ -735,9 +754,9 @@ class MessageInputState extends State { _attachments.add(attachment); }); - final url = await _uploadAttachment(file, type, channel); + final url = await _uploadAttachment(file, fileType, channel); - if (attachmentType == 'image') { + if (fileType == DefaultAttachmentTypes.image) { attachment.attachment = attachment.attachment.copyWith( imageUrl: url, ); @@ -754,11 +773,11 @@ class MessageInputState extends State { Future _uploadAttachment( File file, - FileType type, + DefaultAttachmentTypes type, Channel channel, ) async { String url; - if (type == FileType.image) { + if (type == DefaultAttachmentTypes.image) { url = await doImageUploadRequest(file, channel); } else { url = await doFileUploadRequest(file, channel); @@ -846,10 +865,7 @@ class MessageInputState extends State { message = widget.editMessage.copyWith( parentId: widget.parentMessage?.id, text: text, - attachments: widget.editMessage.attachments - .where((attachment) => attachment.type == 'giphy') - .toList() + - _getAttachments(attachments).toList(), + attachments: _getAttachments(attachments).toList(), mentionedUsers: _mentionedUsers.where((u) => text.contains('@${u.name}')).toList(), ); From 6d45e2d9bb46e556950ca43204f6e115d3de6a57 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 14:02:42 +0200 Subject: [PATCH 092/133] expose showAttachmentModal --- lib/src/message_input.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 8caacedf..2d874bc9 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -604,7 +604,7 @@ class MessageInputState extends State { color: Colors.transparent, child: IconButton( onPressed: () { - _showAttachmentModal(); + showAttachmentModal(); }, icon: Icon( Icons.add_circle_outline, @@ -613,7 +613,7 @@ class MessageInputState extends State { ); } - void _showAttachmentModal() { + void showAttachmentModal() { if (_focusNode.hasFocus) { _focusNode.unfocus(); } From 9709010513de2400c586468803ccd29b551987c8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 14:08:19 +0200 Subject: [PATCH 093/133] cleanup --- lib/src/message_input.dart | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 2d874bc9..d70bd201 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -139,10 +139,7 @@ class MessageInput extends StatefulWidget { final Map attachmentThumbnailBuilder; @override - MessageInputState createState() => MessageInputState( - doFileUploadRequest: doFileUploadRequest, - doImageUploadRequest: doImageUploadRequest, - ); + MessageInputState createState() => MessageInputState(); /// Use this method to get the current [StreamChatState] instance static MessageInputState of(BuildContext context) { @@ -163,22 +160,14 @@ class MessageInputState extends State { final List<_SendingAttachment> _attachments = []; final _focusNode = FocusNode(); final List _mentionedUsers = []; - FileUploader doImageUploadRequest; - FileUploader doFileUploadRequest; - TextEditingController textEditingController; bool _inputEnabled = true; bool _messageIsPresent = false; bool _typingStarted = false; OverlayEntry _commandsOverlay, _mentionsOverlay; - MessageInputState({ - this.doImageUploadRequest, - this.doFileUploadRequest, - }) { - doImageUploadRequest ??= _uploadImage; - doFileUploadRequest ??= _uploadFile; - } + /// The editing controller passed to the input TextField + TextEditingController textEditingController; @override Widget build(BuildContext context) { @@ -613,6 +602,7 @@ class MessageInputState extends State { ); } + /// Show the attachment modal, making the user choose where to pick a media from void showAttachmentModal() { if (_focusNode.hasFocus) { _focusNode.unfocus(); @@ -778,9 +768,17 @@ class MessageInputState extends State { ) async { String url; if (type == DefaultAttachmentTypes.image) { - url = await doImageUploadRequest(file, channel); + if (widget.doImageUploadRequest != null) { + url = await widget.doImageUploadRequest(file, channel); + } else { + url = await _uploadImage(file, channel); + } } else { - url = await doFileUploadRequest(file, channel); + if (widget.doFileUploadRequest != null) { + url = await widget.doFileUploadRequest(file, channel); + } else { + url = await _uploadFile(file, channel); + } } return url; } From eee1426b52d32b859451ed72ddecbdc1911a3023 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 15:17:59 +0200 Subject: [PATCH 094/133] refactoring --- example/lib/main.dart | 1 - lib/src/message_input.dart | 7 +++++-- lib/src/message_widget.dart | 4 ---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index ac895cfa..50a82270 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -67,7 +67,6 @@ void main() async { final client = Client( 's2dxdhpxd94g', logLevel: Level.INFO, - persistenceEnabled: false, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, ); diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index d70bd201..d5d37c32 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -547,6 +547,9 @@ class MessageInputState extends State { } Widget _buildAttachment(_SendingAttachment attachment) { + print('attachment.attachment.toJson(): ${attachment.attachment.toJson()}'); + print( + 'widget.attachmentThumbnailBuilder: ${widget.attachmentThumbnailBuilder}'); if (widget.attachmentThumbnailBuilder ?.containsKey(attachment.attachment.type) == true) { @@ -944,8 +947,7 @@ class MessageInputState extends State { textEditingController = widget.textEditingController ?? TextEditingController(); if (widget.editMessage != null || widget.initialMessage != null) { - _parseExistingMessage( - widget.editMessage ?? widget.initialMessage != null); + _parseExistingMessage(widget.editMessage ?? widget.initialMessage); } } @@ -956,6 +958,7 @@ class MessageInputState extends State { _messageIsPresent = true; message.attachments?.forEach((attachment) { + print('attachment: ${attachment.toJson()}'); _attachments.add(_SendingAttachment( attachment: attachment, uploaded: true, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 49b8fc10..aefb5524 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -712,10 +712,6 @@ class _MessageWidgetState extends State child: Icon( Icons.close, size: 15, - color: - Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, ), ), ), From df4f60c1e088843d3becc0a7bc510122a064d81b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Apr 2020 17:29:17 +0200 Subject: [PATCH 095/133] update llc --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9848410a..0c0b8aea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: file_picker: ^1.6.3+2 image_picker: ^0.6.5 flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+6 + stream_chat: ^0.2.0-alpha+8 mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From cb09f0d09e9462b9e9030134341f2e3aa6d2fb8d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 30 Apr 2020 11:10:33 +0200 Subject: [PATCH 096/133] add editMessageInputBuilder to customize the messageinput while editing messages --- lib/src/message_input.dart | 18 ++++++++--------- lib/src/message_list_view.dart | 8 ++++++++ lib/src/message_widget.dart | 36 ++++++++++++++++++++-------------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index d5d37c32..e4de6915 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -92,7 +92,7 @@ class MessageInput extends StatefulWidget { this.textEditingController, this.actions, this.actionsLocation = ActionsLocation.left, - this.attachmentThumbnailBuilder, + this.attachmentThumbnailBuilders, }) : super(key: key); /// Message to edit @@ -135,8 +135,8 @@ class MessageInput extends StatefulWidget { /// The location of the custom actions final ActionsLocation actionsLocation; - /// Map that defines a builder for an attachment type - final Map attachmentThumbnailBuilder; + /// Map that defines a thumbnail builder for an attachment type + final Map attachmentThumbnailBuilders; @override MessageInputState createState() => MessageInputState(); @@ -547,13 +547,10 @@ class MessageInputState extends State { } Widget _buildAttachment(_SendingAttachment attachment) { - print('attachment.attachment.toJson(): ${attachment.attachment.toJson()}'); - print( - 'widget.attachmentThumbnailBuilder: ${widget.attachmentThumbnailBuilder}'); - if (widget.attachmentThumbnailBuilder + if (widget.attachmentThumbnailBuilders ?.containsKey(attachment.attachment.type) == true) { - return widget.attachmentThumbnailBuilder[attachment.attachment.type]( + return widget.attachmentThumbnailBuilders[attachment.attachment.type]( context, attachment, ); @@ -897,6 +894,10 @@ class MessageInputState extends State { return sendingFuture.whenComplete(() { if (widget.onMessageSent != null) { widget.onMessageSent(message); + } else { + if (widget.editMessage != null) { + Navigator.pop(context); + } } }); } @@ -958,7 +959,6 @@ class MessageInputState extends State { _messageIsPresent = true; message.attachments?.forEach((attachment) { - print('attachment: ${attachment.toJson()}'); _attachments.add(_SendingAttachment( attachment: attachment, uploaded: true, diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 077c5289..fe407971 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -73,6 +73,7 @@ class MessageListView extends StatefulWidget { this.attachmentBuilders, this.dateDividerBuilder, this.showAvatar = true, + this.editMessageInputBuilder, }) : super(key: key); /// Function used to build a custom message widget @@ -115,6 +116,9 @@ class MessageListView extends StatefulWidget { /// if true shows the user avatar final bool showAvatar; + /// Builder used to build the message input to edit a message + final Widget Function(BuildContext, Message) editMessageInputBuilder; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -177,6 +181,7 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, + editMessageInputBuilder: widget.editMessageInputBuilder, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), @@ -243,6 +248,7 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, + editMessageInputBuilder: widget.editMessageInputBuilder, ); } } @@ -337,6 +343,7 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, + editMessageInputBuilder: widget.editMessageInputBuilder, ); } @@ -379,6 +386,7 @@ class _MessageListViewState extends State { onMessageActions: widget.onMessageActions, attachmentBuilders: widget.attachmentBuilders, showAvatar: widget.showAvatar, + editMessageInputBuilder: widget.editMessageInputBuilder, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index aefb5524..237a14fc 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -51,6 +51,7 @@ class MessageWidget extends StatefulWidget { this.showVideoFullScreen = true, this.attachmentBuilders, this.showAvatar = true, + this.editMessageInputBuilder, }) : super(key: key); /// Function called on mention tap @@ -89,6 +90,9 @@ class MessageWidget extends StatefulWidget { /// if true shows the user avatar final bool showAvatar; + /// Builder used to build the message input to edit a message + final Widget Function(BuildContext, Message) editMessageInputBuilder; + @override _MessageWidgetState createState() => _MessageWidgetState(); } @@ -723,21 +727,23 @@ class _MessageWidgetState extends State padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), - child: MessageInput( - editMessage: widget.message, - parentMessage: widget.isParent - ? StreamChannel.of(context) - .channel - .state - .messages - .firstWhere((message) => - message.id == widget.message.parentId) - : null, - onMessageSent: (_) { - FocusScope.of(context).unfocus(); - Navigator.pop(context); - }, - ), + child: widget.editMessageInputBuilder != null + ? widget.editMessageInputBuilder(context, widget.message) + : MessageInput( + editMessage: widget.message, + parentMessage: widget.isParent + ? StreamChannel.of(context) + .channel + .state + .messages + .firstWhere((message) => + message.id == widget.message.parentId) + : null, + onMessageSent: (_) { + FocusScope.of(context).unfocus(); + Navigator.pop(context); + }, + ), ), ], ), From 02d97893c16b828a44a71aad0ef20285069cfe87 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 30 Apr 2020 11:53:47 +0200 Subject: [PATCH 097/133] add scrollPhysics to messagelistview --- lib/src/message_list_view.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 077c5289..487dbc86 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -73,6 +73,7 @@ class MessageListView extends StatefulWidget { this.attachmentBuilders, this.dateDividerBuilder, this.showAvatar = true, + this.scrollPhysics = const AlwaysScrollableScrollPhysics(), }) : super(key: key); /// Function used to build a custom message widget @@ -115,6 +116,9 @@ class MessageListView extends StatefulWidget { /// if true shows the user avatar final bool showAvatar; + /// The ScrollPhysics used by the ListView + final ScrollPhysics scrollPhysics; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -145,7 +149,8 @@ class _MessageListViewState extends State { }, child: ListView.custom( key: Key('messageListView'), - physics: AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + physics: widget.scrollPhysics, controller: _scrollController, reverse: true, childrenDelegate: SliverChildBuilderDelegate( From da095357ed24cdd33da40e5c67a11783e68365a8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 4 May 2020 09:50:51 +0200 Subject: [PATCH 098/133] bump version --- CHANGELOG.md | 20 +++++++++++++++++++- lib/src/message_list_view.dart | 1 - lib/src/stream_chat.dart | 2 +- pubspec.yaml | 12 ++++++------ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ca876f0..1fb95312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## 0.2.0-alpha+14 + +Added some parameters to `MessageInput` +- add actions parameter +- add textEditingController parameter +- add actionsLocation (RIGHT or LEFT) parameter +- add attachmentThumbnailBuilders +- add editMessageInputBuilder to customize the MessageInput while editing messages +- expose MessageInputState + +Using attachmentThumbnailBuilders it's possible to render custom attachment thumbnails both for standard and custom attachment types +Using MessageInput.of or a GlobalKey it's possible to call these methods: + +- `sendMessage` to send the message +- `pickFile` to open the gallery/camera to pick a file +- `addAttachment` to add a custom attachment to the message +- `showAttachmentModal` to show the modal (that's the behaviour of the attachmentButton) + ## 0.2.0-alpha+13 - Handle channel deleted event @@ -46,7 +64,7 @@ - Minor bug fixes -## 0.1.20 +## 0.1.20s - Add message configuration properties to MessageListView diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index ca8deb25..b7c4ef8b 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -155,7 +155,6 @@ class _MessageListViewState extends State { }, child: ListView.custom( key: Key('messageListView'), - shrinkWrap: true, physics: widget.scrollPhysics, controller: _scrollController, reverse: true, diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 75a739a6..6d52e920 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -201,7 +201,7 @@ class StreamChatState extends State with WidgetsBindingObserver { if (client.showLocalNotification != null) { _newMessageSubscription = client .on(EventType.messageNew) - .where((e) => e.user.id != user.id) + .where((e) => e.user?.id != user.id) .listen((event) async { var channel = client.state.channels[event.cid]; diff --git a/pubspec.yaml b/pubspec.yaml index 2b839d1b..d563c55c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+13 +version: 0.2.0-alpha+14 environment: sdk: ">=2.3.0 <3.0.0" @@ -12,13 +12,13 @@ dependencies: photo_view: ^0.9.2 rxdart: ^0.24.0 jiffy: ^3.0.1 - cached_network_image: ^2.1.0+1 + cached_network_image: ^2.2.0 flutter_markdown: ^0.3.5 - url_launcher: ^5.4.2 - video_player: ^0.10.8+1 + url_launcher: ^5.4.5 + video_player: ^0.10.9+1 chewie: ^0.9.10 - file_picker: ^1.6.3+2 - image_picker: ^0.6.5 + file_picker: ^1.8.0+2 + image_picker: ^0.6.5+3 flutter_keyboard_visibility: ^2.0.0 stream_chat: ^0.2.0-alpha+8 mime: ^0.9.6+3 From aadb5277bdfaea740db109e68bf4f31fe676209e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 6 May 2020 09:50:17 +0200 Subject: [PATCH 099/133] version bump --- CHANGELOG.md | 34 ++-------------------------------- lib/src/message_widget.dart | 3 +++ pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f5faa64..f0153a4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,36 +1,6 @@ -## 0.1.35 +## 0.2.0-alpha+15 -- Add backgroundColor as part of StreamChatThemeData - -## 0.1.34 - -- Add `onUserAvatarTap` - -## 0.1.33 - -- Add default user and channel image to `StreamChatTheme` - -## 0.1.32 - -- Update llc version - -## 0.1.31 - -Added some parameters to `MessageInput` -- add actions parameter -- add textEditingController parameter -- add actionsLocation (RIGHT or LEFT) parameter -- add attachmentThumbnailBuilders -- add editMessageInputBuilder to customize the MessageInput while editing messages -- expose MessageInputState - -Using attachmentThumbnailBuilders it's possible to render custom attachment thumbnails both for standard and custom attachment types -Using MessageInput.of or a GlobalKey it's possible to call these methods: - -- `sendMessage` to send the message -- `pickFile` to open the gallery/camera to pick a file -- `addAttachment` to add a custom attachment to the message -- `showAttachmentModal` to show the modal (that's the behaviour of the attachmentButton) +- Add background color in StreamChatTheme ## 0.2.0-alpha+13 diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 237a14fc..84100826 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -716,6 +716,9 @@ class _MessageWidgetState extends State child: Icon( Icons.close, size: 15, + color: StreamChatTheme.of(context) + .primaryIconTheme + .color, ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index d563c55c..13af2fc0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+14 +version: 0.2.0-alpha+15 environment: sdk: ">=2.3.0 <3.0.0" From daf0989f2df20a14d8fecacdfe230321c0ca4c72 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 6 May 2020 10:34:29 +0200 Subject: [PATCH 100/133] add onLongPress to channel preview --- example/lib/main.dart | 6 +++--- lib/src/channel_list_view.dart | 5 +++++ lib/src/channel_preview.dart | 13 ++++++++++++- pubspec.yaml | 3 ++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 137186c7..4e997156 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -68,6 +68,7 @@ void main() async { 's2dxdhpxd94g', logLevel: Level.INFO, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, + backgroundKeepAlive: Duration.zero, ); await client.setUser( @@ -87,13 +88,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = ThemeData(); return MaterialApp( - theme: theme, + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), themeMode: ThemeMode.system, home: Container( child: StreamChat( - streamChatThemeData: StreamChatThemeData.fromTheme(theme), client: client, child: ChannelListPage(), ), diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index ec6e99f5..7b6a78a0 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -55,6 +55,7 @@ class ChannelListView extends StatefulWidget { this.sort, this.pagination, this.onChannelTap, + this.onChannelLongPress, this.channelWidget, this.channelPreviewBuilder, this.errorBuilder, @@ -92,6 +93,9 @@ class ChannelListView extends StatefulWidget { /// with the widget [channelWidget] as child. final ChannelTapCallback onChannelTap; + /// Function called when long pressing on a channel + final Function(Channel) onChannelLongPress; + /// Widget used when opening a channel final Widget channelWidget; @@ -271,6 +275,7 @@ class _ChannelListViewState extends State ); } else { child = ChannelPreview( + onLongPress: widget.onChannelLongPress, channel: channel, onImageTap: widget.onImageTap != null ? () { diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index ff81cc7c..1e86f4fb 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -23,6 +23,9 @@ class ChannelPreview extends StatelessWidget { /// Function called when tapping this widget final void Function(Channel) onTap; + /// Function called when long pressing this widget + final void Function(Channel) onLongPress; + /// Channel displayed final Channel channel; @@ -33,6 +36,7 @@ class ChannelPreview extends StatelessWidget { @required this.channel, Key key, this.onTap, + this.onLongPress, this.onImageTap, }) : super(key: key); @@ -40,7 +44,14 @@ class ChannelPreview extends StatelessWidget { Widget build(BuildContext context) { return ListTile( onTap: () { - onTap(channel); + if (onTap != null) { + onTap(channel); + } + }, + onLongPress: () { + if (onLongPress != null) { + onLongPress(channel); + } }, leading: ChannelImage( onTap: onImageTap, diff --git a/pubspec.yaml b/pubspec.yaml index 13af2fc0..d7661473 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,8 @@ dependencies: file_picker: ^1.8.0+2 image_picker: ^0.6.5+3 flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+8 + stream_chat: + path: ../stream_chat_dart mime: ^0.9.6+3 visibility_detector: ^0.1.4 http_parser: ^3.1.4 From 5b29057ccbc460d76d996416c6f2a7df36a4a327 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 11 May 2020 16:20:03 +0200 Subject: [PATCH 101/133] refactor message widget --- analysis_options.yaml | 122 +- example/lib/custom_message.dart | 7 +- example/lib/main.dart | 17 +- lib/src/attachment_title.dart | 62 +- lib/src/date_divider.dart | 2 +- lib/src/deleted_message.dart | 25 +- lib/src/image_attachment.dart | 4 +- lib/src/message_actions_bottom_sheet.dart | 209 +++ lib/src/message_input.dart | 1 - lib/src/message_list_view.dart | 275 ++-- lib/src/message_text.dart | 64 + lib/src/message_widget.dart | 1436 +++++++++------------ lib/src/reaction_picker.dart | 81 +- lib/src/reply_indicator.dart | 1 + lib/src/sending_indicator.dart | 57 +- lib/stream_chat_flutter.dart | 1 + pubspec.yaml | 9 +- 17 files changed, 1189 insertions(+), 1184 deletions(-) create mode 100644 lib/src/message_actions_bottom_sheet.dart create mode 100644 lib/src/message_text.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 7feb4342..fa8ecab2 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,61 +1,61 @@ -include: package:pedantic/analysis_options.yaml - -analyzer: - exclude: - - lib/**/*.g.dart - - example/* - -linter: - rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - # - always_declare_return_types - # - always_specify_types - # - annotate_overrides - # - avoid_as - - avoid_empty_else - - avoid_init_to_null - - avoid_return_types_on_setters - - avoid_web_libraries_in_flutter - - await_only_futures - - camel_case_types - - cancel_subscriptions - - close_sinks - # - comment_references # we do not presume as to what people want to reference in their dartdocs - # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 - - control_flow_in_finally - - empty_constructor_bodies - - empty_statements - - hash_and_equals - - implementation_imports - # - invariant_booleans - # - iterable_contains_unrelated_type - - library_names - # - library_prefixes - # - list_remove_unrelated_type - # - literal_only_boolean_expressions - - non_constant_identifier_names - # - one_member_abstracts - # - only_throw_errors - # - overridden_fields -# - package_api_docs - - package_names - - package_prefixed_library_names - - prefer_is_not_empty - # - prefer_mixin # https://github.com/dart-lang/language/issues/32 - - public_member_api_docs - - slash_for_doc_comments - # - sort_constructors_first - # - sort_unnamed_constructors_first - # - super_goes_last # no longer needed w/ Dart 2 - - test_types_in_equals - - throw_in_finally - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - # - unawaited_futures - - unnecessary_brace_in_string_interps - - unnecessary_getters_setters - - unnecessary_statements - - unrelated_type_equality_checks - - valid_regexps +#include: package:pedantic/analysis_options.yaml +# +#analyzer: +# exclude: +# - lib/**/*.g.dart +# - example/* +# +#linter: +# rules: +# # these rules are documented on and in the same order as +# # the Dart Lint rules page to make maintenance easier +# # https://github.com/dart-lang/linter/blob/master/example/all.yaml +# # - always_declare_return_types +# # - always_specify_types +# # - annotate_overrides +# # - avoid_as +# - avoid_empty_else +# - avoid_init_to_null +# - avoid_return_types_on_setters +# - avoid_web_libraries_in_flutter +# - await_only_futures +# - camel_case_types +# - cancel_subscriptions +# - close_sinks +# # - comment_references # we do not presume as to what people want to reference in their dartdocs +# # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 +# - control_flow_in_finally +# - empty_constructor_bodies +# - empty_statements +# - hash_and_equals +# - implementation_imports +# # - invariant_booleans +# # - iterable_contains_unrelated_type +# - library_names +# # - library_prefixes +# # - list_remove_unrelated_type +# # - literal_only_boolean_expressions +# - non_constant_identifier_names +# # - one_member_abstracts +# # - only_throw_errors +# # - overridden_fields +## - package_api_docs +# - package_names +# - package_prefixed_library_names +# - prefer_is_not_empty +# # - prefer_mixin # https://github.com/dart-lang/language/issues/32 +# - public_member_api_docs +# - slash_for_doc_comments +# # - sort_constructors_first +# # - sort_unnamed_constructors_first +# # - super_goes_last # no longer needed w/ Dart 2 +# - test_types_in_equals +# - throw_in_finally +# # - type_annotate_public_apis # subset of always_specify_types +# - type_init_formals +# # - unawaited_futures +# - unnecessary_brace_in_string_interps +# - unnecessary_getters_setters +# - unnecessary_statements +# - unrelated_type_equality_checks +# - valid_regexps diff --git a/example/lib/custom_message.dart b/example/lib/custom_message.dart index b084baa2..4f1aa2ca 100644 --- a/example/lib/custom_message.dart +++ b/example/lib/custom_message.dart @@ -90,7 +90,12 @@ class ChannelPage extends StatelessWidget { ); } - Widget _messageBuilder(context, message, index) { + Widget _messageBuilder( + BuildContext context, + MessageDetails details, + List messages, + ) { + final message = details.message; final isCurrentUser = StreamChat.of(context).user.id == message.user.id; final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left; final color = isCurrentUser ? Colors.blueGrey : Colors.blue; diff --git a/example/lib/main.dart b/example/lib/main.dart index 4e997156..251af27e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_apns/apns.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart' @@ -66,9 +67,11 @@ void _initNotifications(Client client) { void main() async { final client = Client( 's2dxdhpxd94g', - logLevel: Level.INFO, + logLevel: Level.SEVERE, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, backgroundKeepAlive: Duration.zero, + persistenceEnabled: true, +// baseURL: 'chat-us-east-staging.stream-io-api.com', ); await client.setUser( @@ -108,11 +111,11 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, +// filter: { +// 'members': { +// '\$in': [StreamChat.of(context).user.id], +// } +// }, sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, @@ -139,8 +142,6 @@ class ChannelPage extends StatelessWidget { child: Stack( children: [ MessageListView( - showOtherMessageUsername: true, - showVideoFullScreen: false, threadBuilder: (_, parentMessage) { return ThreadPage( parent: parentMessage, diff --git a/lib/src/attachment_title.dart b/lib/src/attachment_title.dart index d23c4bf8..4d30f7b2 100644 --- a/lib/src/attachment_title.dart +++ b/lib/src/attachment_title.dart @@ -22,43 +22,33 @@ class AttachmentTitle extends StatelessWidget { launchURL(context, attachment.titleLink); } }, - child: Container( - constraints: BoxConstraints.loose( - Size( - MediaQuery.of(context).size.width * 0.7, - 500, - ), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - attachment.title, - overflow: TextOverflow.ellipsis, - style: messageTheme.messageText.copyWith( - color: StreamChatTheme.of(context).accentColor, - fontWeight: FontWeight.bold, - ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + attachment.title, + overflow: TextOverflow.ellipsis, + style: messageTheme.messageText.copyWith( + color: StreamChatTheme.of(context).accentColor, + fontWeight: FontWeight.bold, ), - if (attachment.titleLink != null || - attachment.ogScrapeUrl != null) - Text( - Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl) - .authority - .split('.') - .reversed - .take(2) - .toList() - .reversed - .join('.'), - overflow: TextOverflow.ellipsis, - style: messageTheme.createdAt, - ), - ], - ), + ), + if (attachment.titleLink != null || attachment.ogScrapeUrl != null) + Text( + Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl) + .authority + .split('.') + .reversed + .take(2) + .toList() + .reversed + .join('.'), + style: messageTheme.createdAt, + ), + ], ), ), ); diff --git a/lib/src/date_divider.dart b/lib/src/date_divider.dart index 1e00f2ae..970169b8 100644 --- a/lib/src/date_divider.dart +++ b/lib/src/date_divider.dart @@ -44,7 +44,7 @@ class DateDivider extends StatelessWidget { } return Padding( - padding: const EdgeInsets.only(top: 24.0), + padding: const EdgeInsets.symmetric(vertical: 12.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/src/deleted_message.dart b/lib/src/deleted_message.dart index 6b63176d..a132f48d 100644 --- a/lib/src/deleted_message.dart +++ b/lib/src/deleted_message.dart @@ -5,30 +5,19 @@ class DeletedMessage extends StatelessWidget { const DeletedMessage({ Key key, @required this.messageTheme, - this.alignment, }) : super(key: key); final MessageTheme messageTheme; - final Alignment alignment; @override Widget build(BuildContext context) { - return Align( - alignment: alignment, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 14, - ), - child: Text( - 'This message was deleted...', - style: messageTheme.messageText.copyWith( - fontStyle: FontStyle.italic, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, - ), - ), + return Text( + 'This message was deleted...', + style: messageTheme.messageText.copyWith( + fontStyle: FontStyle.italic, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black, ), ); } diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index 9fb7f05c..eab4ada6 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -26,10 +26,9 @@ class ImageAttachment extends StatelessWidget { attachment: attachment, ); } - return Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ @@ -42,7 +41,6 @@ class ImageAttachment extends StatelessWidget { return GestureDetector( child: Image( image: provider, - width: MediaQuery.of(context).size.width * 0.7, fit: BoxFit.cover, ), onTap: () { diff --git a/lib/src/message_actions_bottom_sheet.dart b/lib/src/message_actions_bottom_sheet.dart new file mode 100644 index 00000000..1e81900f --- /dev/null +++ b/lib/src/message_actions_bottom_sheet.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/reaction_picker.dart'; +import 'package:stream_chat_flutter/src/stream_channel.dart'; + +import '../stream_chat_flutter.dart'; +import 'message_input.dart'; +import 'stream_chat.dart'; + +class MessageActionsBottomSheet extends StatelessWidget { + final Widget Function(BuildContext, Message) editMessageInputBuilder; + final void Function(Message) onThreadTap; + final Message message; + final bool showReactions; + final bool showDeleteMessage; + final bool showEditMessage; + final bool showReply; + final Map reactionToEmoji = const { + 'love': '❤️️', + 'haha': '😂', + 'like': '👍', + 'sad': '😕', + 'angry': '😡', + 'wow': '😲', + }; + + const MessageActionsBottomSheet({ + Key key, + this.message, + this.showReactions, + this.showDeleteMessage, + this.showEditMessage, + this.onThreadTap, + this.showReply, + this.editMessageInputBuilder, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final channel = StreamChannel.of(context).channel; + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (showReactions && + (message.status == MessageSendingStatus.SENT || + message.status == null)) + ReactionPicker( + channel: channel, + reactionToEmoji: reactionToEmoji, + message: message, + ), + if (showDeleteMessage) _buildDeleteButton(context), + if (showEditMessage) _buildEditMessage(context), + if (showReply && + (message.status == MessageSendingStatus.SENT || + message.status == null) && + message.parentId == null) + _buildReplyButton(context), + ], + ), + ); + } + + FlatButton _buildDeleteButton(BuildContext context) { + return FlatButton( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Delete message', + style: + Theme.of(context).textTheme.headline5.copyWith(color: Colors.red), + ), + ), + onPressed: () { + Navigator.pop(context); + StreamChat.of(context).client.deleteMessage( + message, + StreamChannel.of(context).channel.cid, + ); + }, + ); + } + + FlatButton _buildEditMessage(BuildContext context) { + return FlatButton( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Edit message', + style: Theme.of(context).textTheme.headline5, + ), + ), + onPressed: () async { + Navigator.pop(context); + _showEditBottomSheet(context); + }, + ); + } + + void _showEditBottomSheet(BuildContext context) { + final channel = StreamChannel.of(context).channel; + showModalBottomSheet( + context: context, + elevation: 2, + clipBehavior: Clip.hardEdge, + isScrollControlled: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), + ), + builder: (context) { + return StreamChannel( + channel: channel, + child: Flex( + direction: Axis.vertical, + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 16.0, + left: 16.0, + right: 16.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Edit message', + style: Theme.of(context).textTheme.headline6, + ), + Container( + height: 30, + padding: const EdgeInsets.all(2.0), + child: AspectRatio( + aspectRatio: 1, + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + disabledElevation: 0, + hoverElevation: 0, + onPressed: () { + Navigator.of(context).pop(); + }, + fillColor: + Theme.of(context).brightness == Brightness.dark + ? Colors.white.withOpacity(.1) + : Colors.black.withOpacity(.1), + padding: EdgeInsets.all(4), + child: Icon( + Icons.close, + size: 15, + color: StreamChatTheme.of(context) + .primaryIconTheme + .color, + ), + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: editMessageInputBuilder != null + ? editMessageInputBuilder(context, message) + : MessageInput( + editMessage: message, + onMessageSent: (_) { + FocusScope.of(context).unfocus(); + Navigator.pop(context); + }, + ), + ), + ], + ), + ); + }, + ); + } + + FlatButton _buildReplyButton(BuildContext context) { + return FlatButton( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Start a thread', + style: Theme.of(context).textTheme.headline5, + ), + ), + onPressed: () { + Navigator.pop(context); + if (onThreadTap != null) { + onThreadTap(message); + } + }, + ); + } +} diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index e4de6915..1c48070a 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -861,7 +861,6 @@ class MessageInputState extends State { Message message; if (widget.editMessage != null) { message = widget.editMessage.copyWith( - parentId: widget.parentMessage?.id, text: text, attachments: _getAttachments(attachments).toList(), mentionedUsers: diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index b7c4ef8b..da67bcc9 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -3,19 +3,46 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/message_widget.dart'; import 'package:visibility_detector/visibility_detector.dart'; import '../stream_chat_flutter.dart'; import 'date_divider.dart'; -import 'message_widget.dart'; import 'stream_channel.dart'; typedef MessageBuilder = Widget Function( - BuildContext, Message, List, int index); -typedef ParentMessageBuilder = Widget Function(BuildContext, Message); + BuildContext, + MessageDetails, + List, +); +typedef ParentMessageBuilder = Widget Function( + BuildContext, + Message, +); typedef ThreadBuilder = Widget Function(BuildContext context, Message parent); typedef ThreadTapCallback = void Function(Message, Widget); +class MessageDetails { + bool isMyMessage; + bool isLastUser; + bool isNextUser; + Message message; + int index; + + MessageDetails( + BuildContext context, + this.message, + List messages, + this.index, + ) { + isMyMessage = message.user.id == StreamChat.of(context).user.id; + isLastUser = index + 1 < messages.length && + message.user.id == messages[index + 1]?.user?.id; + isNextUser = + index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id; + } +} + /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview_paint.png) /// @@ -65,15 +92,7 @@ class MessageListView extends StatefulWidget { this.parentMessage, this.threadBuilder, this.onThreadTap, - this.showOtherMessageUsername = false, - this.showVideoFullScreen = true, - this.onMentionTap, - this.onUserAvatarTap, - this.onMessageActions, - this.attachmentBuilders, this.dateDividerBuilder, - this.showAvatar = true, - this.editMessageInputBuilder, this.scrollPhysics = const AlwaysScrollableScrollPhysics(), }) : super(key: key); @@ -93,38 +112,12 @@ class MessageListView extends StatefulWidget { /// Parent message in case of a thread final Message parentMessage; - /// If true show the other users username next to the timestamp of the message - final bool showOtherMessageUsername; - - /// True if the video player will allow fullscreen mode - final bool showVideoFullScreen; - - /// Function called on message mention tap - final void Function(User) onMentionTap; - - /// Function called on User Avatar tap - final void Function(User) onUserAvatarTap; - - /// Function called on message long press - final Function(BuildContext, Message) onMessageActions; - - /// Map that defines a builder for an attachment type - final Map attachmentBuilders; - /// Builder used to render date dividers final Widget Function(DateTime) dateDividerBuilder; - /// if true shows the user avatar - final bool showAvatar; - - - /// Builder used to build the message input to edit a message - final Widget Function(BuildContext, Message) editMessageInputBuilder; - /// The ScrollPhysics used by the ListView final ScrollPhysics scrollPhysics; - @override _MessageListViewState createState() => _MessageListViewState(); } @@ -171,24 +164,7 @@ class _MessageListViewState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - MessageWidget( - key: ValueKey( - 'PARENT-MESSAGE-${widget.parentMessage.id}'), - previousMessage: null, - message: widget.parentMessage, - nextMessage: null, - onThreadTap: _onThreadTap, - isParent: true, - showVideoFullScreen: widget.showVideoFullScreen, - showOtherMessageUsername: - widget.showOtherMessageUsername, - onMentionTap: widget.onMentionTap, - onUserAvatarTap: widget.onUserAvatarTap, - onMessageActions: widget.onMessageActions, - attachmentBuilders: widget.attachmentBuilders, - showAvatar: widget.showAvatar, - editMessageInputBuilder: widget.editMessageInputBuilder, - ), + buildParentMessage(widget.parentMessage), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Container( @@ -212,50 +188,40 @@ class _MessageListViewState extends State { return _buildLoadingIndicator(streamChannel); } final message = _messages[i]; - - final previousMessage = - i < _messages.length - 1 ? _messages[i + 1] : null; final nextMessage = i > 0 ? _messages[i - 1] : null; Widget messageWidget; if (i == 0) { messageWidget = _buildBottomMessage( - streamChannel, - previousMessage, - message, context, + message, + _messages, + streamChannel, ); } else if (i == _messages.length - 1) { messageWidget = _buildTopMessage( - message, - nextMessage, - streamChannel, context, + message, + _messages, + streamChannel, ); } else { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => - widget.messageBuilder(context, message, _messages, i), + builder: (_) => widget.messageBuilder( + context, + MessageDetails( + context, + message, + _messages, + i, + ), + _messages), ); } else { - messageWidget = MessageWidget( - key: ValueKey('MESSAGE-${message.id}'), - previousMessage: previousMessage, - message: message, - nextMessage: nextMessage, - onThreadTap: _onThreadTap, - showOtherMessageUsername: widget.showOtherMessageUsername, - showVideoFullScreen: widget.showVideoFullScreen, - onMentionTap: widget.onMentionTap, - onUserAvatarTap: widget.onUserAvatarTap, - onMessageActions: widget.onMessageActions, - attachmentBuilders: widget.attachmentBuilders, - showAvatar: widget.showAvatar, - editMessageInputBuilder: widget.editMessageInputBuilder, - ); + messageWidget = buildMessage(message, _messages, i); } } @@ -319,38 +285,28 @@ class _MessageListViewState extends State { } Widget _buildTopMessage( - Message message, - Message nextMessage, - StreamChannelState streamChannelState, BuildContext context, + Message message, + List messages, + StreamChannelState streamChannel, ) { Widget messageWidget; if (widget.messageBuilder != null) { messageWidget = Builder( - key: ValueKey('MESSAGE-${message.id}'), + key: ValueKey('TOP-MESSAGE'), builder: (_) => widget.messageBuilder( context, - message, + MessageDetails( + context, + message, + _messages, + _messages.length - 1, + ), _messages, - _messages.length - 1, ), ); } else { - messageWidget = MessageWidget( - key: ValueKey('MESSAGE-${message.id}'), - previousMessage: null, - message: message, - nextMessage: nextMessage, - onThreadTap: _onThreadTap, - showVideoFullScreen: widget.showVideoFullScreen, - showOtherMessageUsername: widget.showOtherMessageUsername, - onMentionTap: widget.onMentionTap, - onUserAvatarTap: widget.onUserAvatarTap, - onMessageActions: widget.onMessageActions, - attachmentBuilders: widget.attachmentBuilders, - showAvatar: widget.showAvatar, - editMessageInputBuilder: widget.editMessageInputBuilder, - ); + messageWidget = buildMessage(message, messages, _messages.length - 1); } return VisibilityDetector( @@ -359,7 +315,7 @@ class _MessageListViewState extends State { onVisibilityChanged: (visibility) { final topIsVisible = visibility.visibleBounds != Rect.zero; if (topIsVisible && !_topWasVisible) { - streamChannelState.queryMessages(); + streamChannel.queryMessages(); } _topWasVisible = topIsVisible; }, @@ -367,33 +323,28 @@ class _MessageListViewState extends State { } Widget _buildBottomMessage( - StreamChannelState streamChannel, - Message previousMessage, - Message message, BuildContext context, + Message message, + List messages, + StreamChannelState streamChannel, ) { Widget messageWidget; if (widget.messageBuilder != null) { messageWidget = Builder( - key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder(context, message, _messages, 0), + key: ValueKey('BOTTOM-MESSAGE'), + builder: (_) => widget.messageBuilder( + context, + MessageDetails( + context, + message, + _messages, + 0, + ), + _messages, + ), ); } else { - messageWidget = MessageWidget( - key: ValueKey('MESSAGE-${message.id}'), - previousMessage: previousMessage, - message: message, - nextMessage: null, - onThreadTap: _onThreadTap, - showVideoFullScreen: widget.showVideoFullScreen, - showOtherMessageUsername: widget.showOtherMessageUsername, - onMentionTap: widget.onMentionTap, - onUserAvatarTap: widget.onUserAvatarTap, - onMessageActions: widget.onMessageActions, - attachmentBuilders: widget.attachmentBuilders, - showAvatar: widget.showAvatar, - editMessageInputBuilder: widget.editMessageInputBuilder, - ); + messageWidget = buildMessage(message, messages, 0); } return VisibilityDetector( @@ -410,6 +361,84 @@ class _MessageListViewState extends State { ); } + Widget buildParentMessage( + Message message, + ) { + final isMyMessage = message.user.id == StreamChat.of(context).user.id; + + return MessageWidget( + showReplyIndicator: false, + message: message, + reverse: isMyMessage, + showUsername: !isMyMessage, + padding: EdgeInsets.only( + top: 8.0, + left: 8.0, + right: 8.0, + bottom: 16.0, + ), + showSendingIndicator: DisplayWidget.hide, + onThreadTap: _onThreadTap, + showEditMessage: false, + showDeleteMessage: false, + borderRadiusGeometry: BorderRadius.only( + topLeft: Radius.circular(16), + bottomLeft: Radius.circular(2), + topRight: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + borderSide: isMyMessage ? BorderSide.none : null, + showUserAvatar: DisplayWidget.show, + messageTheme: isMyMessage + ? StreamChatTheme.of(context).ownMessageTheme + : StreamChatTheme.of(context).otherMessageTheme, + ); + } + + Widget buildMessage( + Message message, + List messages, + int index, + ) { + final isMyMessage = message.user.id == StreamChat.of(context).user.id; + final isLastUser = index + 1 < messages.length && + message.user.id == messages[index + 1]?.user?.id; + final isNextUser = + index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id; + + return MessageWidget( + message: message, + reverse: isMyMessage, + showReactions: !message.isDeleted, + padding: EdgeInsets.only( + left: 8.0, + right: 8.0, + bottom: index == 0 ? 30 : (isLastUser ? 5 : 10), + ), + showUsername: !isMyMessage && !isNextUser, + showSendingIndicator: isMyMessage && + (index == 0 || message.status != MessageSendingStatus.SENT) + ? DisplayWidget.show + : DisplayWidget.hide, + showTimestamp: !isNextUser, + showEditMessage: isMyMessage, + showDeleteMessage: isMyMessage, + borderSide: isMyMessage ? BorderSide.none : null, + onThreadTap: _onThreadTap, + attachmentBorderRadiusGeometry: BorderRadius.circular(16), + borderRadiusGeometry: BorderRadius.only( + topLeft: Radius.circular(isLastUser ? 2 : 16), + bottomLeft: Radius.circular(2), + topRight: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + showUserAvatar: isNextUser ? DisplayWidget.hide : DisplayWidget.show, + messageTheme: isMyMessage + ? StreamChatTheme.of(context).ownMessageTheme + : StreamChatTheme.of(context).otherMessageTheme, + ); + } + StreamSubscription _streamListener; @override diff --git a/lib/src/message_text.dart b/lib/src/message_text.dart new file mode 100644 index 00000000..f35b8549 --- /dev/null +++ b/lib/src/message_text.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:stream_chat/stream_chat.dart'; + +import 'stream_chat_theme.dart'; +import 'utils.dart'; + +class MessageText extends StatelessWidget { + const MessageText({ + Key key, + @required this.message, + @required this.messageTheme, + this.onMentionTap, + }) : super(key: key); + + final Message message; + final void Function(User) onMentionTap; + final MessageTheme messageTheme; + + @override + Widget build(BuildContext context) { + final text = _replaceMentions(message.text); + return MarkdownBody( + data: text, + onTapLink: (link) { + if (link.startsWith('@')) { + final mentionedUser = message.mentionedUsers.firstWhere( + (u) => '@${u.name.replaceAll(' ', '')}' == link, + orElse: () => null, + ); + + if (onMentionTap != null) { + onMentionTap(mentionedUser); + } else { + print('tap on ${mentionedUser.name}'); + } + } else { + launchURL(context, link); + } + }, + 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, + ), + ), + ).copyWith( + p: messageTheme.messageText, + ), + ); + } + + String _replaceMentions(String text) { + message.mentionedUsers?.forEach((u) { + text = text.replaceAll( + '@${u.name}', '[@${u.name}](@${u.name.replaceAll(' ', '')})'); + }); + return text; + } +} diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 84100826..25c40cd1 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1,822 +1,53 @@ import 'dart:math'; +import 'dart:ui'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/message_input.dart'; -import 'package:stream_chat_flutter/src/message_list_view.dart'; -import 'package:stream_chat_flutter/src/reaction_picker.dart'; -import 'package:stream_chat_flutter/src/reply_indicator.dart'; -import 'package:stream_chat_flutter/src/sending_indicator.dart'; -import 'package:stream_chat_flutter/src/stream_channel.dart'; -import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; -import 'package:stream_chat_flutter/src/user_avatar.dart'; -import 'package:stream_chat_flutter/src/utils.dart'; -import 'package:stream_chat_flutter/src/video_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'deleted_message.dart'; -import 'file_attachment.dart'; -import 'giphy_attachment.dart'; -import 'image_attachment.dart'; -import 'stream_chat.dart'; +import 'message_actions_bottom_sheet.dart'; +import 'message_text.dart'; typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment); -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget_paint.png) -/// -/// It shows a message with reactions, replies and user avatar. -/// -/// Usually you don't use this widget as it's the default message widget used by [MessageListView]. -/// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. -/// Modify it to change the widget appearance. -class MessageWidget extends StatefulWidget { - /// Instantiate a new MessageWidget - const MessageWidget({ - Key key, - @required this.previousMessage, - @required this.message, - @required this.nextMessage, - this.onThreadTap, - this.onUserAvatarTap, - this.onMessageActions, - this.isParent = false, - this.onMentionTap, - this.showOtherMessageUsername = false, - this.showVideoFullScreen = true, - this.attachmentBuilders, - this.showAvatar = true, - this.editMessageInputBuilder, - }) : super(key: key); - - /// Function called on mention tap - final void Function(User) onMentionTap; - - /// Function called on long press - final Function(BuildContext, Message) onMessageActions; - - /// If true show the other users username next to the timestamp of the message - final bool showOtherMessageUsername; - - /// This message - final Message message; - - /// The previous message - final Message previousMessage; - - /// The next message - final Message nextMessage; - - /// The function called when tapping on replies - final void Function(Message) onThreadTap; - - /// The function called when tapping on UserAvatar - final void Function(User) onUserAvatarTap; - - /// True if this is the parent of the thread being showed - final bool isParent; - - /// True if the video player will allow fullscreen mode - final bool showVideoFullScreen; - - /// Map that defines a builder for an attachment type - final Map attachmentBuilders; - - /// if true shows the user avatar - final bool showAvatar; - - /// Builder used to build the message input to edit a message - final Widget Function(BuildContext, Message) editMessageInputBuilder; - - @override - _MessageWidgetState createState() => _MessageWidgetState(); +enum DisplayWidget { + hide, + gone, + show, } -class _MessageWidgetState extends State - with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { - MessageTheme _messageTheme; - StreamChatState _streamChat; - StreamChannelState _streamChannel; - bool _isMyMessage; - - String _currentUserId; - String _messageUserId; - String _previousUserId; - String _nextUserId; - bool _isLastUser; - bool _isNextUser; - - Map _attachmentBuilders; - - @override - Widget build(BuildContext context) { - super.build(context); - - _messageTheme = _isMyMessage - ? StreamChatTheme.of(context).ownMessageTheme - : StreamChatTheme.of(context).otherMessageTheme; - - final alignment = - _isMyMessage ? Alignment.centerRight : Alignment.centerLeft; - - var row = List.from([ - Column( - crossAxisAlignment: - _isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, - children: [ - (widget.message.isDeleted && - widget.message.status != MessageSendingStatus.FAILED_DELETE) - ? _buildDeletedMessage(alignment) - : _buildBubble(context), - if (_streamChannel.channel.config?.replies == true) - _buildThreadIndicator(context), - if (!_isNextUser) _buildTimestamp(alignment), - ], - ), - _isNextUser - ? Container( - width: widget.showAvatar ? 40 : 8, - ) - : _buildUserAvatar(), - ]); - - if (!_isMyMessage) { - row = row.reversed.toList(); - } - - return Container( - padding: EdgeInsets.symmetric( - horizontal: (_isMyMessage && widget.nextMessage == null) ? 0.0 : 10, - ), - margin: EdgeInsets.only( - top: _isLastUser ? 5 : 24, - bottom: widget.nextMessage == null ? 30 : 0, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: - _isMyMessage ? MainAxisAlignment.end : MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: row, - ), - ); - } - - Padding _buildUserAvatar() { - return Padding( - padding: EdgeInsets.only( - left: _isMyMessage ? 8.0 : 0, - right: _isMyMessage ? 0 : 8.0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (widget.showAvatar) - UserAvatar( - user: widget.message.user, - onTap: widget.onUserAvatarTap, - ), - if (_isMyMessage && widget.nextMessage == null) - SendingIndicator( - message: widget.message, - ), - ], - ), - ); - } - - @override - void initState() { - super.initState(); - - _streamChat = StreamChat.of(context); - _streamChannel = StreamChannel.of(context); - - _currentUserId = _streamChat.client.state.user.id; - _messageUserId = widget.message.user.id; - _previousUserId = widget.previousMessage?.user?.id; - _nextUserId = widget.nextMessage?.user?.id; - _isLastUser = _previousUserId == _messageUserId; - _isNextUser = _nextUserId == _messageUserId; - - _isMyMessage = _messageUserId == _currentUserId; - - _mergeAttachmentBuilders(); - } - - @override - void didUpdateWidget(MessageWidget oldWidget) { - super.didUpdateWidget(oldWidget); - - _mergeAttachmentBuilders(); - } - - void _mergeAttachmentBuilders() { - _attachmentBuilders = { - 'image': (context, message, attachment) { - return ImageAttachment( - attachment: attachment, - messageTheme: _messageTheme, - ); - }, - 'video': (context, message, attachment) { - return VideoAttachment( - enableFullScreen: widget.showVideoFullScreen, - attachment: attachment, - messageTheme: _messageTheme, - ); - }, - 'giphy': (context, message, attachment) { - return GiphyAttachment( - attachment: attachment, - messageTheme: _messageTheme, - message: message, - ); - }, - 'file': (context, message, attachment) { - return FileAttachment( - attachment: attachment, - ); - }, - }..addAll(widget.attachmentBuilders ?? {}); - } - - Widget _buildDeletedMessage(Alignment alignment) { - return DeletedMessage( - messageTheme: _messageTheme, - alignment: alignment, - ); - } - - Widget _buildThreadIndicator(BuildContext context) { - if (widget.message?.replyCount != null && widget.message.replyCount > 0) { - return ReplyIndicator( - onTap: !widget.isParent - ? () { - widget.onThreadTap(widget.message); - } - : null, - message: widget.message, - messageTheme: _messageTheme, - reversed: _isMyMessage, - ); - } - return SizedBox(); - } - - Widget _buildBubble( - BuildContext context, - ) { - var nOfAttachmentWidgets = 0; - - final column = - List.from(widget.message.attachments.map((attachment) { - nOfAttachmentWidgets++; - - Widget attachmentWidget; - final attachmentBuilder = _attachmentBuilders[attachment.type]; - if (attachmentBuilder == null) { - return SizedBox(); - } - - attachmentWidget = attachmentBuilder( - context, - widget.message, - attachment, - ); - - if (attachmentWidget != null) { - return _buildAttachment( - attachmentWidget, - attachment, - nOfAttachmentWidgets, - context, - ); - } - - nOfAttachmentWidgets--; - return SizedBox(); - }) ?? - []); - - if (widget.message.text.trim().isNotEmpty) { - var text = widget.message.text; - text = _replaceMentions(text); - - column.addAll( - [ - Column( - crossAxisAlignment: _isMyMessage - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - children: [ - if (_streamChannel.channel.config?.reactions == true && - nOfAttachmentWidgets == 0) - Align( - child: _buildReactions(), - alignment: _isMyMessage - ? Alignment.centerLeft - : Alignment.centerRight, - ), - Stack( - overflow: Overflow.visible, - children: [ - if (nOfAttachmentWidgets == 0 && - _streamChannel.channel.config?.reactions == true) - _buildReactionPaint(), - _buildMessageText(nOfAttachmentWidgets, text, context), - ], - ), - ], - ), - ], - ); - } - - if (_streamChannel.channel.config?.reactions == true && - nOfAttachmentWidgets > 0) { - column.insert( - 0, - Align( - child: _buildReactions(), - alignment: - _isMyMessage ? Alignment.centerLeft : Alignment.centerRight, - ), - ); - column[1] = Stack( - overflow: Overflow.visible, - children: [ - Padding( - padding: EdgeInsets.only( - right: _isMyMessage ? 0.0 : 8.0, - left: _isMyMessage ? 8.0 : 0.0, - ), - child: column[1], - ), - _buildReactionPaint(), - ], - ); - } - - return GestureDetector( - child: IntrinsicWidth( - child: Column( - children: column, - crossAxisAlignment: - _isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, - ), - ), - onTap: () { - final channel = StreamChannel.of(context).channel; - if (widget.message.status == MessageSendingStatus.FAILED) { - channel.sendMessage(widget.message); - return; - } - if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) { - StreamChat.of(context).client.updateMessage( - widget.message, - channel.cid, - ); - return; - } - - if (widget.message.status == MessageSendingStatus.FAILED_DELETE) { - StreamChat.of(context).client.deleteMessage( - widget.message, - channel.cid, - ); - return; - } - }, - onLongPress: () { - if (widget.message.isEphemeral || - widget.message.status == MessageSendingStatus.SENDING) { - return; - } - - if (widget.onMessageActions != null) { - widget.onMessageActions(context, widget.message); - } else { - _showMessageBottomSheet(context); - } - }, - ); - } - - Padding _buildAttachment( - Widget attachmentWidget, - Attachment attachment, - int nOfAttachmentWidgets, - BuildContext context, - ) { - final boxDecoration = _buildBoxDecoration(_isLastUser); - return Padding( - padding: const EdgeInsets.only(bottom: 2.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ClipRRect( - borderRadius: boxDecoration.borderRadius, - child: Container( - decoration: boxDecoration, - constraints: BoxConstraints.loose( - Size.fromWidth(MediaQuery.of(context).size.width * 0.7), - ), - child: attachmentWidget, - margin: EdgeInsets.only( - top: nOfAttachmentWidgets > 1 ? 5 : 0, - ), - ), - ), - ], - ), - ); - } - - Widget _buildSendingError(Widget child) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.message.status == MessageSendingStatus.FAILED) - Text( - 'MESSAGE FAILED · CLICK TO TRY AGAIN', - style: _messageTheme.messageText.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), - fontSize: 11, - ), - ), - if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) - Text( - 'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN', - style: _messageTheme.messageText.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), - fontSize: 11, - ), - ), - if (widget.message.status == MessageSendingStatus.FAILED_DELETE) - Text( - 'MESSAGE DELETE FAILED · CLICK TO TRY AGAIN', - style: _messageTheme.messageText.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(.5) - : Colors.black.withOpacity(.5), - fontSize: 11, - ), - ), - child, - ], - ); - } - - Widget _buildMessageText( - int nOfAttachmentWidgets, - String text, - BuildContext context, - ) { - return Padding( - padding: EdgeInsets.only( - right: _isMyMessage ? 0.0 : 8.0, - left: _isMyMessage ? 8.0 : 0.0, - ), - child: Container( - decoration: - _buildBoxDecoration(_isLastUser || nOfAttachmentWidgets > 0), - padding: EdgeInsets.all(10), - constraints: BoxConstraints.loose( - Size.fromWidth(MediaQuery.of(context).size.width * 0.7), - ), - child: _buildSendingError( - MarkdownBody( - data: text, - onTapLink: (link) { - if (link.startsWith('@')) { - final mentionedUser = widget.message.mentionedUsers.firstWhere( - (u) => '@${u.name.replaceAll(' ', '')}' == link, - orElse: () => null, - ); - - if (widget.onMentionTap != null) { - widget.onMentionTap(mentionedUser); - } else { - print('tap on ${mentionedUser.name}'); - } - } else { - launchURL(context, link); - } - }, - 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, - ), - ), - ).copyWith( - p: _messageTheme.messageText, - ), - ), - ), - ), - ); - } - - String _replaceMentions(String text) { - widget.message.mentionedUsers?.forEach((u) { - text = text.replaceAll( - '@${u.name}', '[@${u.name}](@${u.name.replaceAll(' ', '')})'); - }); - return text; - } - - Widget _buildReactionPaint() { - return widget.message.reactionCounts?.isNotEmpty == true - ? Positioned( - left: _isMyMessage ? 4 : null, - right: !_isMyMessage ? 4 : null, - top: -6, - child: Transform( - transform: - !_isMyMessage ? Matrix4.rotationY(pi) : Matrix4.identity(), - alignment: Alignment.center, - child: CustomPaint( - painter: _ReactionBubblePainter( - Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, - ), - ), - ), - ) - : SizedBox(); - } - - void _showMessageBottomSheet(BuildContext context) { - if (!_streamChannel.channel.config.reactions && - !_streamChannel.channel.config.replies) { - return; - } - - final theme = Theme.of(context); - - showModalBottomSheet( - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), - ), - ), - context: context, - builder: (_) { - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - color: Colors.black87, - child: (_streamChannel.channel.config.reactions && - widget.message.status != MessageSendingStatus.FAILED) - ? ReactionPicker( - channel: StreamChannel.of(context).channel, - reactionToEmoji: reactionToEmoji, - message: widget.message, - ) - : SizedBox(), - ), - _isMyMessage - ? FlatButton( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - 'Delete message', - style: theme.textTheme.headline - .copyWith(color: Colors.red), - ), - ), - onPressed: () { - Navigator.pop(context); - StreamChat.of(context).client.deleteMessage( - widget.message, - _streamChannel.channel.cid, - ); - }, - ) - : SizedBox(), - _isMyMessage - ? FlatButton( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - 'Edit message', - style: theme.textTheme.headline, - ), - ), - onPressed: () async { - Navigator.pop(context); - - _showEditBottomSheet(context); - }, - ) - : SizedBox(), - (_streamChannel.channel.config.replies && - widget.message.status != MessageSendingStatus.FAILED && - widget.message.parentId == null && - !widget.isParent) - ? FlatButton( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - 'Start a thread', - style: theme.textTheme.headline, - ), - ), - onPressed: () { - Navigator.pop(context); - widget.onThreadTap(widget.message); - }, - ) - : SizedBox(), - ], - ), - ); - }); - } - - void _showEditBottomSheet(BuildContext context) { - showModalBottomSheet( - context: context, - elevation: 2, - clipBehavior: Clip.hardEdge, - isScrollControlled: true, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), - ), - ), - builder: (context) { - return StreamChannel( - channel: _streamChannel.channel, - child: Flex( - direction: Axis.vertical, - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only( - top: 16.0, - left: 16.0, - right: 16.0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Edit message', - style: Theme.of(context).textTheme.title, - ), - Container( - height: 30, - padding: const EdgeInsets.all(2.0), - child: AspectRatio( - aspectRatio: 1, - child: RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - disabledElevation: 0, - hoverElevation: 0, - onPressed: () { - Navigator.of(context).pop(); - }, - fillColor: - Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(.1) - : Colors.black.withOpacity(.1), - padding: EdgeInsets.all(4), - child: Icon( - Icons.close, - size: 15, - color: StreamChatTheme.of(context) - .primaryIconTheme - .color, - ), - ), - ), - ), - ], - ), - ), - Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: widget.editMessageInputBuilder != null - ? widget.editMessageInputBuilder(context, widget.message) - : MessageInput( - editMessage: widget.message, - parentMessage: widget.isParent - ? StreamChannel.of(context) - .channel - .state - .messages - .firstWhere((message) => - message.id == widget.message.parentId) - : null, - onMessageSent: (_) { - FocusScope.of(context).unfocus(); - Navigator.pop(context); - }, - ), - ), - ], - ), - ); - }, - ); - } - - Widget _buildReactions() { - return GestureDetector( - onTap: () { - if (widget.onMessageActions != null) { - widget.onMessageActions(context, widget.message); - } else { - _showMessageBottomSheet(context); - } - }, - child: Padding( - padding: EdgeInsets.symmetric( - vertical: widget.message.reactionCounts?.isNotEmpty == true ? 4.0 : 0, - ), - child: Container( - padding: widget.message.reactionCounts?.isNotEmpty == true - ? const EdgeInsets.all(8) - : EdgeInsets.zero, - decoration: BoxDecoration( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, - borderRadius: BorderRadius.all(Radius.circular(14))), - child: AnimatedSwitcher( - duration: Duration(milliseconds: 300), - reverseDuration: Duration(milliseconds: 0), - child: (widget.message.reactionCounts != null && - widget.message.reactionCounts.isNotEmpty) - ? _buildReactionRow() - : SizedBox(), - ), - ), - ), - ); - } - - Row _buildReactionRow() { - return Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - ...widget.message.reactionCounts.keys.map((reactionType) { - return Text( - reactionToEmoji[reactionType] ?? '?', - ); - }), - Padding( - padding: const EdgeInsets.only(left: 4.0), - child: Text( - widget.message.reactionCounts.values - .fold(0, (t, v) => v + t) - .toString(), - style: TextStyle( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.black - : Colors.white, - ), - ), - ), - ], - ); - } - +class MessageWidget extends StatelessWidget { + final void Function(User) onMentionTap; + final void Function(Message) onThreadTap; + final Widget Function(BuildContext, Message) editMessageInputBuilder; + final Widget Function(BuildContext, Message) textBuilder; + final void Function(BuildContext, Message) onMessageActions; + final Message message; + final MessageTheme messageTheme; + final bool reverse; + final ShapeBorder shape; + final ShapeBorder attachmentShape; + final BorderSide borderSide; + final BorderSide attachmentBorderSide; + final BorderRadiusGeometry borderRadiusGeometry; + final BorderRadiusGeometry attachmentBorderRadiusGeometry; + final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry textPadding; + final EdgeInsetsGeometry attachmentPadding; + final DisplayWidget showUserAvatar; + final DisplayWidget showSendingIndicator; + final bool showReactions; + final bool showReplyIndicator; + final bool isParent; + final bool showUsername; + final bool showVideoFullScreen; + final bool showTimestamp; + final bool showDeleteMessage; + final bool showEditMessage; + final Map attachmentBuilders; final Map reactionToEmoji = { 'love': '❤️️', 'haha': '😂', @@ -826,67 +57,566 @@ class _MessageWidgetState extends State 'wow': '😲', }; - @override - void dispose() { - super.dispose(); - } + MessageWidget({ + Key key, + @required this.message, + @required this.messageTheme, + this.reverse = false, + this.shape, + this.attachmentShape, + this.borderSide, + this.attachmentBorderSide, + this.borderRadiusGeometry, + this.attachmentBorderRadiusGeometry, + this.onMentionTap, + this.showUserAvatar = DisplayWidget.show, + this.showSendingIndicator = DisplayWidget.show, + this.showReplyIndicator = true, + this.isParent = false, + this.onThreadTap, + this.showUsername = true, + this.showTimestamp = true, + this.showReactions = true, + this.showDeleteMessage = true, + this.showEditMessage = true, + this.onMessageActions, + this.editMessageInputBuilder, + this.textBuilder, + Map customAttachmentBuilders, + this.showVideoFullScreen = true, + this.padding, + this.textPadding = const EdgeInsets.all(8.0), + this.attachmentPadding = EdgeInsets.zero, + }) : attachmentBuilders = { + 'image': (context, message, attachment) { + return ImageAttachment( + attachment: attachment, + messageTheme: messageTheme, + ); + }, + 'video': (context, message, attachment) { + return VideoAttachment( + enableFullScreen: showVideoFullScreen, + attachment: attachment, + messageTheme: messageTheme, + ); + }, + 'giphy': (context, message, attachment) { + return GiphyAttachment( + attachment: attachment, + messageTheme: messageTheme, + message: message, + ); + }, + 'file': (context, message, attachment) { + return FileAttachment( + attachment: attachment, + ); + }, + }..addAll(customAttachmentBuilders ?? {}), + super(key: key); - Widget _buildTimestamp(Alignment alignment) { - return Padding( - padding: const EdgeInsets.only(top: 5.0), - child: RichText( - text: TextSpan( - style: _messageTheme.createdAt, - children: [ - if (!_isMyMessage && widget.showOtherMessageUsername) - TextSpan( - text: widget.message.user.name, - style: TextStyle(fontWeight: FontWeight.bold), + @override + Widget build(BuildContext context) { + final leftPadding = showUserAvatar != DisplayWidget.gone + ? messageTheme.avatarTheme.constraints.maxWidth + 22.0 + : 12.0; + return Portal( + child: Padding( + padding: padding ?? EdgeInsets.all(8), + child: Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(reverse ? pi : 0), + child: Container( + alignment: Alignment.centerLeft, + child: Container( + constraints: BoxConstraints.loose( + Size.fromWidth(MediaQuery.of(context).size.width * 0.8), ), - if (widget.message.createdAt != null) - TextSpan( - text: - Jiffy(widget.message.createdAt.toLocal()).format(' HH:mm'), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (showSendingIndicator == DisplayWidget.show) + _buildSendingIndicator(), + SizedBox( + width: 2, + ), + if (showSendingIndicator == DisplayWidget.hide) + SizedBox( + width: 6, + ), + if (showUserAvatar == DisplayWidget.show) + _buildUserAvatar(), + SizedBox( + width: 6, + ), + if (showUserAvatar == DisplayWidget.hide) + SizedBox( + width: messageTheme + .avatarTheme.constraints.maxWidth + + 10, + ), + Flexible( + child: Padding( + padding: (message.reactionCounts?.isNotEmpty == + true && + showReactions) + ? EdgeInsets.only( + top: _getReactionsTopPadding()) + : EdgeInsets.zero, + child: PortalEntry( + portalAnchor: Alignment(0, 1), + childAnchor: Alignment.topRight, + portal: _buildReactionIndicator(context), + child: (message.isDeleted && + message.status != + MessageSendingStatus + .FAILED_DELETE) + ? Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY( + reverse ? pi : 0), + child: DeletedMessage( + messageTheme: messageTheme, + ), + ) + : Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + ..._parseAttachments(context), + if (message.text.trim().isNotEmpty) + _buildTextBubble(context), + ], + ), + ), + ), + ), + ], + ), + if (showReplyIndicator && message.replyCount > 0) + _buildReplyIndicator(leftPadding), + ], + ), + ), + if ((message.createdAt != null && showTimestamp) || + showUsername) + _buildUsernameAndTimestamp(leftPadding), + ], ), - ], + ), + ), ), ), ); } - BoxDecoration _buildBoxDecoration(bool rectBorders) { - return BoxDecoration( - border: _isMyMessage - ? null - : Border.all( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withAlpha(24) - : Colors.black.withAlpha(24), - ), - borderRadius: BorderRadius.only( - topLeft: Radius.circular((_isMyMessage || !rectBorders) ? 16 : 2), - bottomLeft: Radius.circular(_isMyMessage ? 16 : 2), - topRight: Radius.circular((_isMyMessage && rectBorders) ? 2 : 16), - bottomRight: Radius.circular(_isMyMessage ? 2 : 16), - ), - color: (widget.message.status == MessageSendingStatus.FAILED || - widget.message.status == MessageSendingStatus.FAILED_UPDATE || - widget.message.status == MessageSendingStatus.FAILED_DELETE) - ? Color(0xffd0021B).withOpacity(.1) - : _messageTheme.messageBackgroundColor, + double _getReactionsTopPadding() { + return 36.0 * + ((message.reactionCounts.values + .where((element) => element > 0) + .length ~/ + 4) + + 1); + } + + Widget _buildReactionsTail(BuildContext context) { + return AnimatedSwitcher( + duration: Duration(milliseconds: 300), + child: message.reactionCounts?.isNotEmpty == true + ? Transform.translate( + offset: Offset(4, 0), + child: CustomPaint( + painter: ReactionBubblePainter( + Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black, + ), + ), + ) + : SizedBox(), ); } - @override - bool get wantKeepAlive { - return widget.message.attachments?.isNotEmpty == true; + Padding _buildUsernameAndTimestamp(double leftPadding) { + return Padding( + padding: EdgeInsets.only( + left: leftPadding, + top: 2, + ), + child: Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(reverse ? pi : 0), + child: RichText( + text: TextSpan( + style: messageTheme.createdAt, + children: [ + if (showUsername) + TextSpan( + text: message.user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + if (message.createdAt != null && showTimestamp) + TextSpan( + text: Jiffy(message.createdAt.toLocal()).format(' HH:mm'), + ), + ], + ), + ), + ), + ); + } + + Widget _buildReactionIndicator(BuildContext context) { + return AnimatedSwitcher( + duration: Duration(milliseconds: 300), + child: (showReactions && + message.reactionCounts?.isNotEmpty == true && + !message.isDeleted) + ? Container( + child: GestureDetector( + onTap: () => onLongPress(context), + child: Container( + width: MediaQuery.of(context).size.width * 0.3, + padding: const EdgeInsets.only( + bottom: 4.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: + Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black, + borderRadius: BorderRadius.all(Radius.circular(14)), + ), + child: _buildReactionsText(context), + ), + ), + _buildReactionsTail(context), + ], + ), + ), + ), + ) + : SizedBox(), + ); + } + + Text _buildReactionsText(BuildContext context) { + return Text( + message.reactionCounts.keys.map((reactionType) { + return reactionToEmoji[reactionType] ?? '?'; + }).join(' ') + + ' ${message.reactionCounts.values.fold(0, (t, v) => v + t).toString()}', + style: TextStyle( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.white, + ), + textAlign: TextAlign.justify, + ); + } + + void _showMessageBottomSheet(BuildContext context) { + final channel = StreamChannel.of(context).channel; + showModalBottomSheet( + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), + ), + context: context, + builder: (context) { + return StreamChannel( + channel: channel, + child: MessageActionsBottomSheet( + showDeleteMessage: showDeleteMessage, + message: message, + editMessageInputBuilder: editMessageInputBuilder, + onThreadTap: onThreadTap, + showEditMessage: showEditMessage, + showReactions: showReactions, + showReply: showReplyIndicator, + ), + ); + }); + } + + List _parseAttachments(BuildContext context) { + return message.attachments?.map((attachment) { + final attachmentBuilder = attachmentBuilders[attachment.type]; + + if (attachmentBuilder == null) { + return SizedBox(); + } + + return Padding( + padding: EdgeInsets.only( + bottom: 4, + ), + child: GestureDetector( + onTap: () => retryMessage(context), + onLongPress: () => onLongPress(context), + child: Material( + color: _getBackgroundColor(), + clipBehavior: Clip.hardEdge, + shape: attachmentShape ?? + shape ?? + ContinuousRectangleBorder( + side: attachmentBorderSide ?? + borderSide ?? + BorderSide( + color: + Theme.of(context).brightness == Brightness.dark + ? Colors.white.withAlpha(24) + : Colors.black.withAlpha(24), + ), + borderRadius: attachmentBorderRadiusGeometry ?? + borderRadiusGeometry ?? + BorderRadius.zero, + ), + child: Padding( + padding: attachmentPadding, + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + getFailedMessageWidget( + context, + padding: const EdgeInsets.all(8.0), + ), + attachmentBuilder( + context, + message, + attachment, + ), + ], + ), + ), + ), + ), + ), + ); + })?.toList() ?? + []; + } + + void onLongPress(BuildContext context) { + if (message.isEphemeral || message.status == MessageSendingStatus.SENDING) { + return; + } + + if (onMessageActions != null) { + onMessageActions(context, message); + } else { + _showMessageBottomSheet(context); + } + return; + } + + Widget _buildReplyIndicator(double leftPadding) { + return Padding( + padding: EdgeInsets.only( + left: leftPadding, + ), + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: ReplyIndicator( + message: message, + reversed: reverse, + messageTheme: messageTheme, + onTap: onThreadTap != null + ? () { + onThreadTap(message); + } + : null, + ), + ), + ); + } + + Widget _buildSendingIndicator() { + return Transform.translate( + offset: Offset( + 0, + 4, + ), + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: SendingIndicator( + message: message, + ), + ), + ); + } + + Widget _buildUserAvatar() => Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: Transform.translate( + offset: + Offset(0, messageTheme.avatarTheme.constraints.maxHeight / 2), + child: UserAvatar( + user: message.user, + constraints: messageTheme.avatarTheme.constraints, + ), + ), + ), + ); + + Widget getFailedMessageWidget( + BuildContext context, { + EdgeInsetsGeometry padding, + }) { + Widget widget; + if (message.status == MessageSendingStatus.FAILED) + widget = Text( + 'MESSAGE FAILED · CLICK TO TRY AGAIN', + style: messageTheme.messageText.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withOpacity(.5) + : Colors.black.withOpacity(.5), + fontSize: 11, + ), + ); + if (message.status == MessageSendingStatus.FAILED_UPDATE) + widget = Text( + 'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN', + style: messageTheme.messageText.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withOpacity(.5) + : Colors.black.withOpacity(.5), + fontSize: 11, + ), + ); + if (message.status == MessageSendingStatus.FAILED_DELETE) + widget = Text( + 'MESSAGE DELETE FAILED · CLICK TO TRY AGAIN', + style: messageTheme.messageText.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withOpacity(.5) + : Colors.black.withOpacity(.5), + fontSize: 11, + ), + ); + + if (widget != null) { + return Padding( + padding: padding ?? EdgeInsets.zero, + child: widget, + ); + } + + return SizedBox(); + } + + Widget _buildTextBubble(BuildContext context) { + return GestureDetector( + onTap: () => retryMessage(context), + onLongPress: () => onLongPress(context), + child: Material( + shape: shape ?? + ContinuousRectangleBorder( + side: borderSide ?? + BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withAlpha(24) + : Colors.black.withAlpha(24), + ), + borderRadius: borderRadiusGeometry ?? BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Padding( + padding: textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + getFailedMessageWidget(context), + _buildText(context), + ], + ), + ), + ), + ), + ); + } + + Color _getBackgroundColor() { + return (message.status == MessageSendingStatus.FAILED || + message.status == MessageSendingStatus.FAILED_UPDATE || + message.status == MessageSendingStatus.FAILED_DELETE) + ? Color(0xffd0021B).withOpacity(.1) + : messageTheme.messageBackgroundColor; + } + + void retryMessage(BuildContext context) { + final channel = StreamChannel.of(context).channel; + if (message.status == MessageSendingStatus.FAILED) { + channel.sendMessage(message); + return; + } + if (message.status == MessageSendingStatus.FAILED_UPDATE) { + StreamChat.of(context).client.updateMessage( + message, + channel.cid, + ); + return; + } + + if (message.status == MessageSendingStatus.FAILED_DELETE) { + StreamChat.of(context).client.deleteMessage( + message, + channel.cid, + ); + return; + } + } + + Widget _buildText(BuildContext context) { + return textBuilder != null + ? textBuilder(context, message) + : MessageText( + message: message, + onMentionTap: onMentionTap, + messageTheme: messageTheme, + ); } } -class _ReactionBubblePainter extends CustomPainter { +class ReactionBubblePainter extends CustomPainter { final Color color; - _ReactionBubblePainter(this.color); + ReactionBubblePainter(this.color); @override void paint(Canvas canvas, Size size) { diff --git a/lib/src/reaction_picker.dart b/lib/src/reaction_picker.dart index d24d4e18..56127476 100644 --- a/lib/src/reaction_picker.dart +++ b/lib/src/reaction_picker.dart @@ -24,47 +24,50 @@ class ReactionPicker extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: reactionToEmoji.keys.map((reactionType) { - final ownReactionIndex = message.ownReactions - ?.indexWhere((reaction) => reaction.type == reactionType) ?? - -1; - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - IconButton( - iconSize: size, - icon: Text( - reactionToEmoji[reactionType], - style: TextStyle( - fontSize: size - 10, + return Container( + color: Colors.black87, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: reactionToEmoji.keys.map((reactionType) { + final ownReactionIndex = message.ownReactions + ?.indexWhere((reaction) => reaction.type == reactionType) ?? + -1; + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + IconButton( + iconSize: size, + icon: Text( + reactionToEmoji[reactionType], + style: TextStyle( + fontSize: size - 10, + ), ), + onPressed: () { + if (ownReactionIndex != -1) { + removeReaction( + context, message.ownReactions[ownReactionIndex]); + } else { + sendReaction(context, reactionType); + } + }, ), - onPressed: () { - if (ownReactionIndex != -1) { - removeReaction( - context, message.ownReactions[ownReactionIndex]); - } else { - sendReaction(context, reactionType); - } - }, - ), - ownReactionIndex != -1 - ? Padding( - padding: const EdgeInsets.only(bottom: 4.0), - child: Text( - message.ownReactions[ownReactionIndex].score.toString(), - style: TextStyle(color: Colors.white), - ), - ) - : SizedBox(), - ], - ); - }).toList(), + ownReactionIndex != -1 + ? Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: Text( + message.ownReactions[ownReactionIndex].score.toString(), + style: TextStyle(color: Colors.white), + ), + ) + : SizedBox(), + ], + ); + }).toList(), + ), ); } diff --git a/lib/src/reply_indicator.dart b/lib/src/reply_indicator.dart index e7a8f3c5..cdb1cd57 100644 --- a/lib/src/reply_indicator.dart +++ b/lib/src/reply_indicator.dart @@ -47,6 +47,7 @@ class ReplyIndicator extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(vertical: 2.0), child: Row( + mainAxisSize: MainAxisSize.min, children: row, ), ), diff --git a/lib/src/sending_indicator.dart b/lib/src/sending_indicator.dart index 9c7ebb57..81e7dfef 100644 --- a/lib/src/sending_indicator.dart +++ b/lib/src/sending_indicator.dart @@ -13,53 +13,38 @@ class SendingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { if (message.status == MessageSendingStatus.SENT || message.status == null) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 1.0, - ), - child: CircleAvatar( - radius: 4, - backgroundColor: StreamChatTheme.of(context).accentColor, - child: Icon( - Icons.done, - color: Colors.white, - size: 4, - ), + return CircleAvatar( + radius: 4, + backgroundColor: StreamChatTheme.of(context).accentColor, + child: Icon( + Icons.done, + color: Colors.white, + size: 4, ), ); } if (message.status == MessageSendingStatus.SENDING || message.status == MessageSendingStatus.UPDATING) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 1.0, - ), - child: CircleAvatar( - radius: 4, - backgroundColor: Colors.grey, - child: Icon( - Icons.access_time, - size: 4, - color: Colors.white, - ), + return CircleAvatar( + radius: 4, + backgroundColor: Colors.grey, + child: Icon( + Icons.access_time, + size: 4, + color: Colors.white, ), ); } if (message.status == MessageSendingStatus.FAILED || message.status == MessageSendingStatus.FAILED_UPDATE || message.status == MessageSendingStatus.FAILED_DELETE) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 1.0, - ), - child: CircleAvatar( - radius: 4, - backgroundColor: Color(0xffd0021B).withOpacity(.1), - child: Icon( - Icons.error_outline, - size: 4, - color: Colors.white, - ), + return CircleAvatar( + radius: 4, + backgroundColor: Color(0xffd0021B).withOpacity(.1), + child: Icon( + Icons.error_outline, + size: 4, + color: Colors.white, ), ); } diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 09c67b6d..17b018a6 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -13,6 +13,7 @@ export 'src/giphy_attachment.dart'; export 'src/image_attachment.dart'; export 'src/message_input.dart'; export 'src/message_list_view.dart'; +export 'src/message_text.dart'; export 'src/message_widget.dart'; export 'src/reaction_picker.dart'; export 'src/reply_indicator.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index d7661473..06d6b00a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,18 +12,19 @@ dependencies: photo_view: ^0.9.2 rxdart: ^0.24.0 jiffy: ^3.0.1 - cached_network_image: ^2.2.0 + flutter_portal: ^0.1.0 + cached_network_image: ^2.2.0+1 flutter_markdown: ^0.3.5 url_launcher: ^5.4.5 - video_player: ^0.10.9+1 + video_player: ^0.10.10 chewie: ^0.9.10 file_picker: ^1.8.0+2 - image_picker: ^0.6.5+3 + image_picker: ^0.6.6+1 flutter_keyboard_visibility: ^2.0.0 stream_chat: path: ../stream_chat_dart mime: ^0.9.6+3 - visibility_detector: ^0.1.4 + visibility_detector: ^0.1.5 http_parser: ^3.1.4 dev_dependencies: From 18ded607a84013a34d59c6cded6886e1918811c4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 11 May 2020 16:53:07 +0200 Subject: [PATCH 102/133] fix padding in messages --- lib/src/date_divider.dart | 51 ++++++++++++++++------------------ lib/src/deleted_message.dart | 17 +++++++----- lib/src/message_list_view.dart | 17 +++++++----- lib/src/message_widget.dart | 3 +- 4 files changed, 46 insertions(+), 42 deletions(-) diff --git a/lib/src/date_divider.dart b/lib/src/date_divider.dart index 970169b8..fdf73049 100644 --- a/lib/src/date_divider.dart +++ b/lib/src/date_divider.dart @@ -43,39 +43,36 @@ class DateDivider extends StatelessWidget { dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase(); } - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - divider, - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: Text.rich( - TextSpan( - children: [ - TextSpan( - text: dayInfo, - style: TextStyle( - fontWeight: FontWeight.bold, - ), + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + divider, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: dayInfo, + style: TextStyle( + fontWeight: FontWeight.bold, ), - TextSpan(text: ' AT'), - TextSpan(text: ' $hourInfo'), - ], - style: TextStyle( - fontWeight: FontWeight.normal, ), - ), + TextSpan(text: ' AT'), + TextSpan(text: ' $hourInfo'), + ], style: TextStyle( - fontSize: 10, - color: Theme.of(context).textTheme.title.color.withOpacity(.5), + fontWeight: FontWeight.normal, ), ), + style: TextStyle( + fontSize: 10, + color: Theme.of(context).textTheme.title.color.withOpacity(.5), + ), ), - divider, - ], - ), + ), + divider, + ], ); } } diff --git a/lib/src/deleted_message.dart b/lib/src/deleted_message.dart index a132f48d..93fd62ff 100644 --- a/lib/src/deleted_message.dart +++ b/lib/src/deleted_message.dart @@ -11,13 +11,16 @@ class DeletedMessage extends StatelessWidget { @override Widget build(BuildContext context) { - return Text( - 'This message was deleted...', - style: messageTheme.messageText.copyWith( - fontStyle: FontStyle.italic, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + 'This message was deleted...', + style: messageTheme.messageText.copyWith( + fontStyle: FontStyle.italic, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black, + ), ), ); } diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index da67bcc9..33652222 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -232,12 +232,15 @@ class _MessageListViewState extends State { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ messageWidget, - widget.dateDividerBuilder != null - ? widget - .dateDividerBuilder(nextMessage.createdAt.toLocal()) - : DateDivider( - dateTime: nextMessage.createdAt.toLocal(), - ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: widget.dateDividerBuilder != null + ? widget + .dateDividerBuilder(nextMessage.createdAt.toLocal()) + : DateDivider( + dateTime: nextMessage.createdAt.toLocal(), + ), + ), ], ); } @@ -413,7 +416,7 @@ class _MessageListViewState extends State { padding: EdgeInsets.only( left: 8.0, right: 8.0, - bottom: index == 0 ? 30 : (isLastUser ? 5 : 10), + bottom: index == 0 ? 30 : (isNextUser ? 5 : 10), ), showUsername: !isMyMessage && !isNextUser, showSendingIndicator: isMyMessage && diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 25c40cd1..8e4f85ba 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -173,7 +173,8 @@ class MessageWidget extends StatelessWidget { true && showReactions) ? EdgeInsets.only( - top: _getReactionsTopPadding()) + top: _getReactionsTopPadding(), + ) : EdgeInsets.zero, child: PortalEntry( portalAnchor: Alignment(0, 1), From 073f21dc5d07f92b571054f298785e7be97876f8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 12 May 2020 15:27:16 +0200 Subject: [PATCH 103/133] fix attachment size --- example/lib/main.dart | 10 ++-- lib/src/attachment_actions.dart | 43 +++++++-------- lib/src/attachment_error.dart | 6 ++- lib/src/attachment_title.dart | 2 +- lib/src/file_attachment.dart | 6 ++- lib/src/full_screen_video.dart | 76 ++++++++++++++++++++++++++ lib/src/giphy_attachment.dart | 65 ++++++++++------------ lib/src/image_attachment.dart | 96 +++++++++++++++++---------------- lib/src/message_widget.dart | 23 ++++++-- lib/src/video_attachment.dart | 77 ++++++++++++++++++++------ 10 files changed, 267 insertions(+), 137 deletions(-) create mode 100644 lib/src/full_screen_video.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 251af27e..4355e1f9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -111,11 +111,11 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( -// filter: { -// 'members': { -// '\$in': [StreamChat.of(context).user.id], -// } -// }, + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/lib/src/attachment_actions.dart b/lib/src/attachment_actions.dart index 15d4665f..f101ef53 100644 --- a/lib/src/attachment_actions.dart +++ b/lib/src/attachment_actions.dart @@ -15,42 +15,37 @@ class AttachmentActions extends StatelessWidget { Widget build(BuildContext context) { final streamChannel = StreamChannel.of(context); return Row( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, children: attachment.actions?.map((action) { if (action.style == 'primary') { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: FlatButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Text('${action.text}'), - color: action.style == 'primary' - ? StreamChatTheme.of(context).accentColor - : null, - textColor: Colors.white, - onPressed: () { - streamChannel.channel.sendAction(message, { - action.name: action.value, - }); - }, - ), - ); - } - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: OutlineButton( + return FlatButton( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Text('${action.text}'), - color: StreamChatTheme.of(context).accentColor, + color: action.style == 'primary' + ? StreamChatTheme.of(context).accentColor + : null, + textColor: Colors.white, onPressed: () { streamChannel.channel.sendAction(message, { action.name: action.value, }); }, + ); + } + return OutlineButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), ), + child: Text('${action.text}'), + color: StreamChatTheme.of(context).accentColor, + onPressed: () { + streamChannel.channel.sendAction(message, { + action.name: action.value, + }); + }, ); })?.toList(), ); diff --git a/lib/src/attachment_error.dart b/lib/src/attachment_error.dart index e8f87ab4..e08867b8 100644 --- a/lib/src/attachment_error.dart +++ b/lib/src/attachment_error.dart @@ -5,10 +5,12 @@ import 'package:stream_chat/stream_chat.dart'; class AttachmentError extends StatelessWidget { final Attachment attachment; + final Size size; const AttachmentError({ Key key, @required this.attachment, + this.size, }) : super(key: key); @override @@ -20,8 +22,8 @@ class AttachmentError extends StatelessWidget { } return Center( child: Container( - width: 200, - height: 140, + width: size?.width, + height: size?.height, color: Color(0xffd0021B).withOpacity(.1), child: Center( child: Icon( diff --git a/lib/src/attachment_title.dart b/lib/src/attachment_title.dart index 4d30f7b2..efa34765 100644 --- a/lib/src/attachment_title.dart +++ b/lib/src/attachment_title.dart @@ -26,7 +26,7 @@ class AttachmentTitle extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( attachment.title, diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart index d41cd13c..05318862 100644 --- a/lib/src/file_attachment.dart +++ b/lib/src/file_attachment.dart @@ -4,10 +4,12 @@ import 'package:stream_chat_flutter/src/utils.dart'; class FileAttachment extends StatelessWidget { final Attachment attachment; + final Size size; const FileAttachment({ Key key, @required this.attachment, + this.size, }) : super(key: key); @override @@ -18,8 +20,8 @@ class FileAttachment extends StatelessWidget { launchURL(context, attachment.assetUrl); }, child: Container( - width: 100, - height: 100, + width: size?.width ?? 100, + height: size?.height ?? 100, child: Center( child: Icon(Icons.attach_file), ), diff --git a/lib/src/full_screen_video.dart b/lib/src/full_screen_video.dart new file mode 100644 index 00000000..3bed2d09 --- /dev/null +++ b/lib/src/full_screen_video.dart @@ -0,0 +1,76 @@ +import 'package:chewie/chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:video_player/video_player.dart'; + +import 'utils.dart'; + +class FullScreenVideo extends StatefulWidget { + final Attachment attachment; + + FullScreenVideo({ + Key key, + @required this.attachment, + }) : super(key: key); + + @override + _FullScreenVideoState createState() => _FullScreenVideoState(); +} + +class _FullScreenVideoState extends State { + ChewieController _chewieController; + VideoPlayerController _videoPlayerController; + bool initialized = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Builder( + builder: (context) { + if (!initialized) { + return Center( + child: CircularProgressIndicator(), + ); + } + + return Chewie( + controller: _chewieController, + ); + }, + ), + ); + } + + @override + void initState() { + super.initState(); + _videoPlayerController = + VideoPlayerController.network(widget.attachment.assetUrl); + _videoPlayerController.initialize().whenComplete(() { + setState(() { + initialized = true; + }); + }); + + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoInitialize: false, + aspectRatio: _videoPlayerController.value.aspectRatio, + ); + _videoPlayerController.addListener(() { + if (_videoPlayerController.value.hasError) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + Navigator.pop(context); + launchURL(context, widget.attachment.titleLink); + }); + } + }); + } + + @override + void dispose() { + _videoPlayerController.dispose(); + _chewieController.dispose(); + super.dispose(); + } +} diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index 4ad9e7ab..9cc40d07 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -5,19 +5,20 @@ import 'package:stream_chat_flutter/src/attachment_actions.dart'; import '../stream_chat_flutter.dart'; import 'attachment_error.dart'; import 'attachment_title.dart'; -import 'full_screen_image.dart'; import 'utils.dart'; class GiphyAttachment extends StatelessWidget { final Attachment attachment; final MessageTheme messageTheme; final Message message; + final Size size; const GiphyAttachment({ Key key, this.attachment, this.messageTheme, this.message, + this.size, }) : super(key: key); @override @@ -36,39 +37,26 @@ class GiphyAttachment extends StatelessWidget { children: [ Stack( children: [ - Hero( - tag: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - child: CachedNetworkImage( - imageBuilder: (context, provider) { - return GestureDetector( - child: Image(image: provider), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - url: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - ); - })); - }, - ); - }, - placeholder: (_, __) { - return Container( - width: 200, - height: 140, - ); - }, - imageUrl: attachment.thumbUrl ?? - attachment.imageUrl ?? - attachment.assetUrl, - errorWidget: (context, url, error) => AttachmentError( - attachment: attachment, - ), - fit: BoxFit.cover, + CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + return Container( + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), + ), + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + size: size, ), + fit: BoxFit.cover, ), if (attachment.titleLink != null || attachment.ogScrapeUrl != null) Positioned.fill( @@ -85,9 +73,14 @@ class GiphyAttachment extends StatelessWidget { ], ), if (attachment.title != null) - AttachmentTitle( - messageTheme: messageTheme, - attachment: attachment, + Container( + alignment: Alignment.bottomCenter, + child: Material( + child: AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, + ), + ), ), if (attachment.actions != null) AttachmentActions( diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index eab4ada6..d1673dad 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -10,11 +10,13 @@ import 'utils.dart'; class ImageAttachment extends StatelessWidget { final Attachment attachment; final MessageTheme messageTheme; + final Size size; const ImageAttachment({ Key key, this.attachment, this.messageTheme, + this.size, }) : super(key: key); @override @@ -26,38 +28,34 @@ class ImageAttachment extends StatelessWidget { attachment: attachment, ); } - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Stack( - children: [ - Hero( - tag: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - child: CachedNetworkImage( - imageBuilder: (context, provider) { - return GestureDetector( - child: Image( - image: provider, - fit: BoxFit.cover, - ), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - url: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - ); - })); - }, + return SizedBox.fromSize( + size: size, + child: Stack( + children: [ + Hero( + tag: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + child: GestureDetector( + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, ); - }, + })); + }, + child: CachedNetworkImage( + height: size?.height, + width: size?.width, placeholder: (_, __) { return Container( - width: 200, - height: 140, + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), + ), ); }, imageUrl: attachment.thumbUrl ?? @@ -65,30 +63,38 @@ class ImageAttachment extends StatelessWidget { attachment.assetUrl, errorWidget: (context, url, error) => AttachmentError( attachment: attachment, + size: size, ), fit: BoxFit.cover, ), ), - if (attachment.titleLink != null || attachment.ogScrapeUrl != null) - Positioned.fill( + ), + if (attachment.title != null) + Positioned.fill( + child: Align( + alignment: Alignment.bottomCenter, child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => launchURL( - context, - attachment.titleLink ?? attachment.ogScrapeUrl, - ), + child: AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, ), ), ), - ], - ), - if (attachment.title != null) - AttachmentTitle( - messageTheme: messageTheme, - attachment: attachment, - ), - ], + ), + if (attachment.titleLink != null || attachment.ogScrapeUrl != null) + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => launchURL( + context, + attachment.titleLink ?? attachment.ogScrapeUrl, + ), + ), + ), + ), + ], + ), ); } } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 8e4f85ba..a9f0e642 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -43,7 +43,6 @@ class MessageWidget extends StatelessWidget { final bool showReplyIndicator; final bool isParent; final bool showUsername; - final bool showVideoFullScreen; final bool showTimestamp; final bool showDeleteMessage; final bool showEditMessage; @@ -83,7 +82,6 @@ class MessageWidget extends StatelessWidget { this.editMessageInputBuilder, this.textBuilder, Map customAttachmentBuilders, - this.showVideoFullScreen = true, this.padding, this.textPadding = const EdgeInsets.all(8.0), this.attachmentPadding = EdgeInsets.zero, @@ -92,13 +90,20 @@ class MessageWidget extends StatelessWidget { return ImageAttachment( attachment: attachment, messageTheme: messageTheme, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), ); }, 'video': (context, message, attachment) { return VideoAttachment( - enableFullScreen: showVideoFullScreen, attachment: attachment, messageTheme: messageTheme, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), ); }, 'giphy': (context, message, attachment) { @@ -106,11 +111,19 @@ class MessageWidget extends StatelessWidget { attachment: attachment, messageTheme: messageTheme, message: message, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), ); }, 'file': (context, message, attachment) { return FileAttachment( attachment: attachment, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), ); }, }..addAll(customAttachmentBuilders ?? {}), @@ -165,7 +178,7 @@ class MessageWidget extends StatelessWidget { SizedBox( width: messageTheme .avatarTheme.constraints.maxWidth + - 10, + 8, ), Flexible( child: Padding( @@ -228,7 +241,7 @@ class MessageWidget extends StatelessWidget { ((message.reactionCounts.values .where((element) => element > 0) .length ~/ - 4) + + 5) + 1); } diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index 0d3339bb..2b4b1c7c 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -1,6 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/full_screen_video.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; @@ -10,14 +11,14 @@ import 'attachment_title.dart'; class VideoAttachment extends StatefulWidget { final Attachment attachment; - final bool enableFullScreen; final MessageTheme messageTheme; + final Size size; VideoAttachment({ Key key, @required this.attachment, @required this.messageTheme, - this.enableFullScreen = true, + this.size, }) : super(key: key); @override @@ -33,23 +34,25 @@ class _VideoAttachmentState extends State { Widget build(BuildContext context) { if (!initialized) { return Container( - height: 100, - width: 100, + height: widget.size?.height ?? 100, + width: widget.size?.width ?? 100, child: Center( child: CircularProgressIndicator(), ), ); } _chewieController = ChewieController( - allowFullScreen: widget.enableFullScreen, videoPlayerController: _videoPlayerController, autoInitialize: false, + showControls: false, aspectRatio: _videoPlayerController.value.aspectRatio, errorBuilder: (_, e) { if (widget.attachment.thumbUrl != null) { return Stack( children: [ Container( + height: widget.size?.height, + width: widget.size?.width, decoration: BoxDecoration( image: DecorationImage( fit: BoxFit.cover, @@ -72,22 +75,62 @@ class _VideoAttachmentState extends State { } return AttachmentError( attachment: widget.attachment, + size: widget.size, ); }); - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Chewie( - controller: _chewieController, - ), - if (widget.attachment.title != null) - AttachmentTitle( - messageTheme: widget.messageTheme, - attachment: widget.attachment, + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => FullScreenVideo( + attachment: widget.attachment, + ), ), - ], + ); + }, + child: Container( + height: widget.size?.height, + width: widget.size?.width, + child: Flex( + direction: Axis.vertical, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: FittedBox( + fit: BoxFit.cover, + child: Stack( + children: [ + Chewie( + controller: _chewieController, + ), + Positioned.fill( + child: Center( + child: Material( + shape: CircleBorder(), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Icon(Icons.play_arrow), + ), + ), + ), + ), + ], + ), + ), + ), + if (widget.attachment.title != null) + Material( + child: AttachmentTitle( + messageTheme: widget.messageTheme, + attachment: widget.attachment, + ), + ), + ], + ), + ), ); } From 4dcdc457f45f99e0d955df8edd399d958fc52d38 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 12 May 2020 15:58:24 +0200 Subject: [PATCH 104/133] do not show local notifications on silent messages --- lib/src/stream_chat.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 52e7ccc6..a69b44e0 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -201,6 +201,7 @@ class StreamChatState extends State with WidgetsBindingObserver { _newMessageSubscription = client .on(EventType.messageNew) .where((e) => e.user?.id != user.id) + .where((e) => e.message.silent != true) .listen((event) async { var channel = client.state.channels[event.cid]; From a1a3cb177df571696cf70c5160ef1ecd6c13edf6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 13 May 2020 15:51:45 +0200 Subject: [PATCH 105/133] fix video attachment fullscreen --- example/lib/main.dart | 2 +- lib/src/full_screen_video.dart | 25 +++++---- lib/src/image_attachment.dart | 96 ++++++++++++++++++---------------- lib/src/message_widget.dart | 66 +++++++++++++++++++++-- lib/src/stream_chat_theme.dart | 11 +++- lib/src/video_attachment.dart | 6 +-- lib/stream_chat_flutter.dart | 1 + pubspec.yaml | 4 +- 8 files changed, 142 insertions(+), 69 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 4355e1f9..aa454649 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -94,7 +94,7 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.system, + themeMode: ThemeMode.dark, home: Container( child: StreamChat( client: client, diff --git a/lib/src/full_screen_video.dart b/lib/src/full_screen_video.dart index 3bed2d09..000f13c1 100644 --- a/lib/src/full_screen_video.dart +++ b/lib/src/full_screen_video.dart @@ -32,7 +32,6 @@ class _FullScreenVideoState extends State { child: CircularProgressIndicator(), ); } - return Chewie( controller: _chewieController, ); @@ -49,28 +48,28 @@ class _FullScreenVideoState extends State { _videoPlayerController.initialize().whenComplete(() { setState(() { initialized = true; + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoInitialize: false, + aspectRatio: _videoPlayerController.value.aspectRatio, + ); }); }); - _chewieController = ChewieController( - videoPlayerController: _videoPlayerController, - autoInitialize: false, - aspectRatio: _videoPlayerController.value.aspectRatio, - ); - _videoPlayerController.addListener(() { + VoidCallback errorListener; + errorListener = () { if (_videoPlayerController.value.hasError) { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - Navigator.pop(context); - launchURL(context, widget.attachment.titleLink); - }); + Navigator.pop(context); + launchURL(context, widget.attachment.titleLink); } - }); + _videoPlayerController.removeListener(errorListener); + }; + _videoPlayerController.addListener(errorListener); } @override void dispose() { _videoPlayerController.dispose(); - _chewieController.dispose(); super.dispose(); } } diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index d1673dad..7c27178d 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -32,55 +32,61 @@ class ImageAttachment extends StatelessWidget { size: size, child: Stack( children: [ - Hero( - tag: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - child: GestureDetector( - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - url: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - ); - })); - }, - child: CachedNetworkImage( - height: size?.height, - width: size?.width, - placeholder: (_, __) { - return Container( - width: size?.width, - height: size?.height, - child: Center( - child: CircularProgressIndicator(), + Column( + children: [ + Expanded( + child: Hero( + tag: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + child: GestureDetector( + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + ); + })); + }, + child: CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + return Container( + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), + ), + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + size: size, + ), + fit: BoxFit.cover, ), - ); - }, - imageUrl: attachment.thumbUrl ?? - attachment.imageUrl ?? - attachment.assetUrl, - errorWidget: (context, url, error) => AttachmentError( - attachment: attachment, - size: size, - ), - fit: BoxFit.cover, - ), - ), - ), - if (attachment.title != null) - Positioned.fill( - child: Align( - alignment: Alignment.bottomCenter, - child: Material( - child: AttachmentTitle( - messageTheme: messageTheme, - attachment: attachment, ), ), ), - ), + if (attachment.title != null) + Positioned.fill( + child: Align( + alignment: Alignment.bottomCenter, + child: Material( + child: AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, + ), + ), + ), + ), + ], + ), if (attachment.titleLink != null || attachment.ogScrapeUrl != null) Positioned.fill( child: Material( diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index a9f0e642..124e7e65 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -13,41 +13,98 @@ import 'message_text.dart'; typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment); +/// The display behaviour of a widget enum DisplayWidget { + /// Hides the widget replacing its space with a spacer hide, + + /// Hides the widget not replacing its space gone, + + /// Shows the widget normally show, } +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget.png) +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget_paint.png) +/// +/// It shows a message with reactions, replies and user avatar. +/// +/// Usually you don't use this widget as it's the default message widget used by [MessageListView]. +/// +/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// Modify it to change the widget appearance. class MessageWidget extends StatelessWidget { + /// Function called on mention tap final void Function(User) onMentionTap; + + /// The function called when tapping on replies final void Function(Message) onThreadTap; final Widget Function(BuildContext, Message) editMessageInputBuilder; final Widget Function(BuildContext, Message) textBuilder; + + /// Function called on long press final void Function(BuildContext, Message) onMessageActions; + + /// The message final Message message; + + /// The message theme final MessageTheme messageTheme; + + /// If true the widget will be mirrored final bool reverse; + + /// The shape of the message text final ShapeBorder shape; + + /// The shape of an attachment final ShapeBorder attachmentShape; + + /// The borderside of the message text final BorderSide borderSide; + + /// The borderside of an attachment final BorderSide attachmentBorderSide; + + /// The border radius of the message text final BorderRadiusGeometry borderRadiusGeometry; + + /// The border radius of an attachment final BorderRadiusGeometry attachmentBorderRadiusGeometry; + + /// The padding of the widget final EdgeInsetsGeometry padding; + + /// The internal padding of the message text final EdgeInsetsGeometry textPadding; + + /// The internal padding of an attachment final EdgeInsetsGeometry attachmentPadding; + + /// It controls the display behaviour of the user avatar final DisplayWidget showUserAvatar; + + /// It controls the display behaviour of the sending indicator final DisplayWidget showSendingIndicator; + + /// If true the widget will show the reactions final bool showReactions; + + /// If true the widget will show the reply indicator final bool showReplyIndicator; - final bool isParent; + + /// The function called when tapping on UserAvatar + final void Function(User) onUserAvatarTap; + + /// If true show the users username next to the timestamp of the message final bool showUsername; final bool showTimestamp; final bool showDeleteMessage; final bool showEditMessage; final Map attachmentBuilders; - final Map reactionToEmoji = { + + final Map _reactionToEmoji = { 'love': '❤️️', 'haha': '😂', 'like': '👍', @@ -71,13 +128,13 @@ class MessageWidget extends StatelessWidget { this.showUserAvatar = DisplayWidget.show, this.showSendingIndicator = DisplayWidget.show, this.showReplyIndicator = true, - this.isParent = false, this.onThreadTap, this.showUsername = true, this.showTimestamp = true, this.showReactions = true, this.showDeleteMessage = true, this.showEditMessage = true, + this.onUserAvatarTap, this.onMessageActions, this.editMessageInputBuilder, this.textBuilder, @@ -338,7 +395,7 @@ class MessageWidget extends StatelessWidget { Text _buildReactionsText(BuildContext context) { return Text( message.reactionCounts.keys.map((reactionType) { - return reactionToEmoji[reactionType] ?? '?'; + return _reactionToEmoji[reactionType] ?? '?'; }).join(' ') + ' ${message.reactionCounts.values.fold(0, (t, v) => v + t).toString()}', style: TextStyle( @@ -499,6 +556,7 @@ class MessageWidget extends StatelessWidget { Offset(0, messageTheme.avatarTheme.constraints.maxHeight / 2), child: UserAvatar( user: message.user, + onTap: onUserAvatarTap, constraints: messageTheme.avatarTheme.constraints, ), ), diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index 5e06672e..e75d30c3 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -24,7 +24,16 @@ class StreamChatTheme extends InheritedWidget { /// Use this method to get the current [StreamChatThemeData] instance static StreamChatThemeData of(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType().data; + final streamChatTheme = + context.dependOnInheritedWidgetOfExactType(); + + if (streamChatTheme == null) { + throw Exception( + 'You must have a StreamChatTheme widget at the top of your widget tree', + ); + } + + return streamChatTheme.data; } } diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index 2b4b1c7c..e81ea220 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -43,7 +43,7 @@ class _VideoAttachmentState extends State { } _chewieController = ChewieController( videoPlayerController: _videoPlayerController, - autoInitialize: false, + autoInitialize: true, showControls: false, aspectRatio: _videoPlayerController.value.aspectRatio, errorBuilder: (_, e) { @@ -137,8 +137,8 @@ class _VideoAttachmentState extends State { @override void initState() { super.initState(); - _videoPlayerController = VideoPlayerController.network( - widget.attachment.localUri ?? widget.attachment.assetUrl); + _videoPlayerController = + VideoPlayerController.network(widget.attachment.assetUrl); _videoPlayerController.initialize().whenComplete(() { setState(() { initialized = true; diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 17b018a6..9411aea1 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -9,6 +9,7 @@ export 'src/channels_bloc.dart'; export 'src/date_divider.dart'; export 'src/deleted_message.dart'; export 'src/file_attachment.dart'; +export 'src/full_screen_video.dart'; export 'src/giphy_attachment.dart'; export 'src/image_attachment.dart'; export 'src/message_input.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 06d6b00a..031dbddc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,10 +15,10 @@ dependencies: flutter_portal: ^0.1.0 cached_network_image: ^2.2.0+1 flutter_markdown: ^0.3.5 - url_launcher: ^5.4.5 + url_launcher: ^5.4.7 video_player: ^0.10.10 chewie: ^0.9.10 - file_picker: ^1.8.0+2 + file_picker: ^1.9.0+1 image_picker: ^0.6.6+1 flutter_keyboard_visibility: ^2.0.0 stream_chat: From 2de3cc2b69a8748e64ddbaf858228c232d053bcf Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 13 May 2020 16:54:23 +0200 Subject: [PATCH 106/133] version bump --- CHANGELOG.md | 7 ++ example/lib/customize_message_widget.dart | 129 ++++++++++++++++++++++ example/lib/main.dart | 4 +- lib/src/giphy_attachment.dart | 59 +++++----- lib/src/image_attachment.dart | 13 +-- lib/src/message_list_view.dart | 9 ++ lib/src/message_widget.dart | 9 +- pubspec.yaml | 5 +- 8 files changed, 188 insertions(+), 47 deletions(-) create mode 100644 example/lib/customize_message_widget.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index f0153a4d..4ee52eee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.2.1-alpha + +- New message widget +- Moved some properties from `MessageListView` to `MessageWidget` +- Added `MessageDetails` property to `MessageBuilder` +- Added example to customize the message using `MessageWidget` (`customize_message_widget.dart`) + ## 0.2.0-alpha+15 - Add background color in StreamChatTheme diff --git a/example/lib/customize_message_widget.dart b/example/lib/customize_message_widget.dart new file mode 100644 index 00000000..fa10cf10 --- /dev/null +++ b/example/lib/customize_message_widget.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Fifth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) +/// +/// Customizing how messages are rendered is another very common use-case that the SDK supports easily. +/// +/// Replace the built-in message component with your own is done by passing it as a builder function to the [MessageListView] widget. +/// +/// The message builder function will get the usual [BuildContext] argument as well as the [Message] object and its position inside the list. +/// +/// If you look at the code you can see that we use [StreamChat.of] to retrieve the current user so that we can style messages own messages in a different way. +/// +/// Since custom widgets and builders are always children of [StreamChat] or part of a [Channel], +/// you can use [StreamChat.of], [StreamChannel.of] and [StreamChatTheme.of] to use the API client directly +/// or to retrieve outer scope needed such as messages from the [Channel.state]. +void main() async { + final client = Client( + 'b67pax5b2wdq', + logLevel: Level.INFO, + ); + + await client.setUser( + User(id: 'falling-mountain-7'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + ); + + runApp(MyApp(client)); +} + +class MyApp extends StatelessWidget { + final Client client; + + MyApp(this.client); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Container( + child: StreamChat( + client: client, + child: ChannelListPage(), + ), + ), + ); + } +} + +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelsBloc( + child: 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, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView( + messageBuilder: _messageBuilder, + ), + ), + MessageInput(), + ], + ), + ); + } + + Widget _messageBuilder( + BuildContext context, + MessageDetails details, + List messages, + ) { + final message = details.message; + final color = details.isMyMessage ? Colors.blueGrey : Colors.blue; + return MessageWidget( + message: message, + messageTheme: details.isMyMessage + ? StreamChatTheme.of(context).ownMessageTheme + : StreamChatTheme.of(context).otherMessageTheme, + borderSide: BorderSide( + color: color, + width: 2, + ), + padding: const EdgeInsets.all(2), + attachmentBorderSide: BorderSide( + color: color, + width: 2, + ), + attachmentPadding: EdgeInsets.all(8), + borderRadiusGeometry: BorderRadius.vertical( + top: !details.isLastUser ? Radius.circular(16) : Radius.zero, + bottom: !details.isNextUser ? Radius.circular(16) : Radius.zero, + ), + showSendingIndicator: DisplayWidget.gone, + reverse: false, + showUserAvatar: + details.isNextUser ? DisplayWidget.hide : DisplayWidget.show, + showTimestamp: !details.isNextUser, + showUsername: !details.isNextUser, + showReactions: false, + showReplyIndicator: false, + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index aa454649..e170c5d6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -67,7 +67,7 @@ void _initNotifications(Client client) { void main() async { final client = Client( 's2dxdhpxd94g', - logLevel: Level.SEVERE, + logLevel: Level.INFO, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, backgroundKeepAlive: Duration.zero, persistenceEnabled: true, @@ -94,7 +94,7 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.dark, + themeMode: ThemeMode.system, home: Container( child: StreamChat( client: client, diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index 9cc40d07..994d2e6e 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -5,7 +5,7 @@ import 'package:stream_chat_flutter/src/attachment_actions.dart'; import '../stream_chat_flutter.dart'; import 'attachment_error.dart'; import 'attachment_title.dart'; -import 'utils.dart'; +import 'full_screen_image.dart'; class GiphyAttachment extends StatelessWidget { final Attachment attachment; @@ -37,39 +37,38 @@ class GiphyAttachment extends StatelessWidget { children: [ Stack( children: [ - CachedNetworkImage( - height: size?.height, - width: size?.width, - placeholder: (_, __) { - return Container( - width: size?.width, - height: size?.height, - child: Center( - child: CircularProgressIndicator(), - ), - ); + GestureDetector( + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + ); + })); }, - imageUrl: attachment.thumbUrl ?? - attachment.imageUrl ?? - attachment.assetUrl, - errorWidget: (context, url, error) => AttachmentError( - attachment: attachment, - size: size, - ), - fit: BoxFit.cover, - ), - if (attachment.titleLink != null || attachment.ogScrapeUrl != null) - Positioned.fill( - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => launchURL( - context, - attachment.titleLink ?? attachment.ogScrapeUrl, + child: CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + return Container( + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), ), - ), + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + size: size, ), + fit: BoxFit.cover, ), + ), ], ), if (attachment.title != null) diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index 7c27178d..d39a8948 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -74,15 +74,10 @@ class ImageAttachment extends StatelessWidget { ), ), if (attachment.title != null) - Positioned.fill( - child: Align( - alignment: Alignment.bottomCenter, - child: Material( - child: AttachmentTitle( - messageTheme: messageTheme, - attachment: attachment, - ), - ), + Material( + child: AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, ), ), ], diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 33652222..5750a8a2 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -23,10 +23,19 @@ typedef ThreadBuilder = Widget Function(BuildContext context, Message parent); typedef ThreadTapCallback = void Function(Message, Widget); class MessageDetails { + /// True if the message belongs to the current user bool isMyMessage; + + /// True if the user message is the same of the previous message bool isLastUser; + + /// True if the user message is the same of the next message bool isNextUser; + + /// The message Message message; + + /// The index of the message int index; MessageDetails( diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 124e7e65..02e49e13 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -188,9 +188,12 @@ class MessageWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final leftPadding = showUserAvatar != DisplayWidget.gone - ? messageTheme.avatarTheme.constraints.maxWidth + 22.0 + var leftPadding = showUserAvatar != DisplayWidget.gone + ? messageTheme.avatarTheme.constraints.maxWidth + 23.0 : 12.0; + if (showSendingIndicator == DisplayWidget.gone) { + leftPadding -= 7; + } return Portal( child: Padding( padding: padding ?? EdgeInsets.all(8), @@ -428,7 +431,7 @@ class MessageWidget extends StatelessWidget { onThreadTap: onThreadTap, showEditMessage: showEditMessage, showReactions: showReactions, - showReply: showReplyIndicator, + showReply: showReplyIndicator && onThreadTap != null, ), ); }); diff --git a/pubspec.yaml b/pubspec.yaml index 031dbddc..27ee2d0f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.0-alpha+15 +version: 0.2.1-alpha environment: sdk: ">=2.3.0 <3.0.0" @@ -21,8 +21,7 @@ dependencies: file_picker: ^1.9.0+1 image_picker: ^0.6.6+1 flutter_keyboard_visibility: ^2.0.0 - stream_chat: - path: ../stream_chat_dart + stream_chat: ^0.2.0-alpha+9 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From cb284f773b9d78ba2c0361b55c6a9275465a3abb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 14 May 2020 10:30:56 +0200 Subject: [PATCH 107/133] remove additional navigator widget --- example/ios/Podfile.lock | 63 +++++++++++++++++--- example/ios/Runner.xcodeproj/project.pbxproj | 14 +---- example/lib/custom_message.dart | 9 ++- example/lib/custom_theme.dart | 25 ++++---- example/lib/customize_channel_preview.dart | 9 ++- example/lib/customize_message_widget.dart | 9 +-- example/lib/main.dart | 11 ++-- example/lib/multiple_conversation.dart | 9 +-- example/lib/threads.dart | 9 +-- lib/src/attachment_title.dart | 2 +- lib/src/full_screen_video.dart | 5 +- lib/src/giphy_attachment.dart | 44 ++++++++------ lib/src/image_attachment.dart | 1 + lib/src/message_input.dart | 2 - lib/src/stream_channel.dart | 17 ++++++ lib/src/stream_chat.dart | 22 +------ lib/src/video_attachment.dart | 1 + 17 files changed, 148 insertions(+), 104 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 6e34cefa..9b203d35 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,5 +1,37 @@ PODS: + - DKImagePickerController/Core (4.2.2): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.2.2) + - DKImagePickerController/PhotoGallery (4.2.2): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.2.2) + - DKPhotoGallery (0.0.14): + - DKPhotoGallery/Core (= 0.0.14) + - DKPhotoGallery/Model (= 0.0.14) + - DKPhotoGallery/Preview (= 0.0.14) + - DKPhotoGallery/Resource (= 0.0.14) + - SDWebImage + - SDWebImageFLPlugin + - DKPhotoGallery/Core (0.0.14): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SDWebImageFLPlugin + - DKPhotoGallery/Model (0.0.14): + - SDWebImage + - SDWebImageFLPlugin + - DKPhotoGallery/Preview (0.0.14): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SDWebImageFLPlugin + - DKPhotoGallery/Resource (0.0.14): + - SDWebImage + - SDWebImageFLPlugin - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery - Flutter - Firebase/Core (6.20.0): - Firebase/CoreOnly @@ -53,6 +85,7 @@ PODS: - GoogleUtilities/Reachability (~> 6.5) - GoogleUtilities/UserDefaults (~> 6.5) - Protobuf (>= 3.9.2, ~> 3.9) + - FLAnimatedImage (1.0.12) - Flutter (1.0.0) - flutter_apns (0.0.1): - Flutter @@ -110,6 +143,12 @@ PODS: - PromisesObjC (1.2.8) - Protobuf (3.11.4) - ReachabilitySwift (4.3.1) + - SDWebImage (5.8.0): + - SDWebImage/Core (= 5.8.0) + - SDWebImage/Core (5.8.0) + - SDWebImageFLPlugin (0.4.0): + - FLAnimatedImage (>= 1.0.11) + - SDWebImage/Core (~> 5.6) - shared_preferences (0.0.1): - Flutter - shared_preferences_macos (0.0.1): @@ -163,6 +202,8 @@ DEPENDENCIES: SPEC REPOS: trunk: + - DKImagePickerController + - DKPhotoGallery - Firebase - FirebaseAnalytics - FirebaseAnalyticsInterop @@ -172,6 +213,7 @@ SPEC REPOS: - FirebaseInstallations - FirebaseInstanceID - FirebaseMessaging + - FLAnimatedImage - FMDB - GoogleAppMeasurement - GoogleDataTransport @@ -182,6 +224,8 @@ SPEC REPOS: - PromisesObjC - Protobuf - ReachabilitySwift + - SDWebImage + - SDWebImageFLPlugin - Starscream EXTERNAL SOURCES: @@ -237,9 +281,11 @@ CHECKOUT OPTIONS: :git: https://github.com/GetStream/stream-chat-swift.git SPEC CHECKSUMS: - file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245 + DKImagePickerController: 4a3e7948a848c4348e600b3fe5ce41478835fa10 + DKPhotoGallery: 0290d32343574f06eaa4c26f8f2f8a1035e916be + file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Firebase: fe7f74012742ab403451dd283e6909b8f1fb348a - firebase_messaging: cffb57ce40958c6204f03fb0c81713e4cd1e240c + firebase_messaging: 1069878b13fd61e296607e83897aee0ca0fc1f2e FirebaseAnalytics: 572e467f3d977825266e8ccd52674aa3e6f47eac FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae FirebaseCore: ed0a24c758a57c2b88c5efa8e6a8195e868af589 @@ -248,35 +294,38 @@ SPEC CHECKSUMS: FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b FirebaseInstanceID: 7ee0d6777013bb952f377b41965bf132b6a075be FirebaseMessaging: 4ec33842d36b3319e062e51fb8b35a74f726950d + FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f flutter_keyboard_visibility: 6195387fb6d8f46e5cd6dda4a4154e41f800f545 flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186 - flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b + flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a GoogleAppMeasurement: c29d405ff76e18551b5d158eaba6753fda8c7542 GoogleDataTransport: a857c6a002d201b524dd4bc2ed7e7355ed07e785 GoogleDataTransportCCTSupport: 32f75fbe904c82772fcbb6b6bd4525bfb6f2a862 GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e GzipSwift: 5592f4d62b641e04d06443ba471f8ed76b1363e4 - image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f + image_picker: 66aa71bc96850a90590a35d4c4a2907b0d823109 moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6 nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd - path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d + path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b + SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc + SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 StreamChatClient: a5b5a85b0bcccf3ccb26a6847f110912a8c05e92 - url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 + url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c - video_player: 69c5f029fac4ffe4fc8a85ea7f7b793709661549 + video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 141e283a..6083a722 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -11,12 +11,8 @@ 0BC14C54242B5A7A0028DE94 /* Notifications.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0BC14C4D242B5A7A0028DE94 /* Notifications.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 7DEC2743BD66C91B700A3B97 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BB2E5E4E236267EDF0D8817 /* Pods_Runner.framework */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -50,8 +46,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -68,7 +62,6 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 2452A9E77396497EB4CF3072 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 68F846A6DB42D92393F5F7E0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -77,7 +70,6 @@ 8BB2E5E4E236267EDF0D8817 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -97,8 +89,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 7DEC2743BD66C91B700A3B97 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -119,9 +109,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -312,7 +300,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 5702861DACEDB848A3E454E8 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; diff --git a/example/lib/custom_message.dart b/example/lib/custom_message.dart index 4f1aa2ca..e545a168 100644 --- a/example/lib/custom_message.dart +++ b/example/lib/custom_message.dart @@ -36,12 +36,11 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - home: Container( - child: StreamChat( - client: client, - child: ChannelListPage(), - ), + builder: (context, child) => StreamChat( + child: child, + client: client, ), + home: ChannelListPage(), ); } } diff --git a/example/lib/custom_theme.dart b/example/lib/custom_theme.dart index afd32ccb..27b09350 100644 --- a/example/lib/custom_theme.dart +++ b/example/lib/custom_theme.dart @@ -45,23 +45,22 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: theme, - home: Container( - child: StreamChat( - streamChatThemeData: StreamChatThemeData.fromTheme(theme).copyWith( - ownMessageTheme: MessageTheme( - messageBackgroundColor: Colors.black, - messageText: TextStyle( - color: Colors.white, - ), - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(8), - ), + builder: (context, child) => StreamChat( + child: child, + client: client, + streamChatThemeData: StreamChatThemeData.fromTheme(theme).copyWith( + ownMessageTheme: MessageTheme( + messageBackgroundColor: Colors.black, + messageText: TextStyle( + color: Colors.white, + ), + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(8), ), ), - client: client, - child: ChannelListPage(), ), ), + home: ChannelListPage(), ); } } diff --git a/example/lib/customize_channel_preview.dart b/example/lib/customize_channel_preview.dart index 6fcb5765..c7a34a48 100644 --- a/example/lib/customize_channel_preview.dart +++ b/example/lib/customize_channel_preview.dart @@ -41,12 +41,11 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - home: Container( - child: StreamChat( - client: client, - child: ChannelListPage(), - ), + builder: (context, child) => StreamChat( + child: child, + client: client, ), + home: ChannelListPage(), ); } } diff --git a/example/lib/customize_message_widget.dart b/example/lib/customize_message_widget.dart index fa10cf10..58b4149a 100644 --- a/example/lib/customize_message_widget.dart +++ b/example/lib/customize_message_widget.dart @@ -36,11 +36,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + builder: (context, child) => StreamChat( + child: child, + client: client, + ), home: Container( - child: StreamChat( - client: client, - child: ChannelListPage(), - ), + child: ChannelListPage(), ), ); } diff --git a/example/lib/main.dart b/example/lib/main.dart index e170c5d6..f2ae3128 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -95,12 +95,13 @@ class MyApp extends StatelessWidget { theme: ThemeData.light(), darkTheme: ThemeData.dark(), themeMode: ThemeMode.system, - home: Container( - child: StreamChat( + builder: (context, widget) { + return StreamChat( + child: widget, client: client, - child: ChannelListPage(), - ), - ), + ); + }, + home: ChannelListPage(), ); } } diff --git a/example/lib/multiple_conversation.dart b/example/lib/multiple_conversation.dart index 8bffa449..0a6b9131 100644 --- a/example/lib/multiple_conversation.dart +++ b/example/lib/multiple_conversation.dart @@ -40,11 +40,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + builder: (context, child) => StreamChat( + client: client, + child: child, + ), home: Container( - child: StreamChat( - client: client, - child: ChannelListPage(), - ), + child: ChannelListPage(), ), ); } diff --git a/example/lib/threads.dart b/example/lib/threads.dart index 90e605f6..660842db 100644 --- a/example/lib/threads.dart +++ b/example/lib/threads.dart @@ -31,11 +31,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + builder: (context, child) => StreamChat( + child: child, + client: client, + ), home: Container( - child: StreamChat( - client: client, - child: ChannelListPage(), - ), + child: ChannelListPage(), ), ); } diff --git a/lib/src/attachment_title.dart b/lib/src/attachment_title.dart index efa34765..ab9cd2da 100644 --- a/lib/src/attachment_title.dart +++ b/lib/src/attachment_title.dart @@ -46,7 +46,7 @@ class AttachmentTitle extends StatelessWidget { .toList() .reversed .join('.'), - style: messageTheme.createdAt, + style: messageTheme.messageText, ), ], ), diff --git a/lib/src/full_screen_video.dart b/lib/src/full_screen_video.dart index 000f13c1..c30bcfd1 100644 --- a/lib/src/full_screen_video.dart +++ b/lib/src/full_screen_video.dart @@ -21,11 +21,13 @@ class _FullScreenVideoState extends State { ChewieController _chewieController; VideoPlayerController _videoPlayerController; bool initialized = false; + GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) { return Scaffold( body: Builder( + key: _scaffoldKey, builder: (context) { if (!initialized) { return Center( @@ -60,7 +62,7 @@ class _FullScreenVideoState extends State { errorListener = () { if (_videoPlayerController.value.hasError) { Navigator.pop(context); - launchURL(context, widget.attachment.titleLink); + launchURL(_scaffoldKey.currentContext, widget.attachment.titleLink); } _videoPlayerController.removeListener(errorListener); }; @@ -70,6 +72,7 @@ class _FullScreenVideoState extends State { @override void dispose() { _videoPlayerController.dispose(); + _chewieController.dispose(); super.dispose(); } } diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index 994d2e6e..11e2ea5b 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -47,26 +47,31 @@ class GiphyAttachment extends StatelessWidget { ); })); }, - child: CachedNetworkImage( - height: size?.height, - width: size?.width, - placeholder: (_, __) { - return Container( - width: size?.width, - height: size?.height, - child: Center( - child: CircularProgressIndicator(), - ), - ); - }, - imageUrl: attachment.thumbUrl ?? - attachment.imageUrl ?? - attachment.assetUrl, - errorWidget: (context, url, error) => AttachmentError( - attachment: attachment, - size: size, + child: Hero( + tag: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + child: CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + return Container( + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), + ), + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + size: size, + ), + fit: BoxFit.cover, ), - fit: BoxFit.cover, ), ), ], @@ -75,6 +80,7 @@ class GiphyAttachment extends StatelessWidget { Container( alignment: Alignment.bottomCenter, child: Material( + color: messageTheme.messageBackgroundColor, child: AttachmentTitle( messageTheme: messageTheme, attachment: attachment, diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index d39a8948..f7352564 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -75,6 +75,7 @@ class ImageAttachment extends StatelessWidget { ), if (attachment.title != null) Material( + color: messageTheme.messageBackgroundColor, child: AttachmentTitle( messageTheme: messageTheme, attachment: attachment, diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 1c48070a..09c890a7 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -913,8 +913,6 @@ class MessageInputState extends State { void initState() { super.initState(); - StreamChannel.of(context).queryMembersAndWatchers(); - _keyboardListener = KeyboardVisibility.onChange.listen((visible) { if (visible) { if (_commandsOverlay != null) { diff --git a/lib/src/stream_channel.dart b/lib/src/stream_channel.dart index eb7f4aea..b5ddcd33 100644 --- a/lib/src/stream_channel.dart +++ b/lib/src/stream_channel.dart @@ -50,8 +50,14 @@ class StreamChannelState extends State { /// The stream notifying the state of queryMessage call Stream get queryMessage => _queryMessageController.stream; + bool _paginationEnded = false; + /// Calls [channel.query] updating [queryMessage] stream void queryMessages() { + if (_paginationEnded) { + return; + } + _queryMessageController.add(true); String firstId; @@ -67,6 +73,10 @@ class StreamChannelState extends State { ), ) .then((res) { + print('res.messages.length: ${res.messages.length}'); + if (res.messages.isEmpty) { + _paginationEnded = true; + } _queryMessageController.add(false); }).catchError((e, stack) { _queryMessageController.addError(e, stack); @@ -75,6 +85,10 @@ class StreamChannelState extends State { /// Calls [channel.getReplies] updating [queryMessage] stream Future getReplies(String parentId) async { + if (_paginationEnded) { + return; + } + _queryMessageController.add(true); String firstId; @@ -95,6 +109,9 @@ class StreamChannelState extends State { ), ) .then((res) { + if (res.messages.isEmpty) { + _paginationEnded = true; + } _queryMessageController.add(false); }).catchError((e, stack) { _queryMessageController.addError(e, stack); diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index a69b44e0..92394ff0 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -61,7 +61,6 @@ class StreamChat extends StatefulWidget { /// The current state of the StreamChat widget class StreamChatState extends State with WidgetsBindingObserver { Client get client => widget.client; - final GlobalKey _navigatorKey = GlobalKey(); Timer _disconnectTimer; @override @@ -79,26 +78,7 @@ class StreamChatState extends State with WidgetsBindingObserver { accentColor: streamTheme.accentColor, scaffoldBackgroundColor: streamTheme.backgroundColor, ), - child: WillPopScope( - onWillPop: () async { - if (_navigatorKey.currentState.canPop()) { - _navigatorKey.currentState.pop(); - return false; - } else { - return true; - } - }, - child: Navigator( - initialRoute: '/', - key: _navigatorKey, - onGenerateRoute: (settings) { - return MaterialPageRoute( - settings: settings, - builder: (_) => widget.child, - ); - }, - ), - ), + child: widget.child, ); }, ), diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index e81ea220..4f8cb712 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -149,6 +149,7 @@ class _VideoAttachmentState extends State { @override void dispose() { _videoPlayerController.dispose(); + _chewieController.dispose(); super.dispose(); } } From 121f5c3d21045131c270264c83ca61715557bac9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 14 May 2020 11:36:05 +0200 Subject: [PATCH 108/133] fix reaction bubble ui --- example/ios/Runner.xcodeproj/project.pbxproj | 52 ++-- lib/src/message_widget.dart | 274 +++++++++++-------- 2 files changed, 187 insertions(+), 139 deletions(-) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 6083a722..07264601 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -237,13 +237,13 @@ 0BC14C4C242B5A7A0028DE94 = { CreatedOnToolsVersion = 11.4; DevelopmentTeam = EHV7XZLAHA; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = EHV7XZLAHA; LastSwiftMigration = 1100; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; }; }; @@ -412,9 +412,9 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = EHV7XZLAHA; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Notifications/Info.plist; @@ -424,7 +424,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "flutter example notifications"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -442,9 +442,9 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = EHV7XZLAHA; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Notifications/Info.plist; @@ -453,7 +453,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "flutter example notifications"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -469,9 +469,9 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = EHV7XZLAHA; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Notifications/Info.plist; @@ -480,7 +480,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "flutter example notifications"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -546,10 +546,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = EHV7XZLAHA; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -564,7 +564,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "flutter example"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -687,10 +687,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = EHV7XZLAHA; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -705,7 +705,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "flutter example"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -721,10 +721,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = EHV7XZLAHA; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -739,7 +739,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "flutter example"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 02e49e13..3d554e72 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -34,7 +34,7 @@ enum DisplayWidget { /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. -class MessageWidget extends StatelessWidget { +class MessageWidget extends StatefulWidget { /// Function called on mention tap final void Function(User) onMentionTap; @@ -104,15 +104,6 @@ class MessageWidget extends StatelessWidget { final bool showEditMessage; final Map attachmentBuilders; - final Map _reactionToEmoji = { - 'love': '❤️️', - 'haha': '😂', - 'like': '👍', - 'sad': '😕', - 'angry': '😡', - 'wow': '😲', - }; - MessageWidget({ Key key, @required this.message, @@ -186,20 +177,37 @@ class MessageWidget extends StatelessWidget { }..addAll(customAttachmentBuilders ?? {}), super(key: key); + @override + _MessageWidgetState createState() => _MessageWidgetState(); +} + +class _MessageWidgetState extends State { + final Map _reactionToEmoji = { + 'love': '❤️️', + 'haha': '😂', + 'like': '👍', + 'sad': '😕', + 'angry': '😡', + 'wow': '😲', + }; + + final GlobalKey _reactionPickerKey = GlobalKey(); + double _reactionPadding = 0; + @override Widget build(BuildContext context) { - var leftPadding = showUserAvatar != DisplayWidget.gone - ? messageTheme.avatarTheme.constraints.maxWidth + 23.0 + var leftPadding = widget.showUserAvatar != DisplayWidget.gone + ? widget.messageTheme.avatarTheme.constraints.maxWidth + 23.0 : 12.0; - if (showSendingIndicator == DisplayWidget.gone) { + if (widget.showSendingIndicator == DisplayWidget.gone) { leftPadding -= 7; } return Portal( child: Padding( - padding: padding ?? EdgeInsets.all(8), + padding: widget.padding ?? EdgeInsets.all(8), child: Transform( alignment: Alignment.center, - transform: Matrix4.rotationY(reverse ? pi : 0), + transform: Matrix4.rotationY(widget.reverse ? pi : 0), child: Container( alignment: Alignment.centerLeft, child: Container( @@ -220,31 +228,31 @@ class MessageWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ - if (showSendingIndicator == DisplayWidget.show) + if (widget.showSendingIndicator == + DisplayWidget.show) _buildSendingIndicator(), SizedBox( width: 2, ), - if (showSendingIndicator == DisplayWidget.hide) + if (widget.showSendingIndicator == + DisplayWidget.hide) SizedBox( width: 6, ), - if (showUserAvatar == DisplayWidget.show) + if (widget.showUserAvatar == DisplayWidget.show) _buildUserAvatar(), SizedBox( width: 6, ), - if (showUserAvatar == DisplayWidget.hide) + if (widget.showUserAvatar == DisplayWidget.hide) SizedBox( - width: messageTheme - .avatarTheme.constraints.maxWidth + + width: widget.messageTheme.avatarTheme + .constraints.maxWidth + 8, ), Flexible( child: Padding( - padding: (message.reactionCounts?.isNotEmpty == - true && - showReactions) + padding: widget.showReactions ? EdgeInsets.only( top: _getReactionsTopPadding(), ) @@ -253,16 +261,16 @@ class MessageWidget extends StatelessWidget { portalAnchor: Alignment(0, 1), childAnchor: Alignment.topRight, portal: _buildReactionIndicator(context), - child: (message.isDeleted && - message.status != + child: (widget.message.isDeleted && + widget.message.status != MessageSendingStatus .FAILED_DELETE) ? Transform( alignment: Alignment.center, transform: Matrix4.rotationY( - reverse ? pi : 0), + widget.reverse ? pi : 0), child: DeletedMessage( - messageTheme: messageTheme, + messageTheme: widget.messageTheme, ), ) : Column( @@ -270,7 +278,9 @@ class MessageWidget extends StatelessWidget { CrossAxisAlignment.start, children: [ ..._parseAttachments(context), - if (message.text.trim().isNotEmpty) + if (widget.message.text + .trim() + .isNotEmpty) _buildTextBubble(context), ], ), @@ -279,13 +289,15 @@ class MessageWidget extends StatelessWidget { ), ], ), - if (showReplyIndicator && message.replyCount > 0) + if (widget.showReplyIndicator && + widget.message.replyCount > 0) _buildReplyIndicator(leftPadding), ], ), ), - if ((message.createdAt != null && showTimestamp) || - showUsername) + if ((widget.message.createdAt != null && + widget.showTimestamp) || + widget.showUsername) _buildUsernameAndTimestamp(leftPadding), ], ), @@ -297,18 +309,49 @@ class MessageWidget extends StatelessWidget { } double _getReactionsTopPadding() { + return _reactionPadding; return 36.0 * - ((message.reactionCounts.values + ((widget.message.reactionCounts.values .where((element) => element > 0) .length ~/ 5) + 1); } + @override + void didUpdateWidget(MessageWidget oldWidget) { + super.didUpdateWidget(oldWidget); + _updateReactionPadding(); + } + + @override + void initState() { + super.initState(); + _updateReactionPadding(); + } + + void _updateReactionPadding() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (_reactionPickerKey.currentContext != null && + widget.message.reactionCounts.values + .where((element) => element > 0) + .length > + 0) { + setState(() { + _reactionPadding = _reactionPickerKey.currentContext.size.height; + }); + } else { + setState(() { + _reactionPadding = 0; + }); + } + }); + } + Widget _buildReactionsTail(BuildContext context) { return AnimatedSwitcher( duration: Duration(milliseconds: 300), - child: message.reactionCounts?.isNotEmpty == true + child: widget.message.reactionCounts?.isNotEmpty == true ? Transform.translate( offset: Offset(4, 0), child: CustomPaint( @@ -331,19 +374,20 @@ class MessageWidget extends StatelessWidget { ), child: Transform( alignment: Alignment.center, - transform: Matrix4.rotationY(reverse ? pi : 0), + transform: Matrix4.rotationY(widget.reverse ? pi : 0), child: RichText( text: TextSpan( - style: messageTheme.createdAt, + style: widget.messageTheme.createdAt, children: [ - if (showUsername) + if (widget.showUsername) TextSpan( - text: message.user.name, + text: widget.message.user.name, style: TextStyle(fontWeight: FontWeight.bold), ), - if (message.createdAt != null && showTimestamp) + if (widget.message.createdAt != null && widget.showTimestamp) TextSpan( - text: Jiffy(message.createdAt.toLocal()).format(' HH:mm'), + text: Jiffy(widget.message.createdAt.toLocal()) + .format(' HH:mm'), ), ], ), @@ -354,10 +398,11 @@ class MessageWidget extends StatelessWidget { Widget _buildReactionIndicator(BuildContext context) { return AnimatedSwitcher( + key: _reactionPickerKey, duration: Duration(milliseconds: 300), - child: (showReactions && - message.reactionCounts?.isNotEmpty == true && - !message.isDeleted) + child: (widget.showReactions && + widget.message.reactionCounts?.isNotEmpty == true && + !widget.message.isDeleted) ? Container( child: GestureDetector( onTap: () => onLongPress(context), @@ -371,7 +416,7 @@ class MessageWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), + transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, child: Container( padding: const EdgeInsets.all(8), @@ -397,10 +442,10 @@ class MessageWidget extends StatelessWidget { Text _buildReactionsText(BuildContext context) { return Text( - message.reactionCounts.keys.map((reactionType) { + widget.message.reactionCounts.keys.map((reactionType) { return _reactionToEmoji[reactionType] ?? '?'; }).join(' ') + - ' ${message.reactionCounts.values.fold(0, (t, v) => v + t).toString()}', + ' ${widget.message.reactionCounts.values.fold(0, (t, v) => v + t).toString()}', style: TextStyle( color: Theme.of(context).brightness == Brightness.dark ? Colors.black @@ -425,21 +470,22 @@ class MessageWidget extends StatelessWidget { return StreamChannel( channel: channel, child: MessageActionsBottomSheet( - showDeleteMessage: showDeleteMessage, - message: message, - editMessageInputBuilder: editMessageInputBuilder, - onThreadTap: onThreadTap, - showEditMessage: showEditMessage, - showReactions: showReactions, - showReply: showReplyIndicator && onThreadTap != null, + showDeleteMessage: widget.showDeleteMessage, + message: widget.message, + editMessageInputBuilder: widget.editMessageInputBuilder, + onThreadTap: widget.onThreadTap, + showEditMessage: widget.showEditMessage, + showReactions: widget.showReactions, + showReply: + widget.showReplyIndicator && widget.onThreadTap != null, ), ); }); } List _parseAttachments(BuildContext context) { - return message.attachments?.map((attachment) { - final attachmentBuilder = attachmentBuilders[attachment.type]; + return widget.message.attachments?.map((attachment) { + final attachmentBuilder = widget.attachmentBuilders[attachment.type]; if (attachmentBuilder == null) { return SizedBox(); @@ -455,27 +501,28 @@ class MessageWidget extends StatelessWidget { child: Material( color: _getBackgroundColor(), clipBehavior: Clip.hardEdge, - shape: attachmentShape ?? - shape ?? + shape: widget.attachmentShape ?? + widget.shape ?? ContinuousRectangleBorder( - side: attachmentBorderSide ?? - borderSide ?? + side: widget.attachmentBorderSide ?? + widget.borderSide ?? BorderSide( color: Theme.of(context).brightness == Brightness.dark ? Colors.white.withAlpha(24) : Colors.black.withAlpha(24), ), - borderRadius: attachmentBorderRadiusGeometry ?? - borderRadiusGeometry ?? + borderRadius: widget.attachmentBorderRadiusGeometry ?? + widget.borderRadiusGeometry ?? BorderRadius.zero, ), child: Padding( - padding: attachmentPadding, + padding: widget.attachmentPadding, child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), + transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, child: Column( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ getFailedMessageWidget( @@ -484,7 +531,7 @@ class MessageWidget extends StatelessWidget { ), attachmentBuilder( context, - message, + widget.message, attachment, ), ], @@ -499,12 +546,13 @@ class MessageWidget extends StatelessWidget { } void onLongPress(BuildContext context) { - if (message.isEphemeral || message.status == MessageSendingStatus.SENDING) { + if (widget.message.isEphemeral || + widget.message.status == MessageSendingStatus.SENDING) { return; } - if (onMessageActions != null) { - onMessageActions(context, message); + if (widget.onMessageActions != null) { + widget.onMessageActions(context, widget.message); } else { _showMessageBottomSheet(context); } @@ -517,15 +565,15 @@ class MessageWidget extends StatelessWidget { left: leftPadding, ), child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), + transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, child: ReplyIndicator( - message: message, - reversed: reverse, - messageTheme: messageTheme, - onTap: onThreadTap != null + message: widget.message, + reversed: widget.reverse, + messageTheme: widget.messageTheme, + onTap: widget.onThreadTap != null ? () { - onThreadTap(message); + widget.onThreadTap(widget.message); } : null, ), @@ -540,27 +588,27 @@ class MessageWidget extends StatelessWidget { 4, ), child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), + transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, child: SendingIndicator( - message: message, + message: widget.message, ), ), ); } Widget _buildUserAvatar() => Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), + transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: Transform.translate( - offset: - Offset(0, messageTheme.avatarTheme.constraints.maxHeight / 2), + offset: Offset( + 0, widget.messageTheme.avatarTheme.constraints.maxHeight / 2), child: UserAvatar( - user: message.user, - onTap: onUserAvatarTap, - constraints: messageTheme.avatarTheme.constraints, + user: widget.message.user, + onTap: widget.onUserAvatarTap, + constraints: widget.messageTheme.avatarTheme.constraints, ), ), ), @@ -570,31 +618,31 @@ class MessageWidget extends StatelessWidget { BuildContext context, { EdgeInsetsGeometry padding, }) { - Widget widget; - if (message.status == MessageSendingStatus.FAILED) - widget = Text( + Widget failedWidget; + if (widget.message.status == MessageSendingStatus.FAILED) + failedWidget = Text( 'MESSAGE FAILED · CLICK TO TRY AGAIN', - style: messageTheme.messageText.copyWith( + style: widget.messageTheme.messageText.copyWith( color: Theme.of(context).brightness == Brightness.dark ? Colors.white.withOpacity(.5) : Colors.black.withOpacity(.5), fontSize: 11, ), ); - if (message.status == MessageSendingStatus.FAILED_UPDATE) - widget = Text( + if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) + failedWidget = Text( 'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN', - style: messageTheme.messageText.copyWith( + style: widget.messageTheme.messageText.copyWith( color: Theme.of(context).brightness == Brightness.dark ? Colors.white.withOpacity(.5) : Colors.black.withOpacity(.5), fontSize: 11, ), ); - if (message.status == MessageSendingStatus.FAILED_DELETE) - widget = Text( + if (widget.message.status == MessageSendingStatus.FAILED_DELETE) + failedWidget = Text( 'MESSAGE DELETE FAILED · CLICK TO TRY AGAIN', - style: messageTheme.messageText.copyWith( + style: widget.messageTheme.messageText.copyWith( color: Theme.of(context).brightness == Brightness.dark ? Colors.white.withOpacity(.5) : Colors.black.withOpacity(.5), @@ -602,10 +650,10 @@ class MessageWidget extends StatelessWidget { ), ); - if (widget != null) { + if (failedWidget != null) { return Padding( padding: padding ?? EdgeInsets.zero, - child: widget, + child: failedWidget, ); } @@ -617,22 +665,22 @@ class MessageWidget extends StatelessWidget { onTap: () => retryMessage(context), onLongPress: () => onLongPress(context), child: Material( - shape: shape ?? + shape: widget.shape ?? ContinuousRectangleBorder( - side: borderSide ?? + side: widget.borderSide ?? BorderSide( color: Theme.of(context).brightness == Brightness.dark ? Colors.white.withAlpha(24) : Colors.black.withAlpha(24), ), - borderRadius: borderRadiusGeometry ?? BorderRadius.zero, + borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero, ), color: _getBackgroundColor(), child: Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), + transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, child: Padding( - padding: textPadding, + padding: widget.textPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -647,30 +695,30 @@ class MessageWidget extends StatelessWidget { } Color _getBackgroundColor() { - return (message.status == MessageSendingStatus.FAILED || - message.status == MessageSendingStatus.FAILED_UPDATE || - message.status == MessageSendingStatus.FAILED_DELETE) + return (widget.message.status == MessageSendingStatus.FAILED || + widget.message.status == MessageSendingStatus.FAILED_UPDATE || + widget.message.status == MessageSendingStatus.FAILED_DELETE) ? Color(0xffd0021B).withOpacity(.1) - : messageTheme.messageBackgroundColor; + : widget.messageTheme.messageBackgroundColor; } void retryMessage(BuildContext context) { final channel = StreamChannel.of(context).channel; - if (message.status == MessageSendingStatus.FAILED) { - channel.sendMessage(message); + if (widget.message.status == MessageSendingStatus.FAILED) { + channel.sendMessage(widget.message); return; } - if (message.status == MessageSendingStatus.FAILED_UPDATE) { + if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) { StreamChat.of(context).client.updateMessage( - message, + widget.message, channel.cid, ); return; } - if (message.status == MessageSendingStatus.FAILED_DELETE) { + if (widget.message.status == MessageSendingStatus.FAILED_DELETE) { StreamChat.of(context).client.deleteMessage( - message, + widget.message, channel.cid, ); return; @@ -678,12 +726,12 @@ class MessageWidget extends StatelessWidget { } Widget _buildText(BuildContext context) { - return textBuilder != null - ? textBuilder(context, message) + return widget.textBuilder != null + ? widget.textBuilder(context, widget.message) : MessageText( - message: message, - onMentionTap: onMentionTap, - messageTheme: messageTheme, + message: widget.message, + onMentionTap: widget.onMentionTap, + messageTheme: widget.messageTheme, ); } } From 91c1b261c0813e85b95297c401aaabbb5b5cb34c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 14 May 2020 11:38:56 +0200 Subject: [PATCH 109/133] version bump --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ee52eee..614151d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +## 0.2.1-alpha+1 + +- Removed the additional `Navigator` in `StreamChat` widget. + It was added to make the app have the `StreamChat` widget as ancestor in every route. + Now the recommended way to add `StreamChat` to your app is using the `builder` property of your `MaterialApp` widget. + Otherwise you can use it in the usual way, but you need to add a `StreamChat` widget to every route of your app. + Read [this issue](https://github.com/GetStream/stream-chat-flutter/issues/47) for more information. + +```dart + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + themeMode: ThemeMode.system, + builder: (context, widget) { + return StreamChat( + child: widget, + client: client, + ); + }, + home: ChannelListPage(), + ); +``` + +- Fix reaction bubble going below previous message on iOS + +- Fix message list view reloading messages even if the pagination is ended + ## 0.2.1-alpha - New message widget diff --git a/pubspec.yaml b/pubspec.yaml index 27ee2d0f..ebb42bde 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha +version: 0.2.1-alpha+1 environment: sdk: ">=2.3.0 <3.0.0" From e8be4a2575db5192107f5c1265c2266ea862b2f8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 14 May 2020 15:52:40 +0200 Subject: [PATCH 110/133] version bump --- CHANGELOG.md | 5 +++++ example/lib/main.dart | 1 - lib/src/message_widget.dart | 6 +++++- lib/src/stream_channel.dart | 1 - pubspec.yaml | 6 +++--- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 614151d8..34d41786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.1-alpha+2 + +- Fixed reactions bubble going below other messages +- Updated llc dependency + ## 0.2.1-alpha+1 - Removed the additional `Navigator` in `StreamChat` widget. diff --git a/example/lib/main.dart b/example/lib/main.dart index f2ae3128..d41c3f4a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -69,7 +69,6 @@ void main() async { 's2dxdhpxd94g', logLevel: Level.INFO, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, - backgroundKeepAlive: Duration.zero, persistenceEnabled: true, // baseURL: 'chat-us-east-staging.stream-io-api.com', ); diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 3d554e72..16ed2be3 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -237,7 +237,7 @@ class _MessageWidgetState extends State { if (widget.showSendingIndicator == DisplayWidget.hide) SizedBox( - width: 6, + width: 8, ), if (widget.showUserAvatar == DisplayWidget.show) _buildUserAvatar(), @@ -332,7 +332,11 @@ class _MessageWidgetState extends State { void _updateReactionPadding() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (!mounted) { + return; + } if (_reactionPickerKey.currentContext != null && + widget.message.reactionCounts != null && widget.message.reactionCounts.values .where((element) => element > 0) .length > diff --git a/lib/src/stream_channel.dart b/lib/src/stream_channel.dart index b5ddcd33..1f4edf30 100644 --- a/lib/src/stream_channel.dart +++ b/lib/src/stream_channel.dart @@ -73,7 +73,6 @@ class StreamChannelState extends State { ), ) .then((res) { - print('res.messages.length: ${res.messages.length}'); if (res.messages.isEmpty) { _paginationEnded = true; } diff --git a/pubspec.yaml b/pubspec.yaml index ebb42bde..0df9729c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha+1 +version: 0.2.1-alpha+2 environment: sdk: ">=2.3.0 <3.0.0" @@ -16,12 +16,12 @@ dependencies: cached_network_image: ^2.2.0+1 flutter_markdown: ^0.3.5 url_launcher: ^5.4.7 - video_player: ^0.10.10 + video_player: ^0.10.11 chewie: ^0.9.10 file_picker: ^1.9.0+1 image_picker: ^0.6.6+1 flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+9 + stream_chat: ^0.2.0-alpha+11 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From 890f7296683ed82d65d5f4b226701282437f6ea1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Sat, 16 May 2020 12:29:00 +0200 Subject: [PATCH 111/133] fix hero tag duplicated --- lib/src/giphy_attachment.dart | 5 ++--- lib/src/image_attachment.dart | 9 +++++---- lib/src/message_widget.dart | 1 + pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index 11e2ea5b..e097efab 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -48,9 +48,8 @@ class GiphyAttachment extends StatelessWidget { })); }, child: Hero( - tag: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, + tag: + '${message.id} - ${attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl}', child: CachedNetworkImage( height: size?.height, width: size?.width, diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index f7352564..6a983ee9 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -9,12 +9,14 @@ import 'utils.dart'; class ImageAttachment extends StatelessWidget { final Attachment attachment; + final Message message; final MessageTheme messageTheme; final Size size; const ImageAttachment({ Key key, - this.attachment, + @required this.attachment, + @required this.message, this.messageTheme, this.size, }) : super(key: key); @@ -36,9 +38,8 @@ class ImageAttachment extends StatelessWidget { children: [ Expanded( child: Hero( - tag: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, + tag: + '${message.id} - ${attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl}', child: GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 16ed2be3..29812f97 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -137,6 +137,7 @@ class MessageWidget extends StatefulWidget { 'image': (context, message, attachment) { return ImageAttachment( attachment: attachment, + message: message, messageTheme: messageTheme, size: Size( MediaQuery.of(context).size.width * 0.8, diff --git a/pubspec.yaml b/pubspec.yaml index 0df9729c..570a7159 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter photo_view: ^0.9.2 - rxdart: ^0.24.0 + rxdart: ^0.24.1 jiffy: ^3.0.1 flutter_portal: ^0.1.0 cached_network_image: ^2.2.0+1 From 18aeb0eba3414ca3e100612bf9462a454683c79b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 18 May 2020 10:55:27 +0200 Subject: [PATCH 112/133] version bump --- CHANGELOG.md | 6 ++ analysis_options.yaml | 122 ++++++++++++++++----------------- lib/src/channel_list_view.dart | 2 +- lib/src/date_divider.dart | 3 +- lib/src/full_screen_video.dart | 6 +- lib/src/giphy_attachment.dart | 4 +- lib/src/image_attachment.dart | 3 +- lib/src/message_input.dart | 2 +- lib/src/message_widget.dart | 14 ++-- lib/src/utils.dart | 5 ++ lib/src/video_attachment.dart | 4 +- pubspec.yaml | 4 +- 12 files changed, 94 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d41786..40bbaa39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.1-alpha+3 + +- Update llc dependency + +- Fix hero tag generation for attachment + ## 0.2.1-alpha+2 - Fixed reactions bubble going below other messages diff --git a/analysis_options.yaml b/analysis_options.yaml index fa8ecab2..7feb4342 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,61 +1,61 @@ -#include: package:pedantic/analysis_options.yaml -# -#analyzer: -# exclude: -# - lib/**/*.g.dart -# - example/* -# -#linter: -# rules: -# # these rules are documented on and in the same order as -# # the Dart Lint rules page to make maintenance easier -# # https://github.com/dart-lang/linter/blob/master/example/all.yaml -# # - always_declare_return_types -# # - always_specify_types -# # - annotate_overrides -# # - avoid_as -# - avoid_empty_else -# - avoid_init_to_null -# - avoid_return_types_on_setters -# - avoid_web_libraries_in_flutter -# - await_only_futures -# - camel_case_types -# - cancel_subscriptions -# - close_sinks -# # - comment_references # we do not presume as to what people want to reference in their dartdocs -# # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 -# - control_flow_in_finally -# - empty_constructor_bodies -# - empty_statements -# - hash_and_equals -# - implementation_imports -# # - invariant_booleans -# # - iterable_contains_unrelated_type -# - library_names -# # - library_prefixes -# # - list_remove_unrelated_type -# # - literal_only_boolean_expressions -# - non_constant_identifier_names -# # - one_member_abstracts -# # - only_throw_errors -# # - overridden_fields -## - package_api_docs -# - package_names -# - package_prefixed_library_names -# - prefer_is_not_empty -# # - prefer_mixin # https://github.com/dart-lang/language/issues/32 -# - public_member_api_docs -# - slash_for_doc_comments -# # - sort_constructors_first -# # - sort_unnamed_constructors_first -# # - super_goes_last # no longer needed w/ Dart 2 -# - test_types_in_equals -# - throw_in_finally -# # - type_annotate_public_apis # subset of always_specify_types -# - type_init_formals -# # - unawaited_futures -# - unnecessary_brace_in_string_interps -# - unnecessary_getters_setters -# - unnecessary_statements -# - unrelated_type_equality_checks -# - valid_regexps +include: package:pedantic/analysis_options.yaml + +analyzer: + exclude: + - lib/**/*.g.dart + - example/* + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + # - always_declare_return_types + # - always_specify_types + # - annotate_overrides + # - avoid_as + - avoid_empty_else + - avoid_init_to_null + - avoid_return_types_on_setters + - avoid_web_libraries_in_flutter + - await_only_futures + - camel_case_types + - cancel_subscriptions + - close_sinks + # - comment_references # we do not presume as to what people want to reference in their dartdocs + # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + - empty_constructor_bodies + - empty_statements + - hash_and_equals + - implementation_imports + # - invariant_booleans + # - iterable_contains_unrelated_type + - library_names + # - library_prefixes + # - list_remove_unrelated_type + # - literal_only_boolean_expressions + - non_constant_identifier_names + # - one_member_abstracts + # - only_throw_errors + # - overridden_fields +# - package_api_docs + - package_names + - package_prefixed_library_names + - prefer_is_not_empty + # - prefer_mixin # https://github.com/dart-lang/language/issues/32 + - public_member_api_docs + - slash_for_doc_comments + # - sort_constructors_first + # - sort_unnamed_constructors_first + # - super_goes_last # no longer needed w/ Dart 2 + - test_types_in_equals + - throw_in_finally + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures + - unnecessary_brace_in_string_interps + - unnecessary_getters_setters + - unnecessary_statements + - unrelated_type_equality_checks + - valid_regexps diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 7b6a78a0..d94bf3c8 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -165,7 +165,7 @@ class _ChannelListViewState extends State TextSpan(text: 'Error loading channels'), ], ), - style: Theme.of(context).textTheme.title, + style: Theme.of(context).textTheme.headline6, ), Padding( padding: const EdgeInsets.only( diff --git a/lib/src/date_divider.dart b/lib/src/date_divider.dart index fdf73049..102c6e14 100644 --- a/lib/src/date_divider.dart +++ b/lib/src/date_divider.dart @@ -67,7 +67,8 @@ class DateDivider extends StatelessWidget { ), style: TextStyle( fontSize: 10, - color: Theme.of(context).textTheme.title.color.withOpacity(.5), + color: + Theme.of(context).textTheme.headline6.color.withOpacity(.5), ), ), ), diff --git a/lib/src/full_screen_video.dart b/lib/src/full_screen_video.dart index c30bcfd1..46325b07 100644 --- a/lib/src/full_screen_video.dart +++ b/lib/src/full_screen_video.dart @@ -21,7 +21,7 @@ class _FullScreenVideoState extends State { ChewieController _chewieController; VideoPlayerController _videoPlayerController; bool initialized = false; - GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) { @@ -71,8 +71,8 @@ class _FullScreenVideoState extends State { @override void dispose() { - _videoPlayerController.dispose(); - _chewieController.dispose(); + _videoPlayerController?.dispose(); + _chewieController?.dispose(); super.dispose(); } } diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index e097efab..d5e6bb78 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -6,6 +6,7 @@ import '../stream_chat_flutter.dart'; import 'attachment_error.dart'; import 'attachment_title.dart'; import 'full_screen_image.dart'; +import 'utils.dart'; class GiphyAttachment extends StatelessWidget { final Attachment attachment; @@ -48,8 +49,7 @@ class GiphyAttachment extends StatelessWidget { })); }, child: Hero( - tag: - '${message.id} - ${attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl}', + tag: getAttachmentHeroTag(message, attachment), child: CachedNetworkImage( height: size?.height, width: size?.width, diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index 6a983ee9..f8ac69af 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -38,8 +38,7 @@ class ImageAttachment extends StatelessWidget { children: [ Expanded( child: Hero( - tag: - '${message.id} - ${attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl}', + tag: getAttachmentHeroTag(message, attachment), child: GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 09c890a7..e00cad54 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -275,7 +275,7 @@ class MessageInputState extends State { _typingStarted = true; }); }, - style: Theme.of(context).textTheme.body1, + style: Theme.of(context).textTheme.bodyText2, autofocus: false, decoration: InputDecoration( hintText: 'Write a message', diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 29812f97..942bec1c 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -339,9 +339,8 @@ class _MessageWidgetState extends State { if (_reactionPickerKey.currentContext != null && widget.message.reactionCounts != null && widget.message.reactionCounts.values - .where((element) => element > 0) - .length > - 0) { + .where((element) => element > 0) + .isNotEmpty) { setState(() { _reactionPadding = _reactionPickerKey.currentContext.size.height; }); @@ -624,7 +623,7 @@ class _MessageWidgetState extends State { EdgeInsetsGeometry padding, }) { Widget failedWidget; - if (widget.message.status == MessageSendingStatus.FAILED) + if (widget.message.status == MessageSendingStatus.FAILED) { failedWidget = Text( 'MESSAGE FAILED · CLICK TO TRY AGAIN', style: widget.messageTheme.messageText.copyWith( @@ -634,7 +633,8 @@ class _MessageWidgetState extends State { fontSize: 11, ), ); - if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) + } + if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) { failedWidget = Text( 'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN', style: widget.messageTheme.messageText.copyWith( @@ -644,7 +644,8 @@ class _MessageWidgetState extends State { fontSize: 11, ), ); - if (widget.message.status == MessageSendingStatus.FAILED_DELETE) + } + if (widget.message.status == MessageSendingStatus.FAILED_DELETE) { failedWidget = Text( 'MESSAGE DELETE FAILED · CLICK TO TRY AGAIN', style: widget.messageTheme.messageText.copyWith( @@ -654,6 +655,7 @@ class _MessageWidgetState extends State { fontSize: 11, ), ); + } if (failedWidget != null) { return Padding( diff --git a/lib/src/utils.dart b/lib/src/utils.dart index c427099b..0b9af1e3 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; Future launchURL(BuildContext context, String url) async { @@ -12,3 +13,7 @@ Future launchURL(BuildContext context, String url) async { ); } } + +String getAttachmentHeroTag(Message message, Attachment attachment) { + return '${message.id}-${attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl ?? attachment.ogScrapeUrl}'; +} diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index 4f8cb712..a903147b 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -148,8 +148,8 @@ class _VideoAttachmentState extends State { @override void dispose() { - _videoPlayerController.dispose(); - _chewieController.dispose(); + _videoPlayerController?.dispose(); + _chewieController?.dispose(); super.dispose(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 570a7159..45b87120 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha+2 +version: 0.2.1-alpha+3 environment: sdk: ">=2.3.0 <3.0.0" @@ -21,7 +21,7 @@ dependencies: file_picker: ^1.9.0+1 image_picker: ^0.6.6+1 flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+11 + stream_chat: ^0.2.0-alpha+15 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From 5a0e237a1f826e65803a53c3cc5b4caefbf6f064 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 18 May 2020 10:57:06 +0200 Subject: [PATCH 113/133] remove dead code --- lib/src/message_widget.dart | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 942bec1c..83705894 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -255,7 +255,7 @@ class _MessageWidgetState extends State { child: Padding( padding: widget.showReactions ? EdgeInsets.only( - top: _getReactionsTopPadding(), + top: _reactionPadding, ) : EdgeInsets.zero, child: PortalEntry( @@ -309,16 +309,6 @@ class _MessageWidgetState extends State { ); } - double _getReactionsTopPadding() { - return _reactionPadding; - return 36.0 * - ((widget.message.reactionCounts.values - .where((element) => element > 0) - .length ~/ - 5) + - 1); - } - @override void didUpdateWidget(MessageWidget oldWidget) { super.didUpdateWidget(oldWidget); From d646dcb8c28fc5dfd5b1b4843808886334fb7089 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 18 May 2020 18:02:10 +0200 Subject: [PATCH 114/133] add channel added delete events --- example/lib/main.dart | 13 +++++++++++++ lib/src/channels_bloc.dart | 15 ++++++++++++++- lib/src/stream_channel.dart | 12 ++++++++---- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d41c3f4a..51784e02 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -78,6 +78,19 @@ void main() async { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); +// final client = Client( +// 'qk4nn7rpcn75', +// logLevel: Level.INFO, +// showLocalNotification: Platform.isAndroid ? showLocalNotification : null, +// persistenceEnabled: true, +// baseURL: '127.0.0.1:3030', +// ); +// +// await client.setUser( +// User(id: 'super-band-9'), +// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic3VwZXItdHJhbXAtOSJ9.BbcxsR3FsK9c1q1ijhbBWvlXRwRpZ1RoHMKscKWZklk', +// ); + _initNotifications(client); runApp(MyApp(client)); diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart index 6acb04cc..a58759ab 100644 --- a/lib/src/channels_bloc.dart +++ b/lib/src/channels_bloc.dart @@ -125,7 +125,20 @@ class ChannelsBlocState extends State _subscriptions.add(client.on(EventType.channelDeleted).listen((e) { final channel = e.channel; _channelsController - .add(channels..removeWhere((c) => c.cid == channel.cid)); + .add(List.from(channels..removeWhere((c) => c.cid == channel.cid))); + })); + + _subscriptions + .add(client.on(EventType.notificationAddedToChannel).listen((e) async { + final channelModel = e.channel; + final channel = Channel( + client, + channelModel.type, + channelModel.id, + channelModel.extraData, + ); + await channel.watch(); + _channelsController.add(List.from(channels..insert(0, channel))); })); } diff --git a/lib/src/stream_channel.dart b/lib/src/stream_channel.dart index 1f4edf30..62775281 100644 --- a/lib/src/stream_channel.dart +++ b/lib/src/stream_channel.dart @@ -149,14 +149,18 @@ class StreamChannelState extends State { initialData: widget.channel.state != null, builder: (context, snapshot) { if (!snapshot.hasData || !snapshot.data) { - return Scaffold( - body: Center( + return Container( + height: 30, + child: Center( child: CircularProgressIndicator(), ), ); } else if (snapshot.hasError) { - return Center( - child: Text(snapshot.error), + return Container( + height: 30, + child: Center( + child: Text(snapshot.error), + ), ); } else { return widget.child; From 4312e3133b2ba9ce832fe5e43fd5c0f14c88c858 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 19 May 2020 12:57:07 +0200 Subject: [PATCH 115/133] add system messages --- example/lib/main.dart | 6 +- lib/src/giphy_attachment.dart | 42 +++++++------- lib/src/image_attachment.dart | 59 +++++++++---------- lib/src/message_list_view.dart | 7 +++ lib/src/system_message.dart | 100 +++++++++++++++++++++++++++++++++ lib/src/utils.dart | 5 -- lib/stream_chat_flutter.dart | 1 + pubspec.yaml | 3 +- 8 files changed, 160 insertions(+), 63 deletions(-) create mode 100644 lib/src/system_message.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 51784e02..701b1759 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -79,16 +79,16 @@ void main() async { ); // final client = Client( -// 'qk4nn7rpcn75', +// '892s22ypvt6m', // logLevel: Level.INFO, // showLocalNotification: Platform.isAndroid ? showLocalNotification : null, // persistenceEnabled: true, -// baseURL: '127.0.0.1:3030', +// baseURL: '10.0.2.2:3030', // ); // // await client.setUser( // User(id: 'super-band-9'), -// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic3VwZXItdHJhbXAtOSJ9.BbcxsR3FsK9c1q1ijhbBWvlXRwRpZ1RoHMKscKWZklk', +// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.MfmkavyPRSztKxcxpOk8Wud3WrzQ4YdNfXqoVjtIoRM', // ); _initNotifications(client); diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index d5e6bb78..62de980b 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -6,7 +6,6 @@ import '../stream_chat_flutter.dart'; import 'attachment_error.dart'; import 'attachment_title.dart'; import 'full_screen_image.dart'; -import 'utils.dart'; class GiphyAttachment extends StatelessWidget { final Attachment attachment; @@ -48,29 +47,26 @@ class GiphyAttachment extends StatelessWidget { ); })); }, - child: Hero( - tag: getAttachmentHeroTag(message, attachment), - child: CachedNetworkImage( - height: size?.height, - width: size?.width, - placeholder: (_, __) { - return Container( - width: size?.width, - height: size?.height, - child: Center( - child: CircularProgressIndicator(), - ), - ); - }, - imageUrl: attachment.thumbUrl ?? - attachment.imageUrl ?? - attachment.assetUrl, - errorWidget: (context, url, error) => AttachmentError( - attachment: attachment, - size: size, - ), - fit: BoxFit.cover, + child: CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + return Container( + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), + ), + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + size: size, ), + fit: BoxFit.cover, ), ), ], diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index f8ac69af..a4bd1a0f 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -37,39 +37,36 @@ class ImageAttachment extends StatelessWidget { Column( children: [ Expanded( - child: Hero( - tag: getAttachmentHeroTag(message, attachment), - child: GestureDetector( - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - url: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - ); - })); + child: GestureDetector( + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + ); + })); + }, + child: CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + return Container( + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), + ), + ); }, - child: CachedNetworkImage( - height: size?.height, - width: size?.width, - placeholder: (_, __) { - return Container( - width: size?.width, - height: size?.height, - child: Center( - child: CircularProgressIndicator(), - ), - ); - }, - imageUrl: attachment.thumbUrl ?? - attachment.imageUrl ?? - attachment.assetUrl, - errorWidget: (context, url, error) => AttachmentError( - attachment: attachment, - size: size, - ), - fit: BoxFit.cover, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + size: size, ), + fit: BoxFit.cover, ), ), ), diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 5750a8a2..85a1b0b0 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/message_widget.dart'; +import 'package:stream_chat_flutter/src/system_message.dart'; import 'package:visibility_detector/visibility_detector.dart'; import '../stream_chat_flutter.dart'; @@ -412,6 +413,12 @@ class _MessageListViewState extends State { List messages, int index, ) { + if (message.type == 'system' && message.text?.isNotEmpty == true) { + return SystemMessage( + message: message, + ); + } + final isMyMessage = message.user.id == StreamChat.of(context).user.id; final isLastUser = index + 1 < messages.length && message.user.id == messages[index + 1]?.user?.id; diff --git a/lib/src/system_message.dart b/lib/src/system_message.dart new file mode 100644 index 00000000..7454b0ee --- /dev/null +++ b/lib/src/system_message.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// It shows a date divider depending on the date difference +class SystemMessage extends StatelessWidget { + final Message message; + + const SystemMessage({ + Key key, + @required this.message, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final divider = Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Divider(), + ), + ); + + final createdAt = Jiffy(message.createdAt.toLocal()); + final now = DateTime.now(); + final hourInfo = createdAt.format('h:mm a'); + + String dayInfo; + if (Jiffy(createdAt).isSame(now, Units.DAY)) { + dayInfo = 'TODAY'; + } else if (Jiffy(createdAt) + .isSame(now.subtract(Duration(days: 1)), Units.DAY)) { + dayInfo = 'YESTERDAY'; + } else if (Jiffy(createdAt).isAfter( + now.subtract(Duration(days: 7)), + Units.DAY, + )) { + dayInfo = createdAt.format('EEEE').toUpperCase(); + } else if (Jiffy(createdAt).isAfter( + Jiffy(now).subtract(years: 1), + Units.DAY, + )) { + dayInfo = createdAt.format('dd/MM').toUpperCase(); + } else { + dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase(); + } + + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + divider, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + message.text, + style: TextStyle( + fontSize: 10, + color: Theme.of(context) + .textTheme + .headline6 + .color + .withOpacity(.5), + fontWeight: FontWeight.bold, + ), + ), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: dayInfo, + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + TextSpan(text: ' AT'), + TextSpan(text: ' $hourInfo'), + ], + style: TextStyle( + fontWeight: FontWeight.normal, + ), + ), + style: TextStyle( + fontSize: 10, + color: Theme.of(context) + .textTheme + .headline6 + .color + .withOpacity(.5), + ), + ), + ], + ), + ), + divider, + ], + ); + } +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 0b9af1e3..c427099b 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; Future launchURL(BuildContext context, String url) async { @@ -13,7 +12,3 @@ Future launchURL(BuildContext context, String url) async { ); } } - -String getAttachmentHeroTag(Message message, Attachment attachment) { - return '${message.id}-${attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl ?? attachment.ogScrapeUrl}'; -} diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 9411aea1..3000a503 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -22,6 +22,7 @@ export 'src/sending_indicator.dart'; export 'src/stream_channel.dart'; export 'src/stream_chat.dart'; export 'src/stream_chat_theme.dart'; +export 'src/system_message.dart'; export 'src/thread_header.dart'; export 'src/typing_indicator.dart'; export 'src/user_avatar.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 45b87120..3640ef69 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,8 @@ dependencies: file_picker: ^1.9.0+1 image_picker: ^0.6.6+1 flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+15 + stream_chat: + path: ../stream_chat_dart mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From 69da93487733256ed0d0a84a8887ef61ca43d350 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 20 May 2020 09:50:08 +0200 Subject: [PATCH 116/133] liste for notificationRemovedFromChannel --- lib/src/channels_bloc.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart index a58759ab..d21d7cea 100644 --- a/lib/src/channels_bloc.dart +++ b/lib/src/channels_bloc.dart @@ -140,6 +140,13 @@ class ChannelsBlocState extends State await channel.watch(); _channelsController.add(List.from(channels..insert(0, channel))); })); + + _subscriptions.add( + client.on(EventType.notificationRemovedFromChannel).listen((e) async { + final channelModel = e.channel; + _channelsController.add( + List.from(channels..removeWhere((c) => c.cid == channelModel.cid))); + })); } @override From c080c3f7123de8ba2981e656374ed5ce25d2ef06 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 20 May 2020 09:55:25 +0200 Subject: [PATCH 117/133] version bump --- CHANGELOG.md | 6 ++++++ pubspec.yaml | 9 ++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40bbaa39..d4bd32e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.1-alpha+4 + +- Update llc dependency + +- Add system messages + ## 0.2.1-alpha+3 - Update llc dependency diff --git a/pubspec.yaml b/pubspec.yaml index 3640ef69..3abcdbee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha+3 +version: 0.2.1-alpha+4 environment: sdk: ">=2.3.0 <3.0.0" @@ -14,15 +14,14 @@ dependencies: jiffy: ^3.0.1 flutter_portal: ^0.1.0 cached_network_image: ^2.2.0+1 - flutter_markdown: ^0.3.5 + flutter_markdown: ^0.4.0 url_launcher: ^5.4.7 video_player: ^0.10.11 chewie: ^0.9.10 file_picker: ^1.9.0+1 - image_picker: ^0.6.6+1 + image_picker: ^0.6.6+4 flutter_keyboard_visibility: ^2.0.0 - stream_chat: - path: ../stream_chat_dart + stream_chat: ^0.2.0-alpha+16 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From 24eca0a9c398b26c2f810e5efae5e09517176458 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 22 May 2020 16:33:47 +0200 Subject: [PATCH 118/133] update event listeners --- lib/src/channel_list_view.dart | 17 +++++++++++++++++ lib/src/channels_bloc.dart | 24 +++--------------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index d94bf3c8..27f1a5b4 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -370,6 +370,23 @@ class _ChannelListViewState extends State } }); }); + + final client = StreamChat.of(context).client; + + client + .on( + EventType.connectionRecovered, + EventType.notificationAddedToChannel, + EventType.channelVisible, + ) + .listen((event) { + channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + ); + }); } @override diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart index d21d7cea..b38c6030 100644 --- a/lib/src/channels_bloc.dart +++ b/lib/src/channels_bloc.dart @@ -122,31 +122,13 @@ class ChannelsBlocState extends State } })); - _subscriptions.add(client.on(EventType.channelDeleted).listen((e) { + _subscriptions.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.notificationAddedToChannel).listen((e) async { - final channelModel = e.channel; - final channel = Channel( - client, - channelModel.type, - channelModel.id, - channelModel.extraData, - ); - await channel.watch(); - _channelsController.add(List.from(channels..insert(0, channel))); - })); - - _subscriptions.add( - client.on(EventType.notificationRemovedFromChannel).listen((e) async { - final channelModel = e.channel; - _channelsController.add( - List.from(channels..removeWhere((c) => c.cid == channelModel.cid))); - })); } @override From ba33602a2499ecc7be44c5927833095b234a5418 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 27 May 2020 16:51:32 +0200 Subject: [PATCH 119/133] version bump --- CHANGELOG.md | 5 +++++ example/lib/main.dart | 14 -------------- lib/src/channel_list_view.dart | 13 ------------- pubspec.yaml | 12 ++++++------ 4 files changed, 11 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4bd32e6..823b859c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.1-alpha+6 + +- Update llc dependency +- Minor bugfix + ## 0.2.1-alpha+4 - Update llc dependency diff --git a/example/lib/main.dart b/example/lib/main.dart index 701b1759..f81c61be 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -70,7 +70,6 @@ void main() async { logLevel: Level.INFO, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, persistenceEnabled: true, -// baseURL: 'chat-us-east-staging.stream-io-api.com', ); await client.setUser( @@ -78,19 +77,6 @@ void main() async { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); -// final client = Client( -// '892s22ypvt6m', -// logLevel: Level.INFO, -// showLocalNotification: Platform.isAndroid ? showLocalNotification : null, -// persistenceEnabled: true, -// baseURL: '10.0.2.2:3030', -// ); -// -// await client.setUser( -// User(id: 'super-band-9'), -// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.MfmkavyPRSztKxcxpOk8Wud3WrzQ4YdNfXqoVjtIoRM', -// ); - _initNotifications(client); runApp(MyApp(client)); diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 27f1a5b4..782dd772 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -389,19 +389,6 @@ class _ChannelListViewState extends State }); } - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.resumed) { - ChannelsBloc.of(context).queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination, - options: widget.options, - onlyOffline: true, - ); - } - } - @override void dispose() { WidgetsBinding.instance.removeObserver(this); diff --git a/pubspec.yaml b/pubspec.yaml index 3abcdbee..c928d1e0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha+4 +version: 0.2.1-alpha+6 environment: sdk: ">=2.3.0 <3.0.0" @@ -14,14 +14,14 @@ dependencies: jiffy: ^3.0.1 flutter_portal: ^0.1.0 cached_network_image: ^2.2.0+1 - flutter_markdown: ^0.4.0 - url_launcher: ^5.4.7 - video_player: ^0.10.11 + flutter_markdown: ^0.4.1 + url_launcher: ^5.4.10 + video_player: ^0.10.11+1 chewie: ^0.9.10 file_picker: ^1.9.0+1 - image_picker: ^0.6.6+4 + image_picker: ^0.6.6+5 flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+16 + stream_chat: ^0.2.0-alpha+17 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From acdcb01fe06050e6262fe5da481ccacb163af53d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 28 May 2020 12:11:15 +0200 Subject: [PATCH 120/133] docs and example update --- analysis_options.yaml | 2 +- example/lib/custom_message.dart | 6 +++--- example/lib/custom_theme.dart | 6 +++--- example/lib/customize_channel_preview.dart | 6 +++--- example/lib/customize_message_widget.dart | 6 +++--- example/lib/multiple_conversation.dart | 6 +++--- example/lib/single_conversation.dart | 4 ++-- example/lib/threads.dart | 6 +++--- example/pubspec.yaml | 2 +- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 7feb4342..3723c0af 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,7 +3,7 @@ include: package:pedantic/analysis_options.yaml analyzer: exclude: - lib/**/*.g.dart - - example/* + - example/** linter: rules: diff --git a/example/lib/custom_message.dart b/example/lib/custom_message.dart index e545a168..5da4541a 100644 --- a/example/lib/custom_message.dart +++ b/example/lib/custom_message.dart @@ -16,13 +16,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// or to retrieve outer scope needed such as messages from the [Channel.state]. void main() async { final client = Client( - 'b67pax5b2wdq', + 's2dxdhpxd94g', logLevel: Level.INFO, ); await client.setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); runApp(MyApp(client)); diff --git a/example/lib/custom_theme.dart b/example/lib/custom_theme.dart index 27b09350..21db1c6c 100644 --- a/example/lib/custom_theme.dart +++ b/example/lib/custom_theme.dart @@ -20,13 +20,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// You can perform these more granular style changes using [StreamChatTheme.copyWith]. void main() async { final client = Client( - 'b67pax5b2wdq', + 's2dxdhpxd94g', logLevel: Level.INFO, ); await client.setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); runApp(MyApp(client)); diff --git a/example/lib/customize_channel_preview.dart b/example/lib/customize_channel_preview.dart index c7a34a48..55da346c 100644 --- a/example/lib/customize_channel_preview.dart +++ b/example/lib/customize_channel_preview.dart @@ -21,13 +21,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// - We retrieve the count of unread messages from [Channel.state] void main() async { final client = Client( - 'b67pax5b2wdq', + 's2dxdhpxd94g', logLevel: Level.INFO, ); await client.setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); runApp(MyApp(client)); diff --git a/example/lib/customize_message_widget.dart b/example/lib/customize_message_widget.dart index 58b4149a..c963f7ba 100644 --- a/example/lib/customize_message_widget.dart +++ b/example/lib/customize_message_widget.dart @@ -16,13 +16,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// or to retrieve outer scope needed such as messages from the [Channel.state]. void main() async { final client = Client( - 'b67pax5b2wdq', + 's2dxdhpxd94g', logLevel: Level.INFO, ); await client.setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); runApp(MyApp(client)); diff --git a/example/lib/multiple_conversation.dart b/example/lib/multiple_conversation.dart index 0a6b9131..17646bd3 100644 --- a/example/lib/multiple_conversation.dart +++ b/example/lib/multiple_conversation.dart @@ -20,13 +20,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// [ChannelListView] handles pagination and updates automatically out of the box when new channels are created or when a new message is added to a channel. void main() async { final client = Client( - 'b67pax5b2wdq', + 's2dxdhpxd94g', logLevel: Level.INFO, ); await client.setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); runApp(MyApp(client)); diff --git a/example/lib/single_conversation.dart b/example/lib/single_conversation.dart index 7a7b40ab..5f5d03ea 100644 --- a/example/lib/single_conversation.dart +++ b/example/lib/single_conversation.dart @@ -31,8 +31,8 @@ void main() async { ); await client.setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.Xd4h2PUBo2NYPk12gjlXDNY71jlyJYTCuQ_moeNbnbA', + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); final channel = client.channel('messaging', id: 'godevs'); diff --git a/example/lib/threads.dart b/example/lib/threads.dart index 660842db..f89e6fce 100644 --- a/example/lib/threads.dart +++ b/example/lib/threads.dart @@ -11,13 +11,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Now we can open threads and create new ones as well, if you long press a message you can tap on Reply and it will open the same [ThreadPage]. void main() async { final client = Client( - 'b67pax5b2wdq', + 's2dxdhpxd94g', logLevel: Level.INFO, ); await client.setUser( - User(id: 'falling-mountain-7'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmFsbGluZy1tb3VudGFpbi03In0.AKgRXHMQQMz6vJAKszXdY8zMFfsAgkoUeZHlI-Szz9E', + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', ); runApp(MyApp(client)); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9ceff34f..266926c8 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: stream_chat_flutter: path: ../ flutter_apns: ^1.1.0 - flutter_local_notifications: ^1.4.1 + flutter_local_notifications: ^1.4.3 dev_dependencies: flutter_test: From 3099b9566b215131043764494149c35321ca74a1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 3 Jun 2020 11:56:57 +0200 Subject: [PATCH 121/133] version bump --- CHANGELOG.md | 4 ++ example/ios/Podfile | 3 +- example/ios/Podfile.lock | 33 +++++-------- example/ios/Runner.xcodeproj/project.pbxproj | 52 ++++++++++---------- pubspec.yaml | 6 +-- 5 files changed, 47 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 823b859c..ffe5d7f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1-alpha+7 + +- Update llc dependency + ## 0.2.1-alpha+6 - Update llc dependency diff --git a/example/ios/Podfile b/example/ios/Podfile index d4fe653f..ad84eb7b 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -63,8 +63,7 @@ target 'Runner' do # Keep pod path relative so it can be checked into Podfile.lock. pod 'Flutter', :path => 'Flutter' - pod 'StreamChatClient', :git => 'https://github.com/GetStream/stream-chat-swift.git', :branch => 'release/2.0' - + pod 'StreamChatClient' # Plugin Pods # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 9b203d35..a3821dbf 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -126,7 +126,7 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (6.5.2): - GoogleUtilities/Logger - - GzipSwift (5.0.0) + - GzipSwift (5.1.1) - image_picker (0.0.1): - Flutter - moor_ffi (0.0.1): @@ -142,7 +142,7 @@ PODS: - Flutter - PromisesObjC (1.2.8) - Protobuf (3.11.4) - - ReachabilitySwift (4.3.1) + - ReachabilitySwift (5.0.0) - SDWebImage (5.8.0): - SDWebImage/Core (= 5.8.0) - SDWebImage/Core (5.8.0) @@ -159,10 +159,10 @@ PODS: - Flutter - FMDB (~> 2.7.2) - Starscream (3.1.1) - - StreamChatClient (2.0.0): - - GzipSwift (~> 5.0.0) - - ReachabilitySwift (~> 4.3.0) - - Starscream (~> 3.1.0) + - StreamChatClient (2.0.1): + - GzipSwift (~> 5.1) + - ReachabilitySwift (~> 5.0) + - Starscream (~> 3.1) - url_launcher (0.0.1): - Flutter - url_launcher_macos (0.0.1): @@ -192,7 +192,7 @@ DEPENDENCIES: - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - - StreamChatClient (from `https://github.com/GetStream/stream-chat-swift.git`, branch `release/2.0`) + - StreamChatClient - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) @@ -227,6 +227,7 @@ SPEC REPOS: - SDWebImage - SDWebImageFLPlugin - Starscream + - StreamChatClient EXTERNAL SOURCES: file_picker: @@ -259,9 +260,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_web/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" - StreamChatClient: - :branch: release/2.0 - :git: https://github.com/GetStream/stream-chat-swift.git url_launcher: :path: ".symlinks/plugins/url_launcher/ios" url_launcher_macos: @@ -275,17 +273,12 @@ EXTERNAL SOURCES: wakelock: :path: ".symlinks/plugins/wakelock/ios" -CHECKOUT OPTIONS: - StreamChatClient: - :commit: 941fed4d692712fe32a8c3fa7a3acc01a4b6f60e - :git: https://github.com/GetStream/stream-chat-swift.git - SPEC CHECKSUMS: DKImagePickerController: 4a3e7948a848c4348e600b3fe5ce41478835fa10 DKPhotoGallery: 0290d32343574f06eaa4c26f8f2f8a1035e916be file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Firebase: fe7f74012742ab403451dd283e6909b8f1fb348a - firebase_messaging: 1069878b13fd61e296607e83897aee0ca0fc1f2e + firebase_messaging: 21344b3b3a7d9d325d63a70e3750c0c798fe1e03 FirebaseAnalytics: 572e467f3d977825266e8ccd52674aa3e6f47eac FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae FirebaseCore: ed0a24c758a57c2b88c5efa8e6a8195e868af589 @@ -305,7 +298,7 @@ SPEC CHECKSUMS: GoogleDataTransport: a857c6a002d201b524dd4bc2ed7e7355ed07e785 GoogleDataTransportCCTSupport: 32f75fbe904c82772fcbb6b6bd4525bfb6f2a862 GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e - GzipSwift: 5592f4d62b641e04d06443ba471f8ed76b1363e4 + GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa image_picker: 66aa71bc96850a90590a35d4c4a2907b0d823109 moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6 nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd @@ -313,7 +306,7 @@ SPEC CHECKSUMS: path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 - ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b + ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d @@ -321,7 +314,7 @@ SPEC CHECKSUMS: shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 - StreamChatClient: a5b5a85b0bcccf3ccb26a6847f110912a8c05e92 + StreamChatClient: 91b0f585e7dc92ade58e657daffafb16d485b2a6 url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c @@ -329,6 +322,6 @@ SPEC CHECKSUMS: video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 -PODFILE CHECKSUM: fc856097c8855a277ba9200358b7361bf84ee637 +PODFILE CHECKSUM: 5cc7e2f1316491ee530029e2e8391f4100d41fbd COCOAPODS: 1.8.4 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 07264601..456c5e1f 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -237,13 +237,13 @@ 0BC14C4C242B5A7A0028DE94 = { CreatedOnToolsVersion = 11.4; DevelopmentTeam = EHV7XZLAHA; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = EHV7XZLAHA; LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; }; }; @@ -412,9 +412,9 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Notifications/Info.plist; @@ -424,7 +424,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "flutter example notifications"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -442,9 +442,9 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Notifications/Info.plist; @@ -453,7 +453,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "flutter example notifications"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -469,9 +469,9 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Notifications/Info.plist; @@ -480,7 +480,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "flutter example notifications"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -546,10 +546,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -564,7 +564,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "flutter app example"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -687,10 +687,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -705,7 +705,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "flutter app example"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -721,10 +721,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = EHV7XZLAHA; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -739,7 +739,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "flutter app example"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/pubspec.yaml b/pubspec.yaml index c928d1e0..12e0e323 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha+6 +version: 0.2.1-alpha+7 environment: sdk: ">=2.3.0 <3.0.0" @@ -20,8 +20,8 @@ dependencies: chewie: ^0.9.10 file_picker: ^1.9.0+1 image_picker: ^0.6.6+5 - flutter_keyboard_visibility: ^2.0.0 - stream_chat: ^0.2.0-alpha+17 + flutter_keyboard_visibility: ^3.0.0 + stream_chat: ^0.2.0-alpha+18 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From 301cac18fb82efbd75da22251234845370e53dc5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 4 Jun 2020 14:49:47 +0200 Subject: [PATCH 122/133] use querymembers --- lib/src/message_input.dart | 70 +++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index e00cad54..9926aa8e 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -392,6 +392,16 @@ class MessageInputState extends State { .split('@'); final query = splits.last.toLowerCase(); + Future> queryMembers; + + if (query.isNotEmpty) { + queryMembers = StreamChannel.of(context).channel.queryMembers(filter: { + 'name': { + '\$autocomplete': query, + }, + }).then((res) => res.members); + } + final members = StreamChannel.of(context).channel.state.members?.where((m) { return m.user.name.toLowerCase().contains(query); })?.toList() ?? @@ -419,36 +429,42 @@ class MessageInputState extends State { ], color: StreamChatTheme.of(context).primaryColor, ), - child: ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: members - .map((m) => ListTile( - leading: UserAvatar( - user: m.user, - ), - title: Text('${m.user.name}'), - onTap: () { - _mentionedUsers.add(m.user); + child: FutureBuilder>( + future: queryMembers ?? Future.value(members), + initialData: members, + builder: (context, snapshot) { + return ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: snapshot.data + .map((m) => ListTile( + leading: UserAvatar( + user: m.user, + ), + title: Text('${m.user.name}'), + onTap: () { + _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, - ), - ); + textEditingController.value = TextEditingValue( + text: rejoin + + textEditingController.text.substring( + textEditingController + .selection.start), + selection: TextSelection.collapsed( + offset: rejoin.length, + ), + ); - _mentionsOverlay?.remove(); - _mentionsOverlay = null; - }, - )) - .toList(), - ), + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + }, + )) + .toList(), + ); + }), ), ), ); From cf29e62873f012f0eba7092f5533a2de126f4371 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 4 Jun 2020 16:38:44 +0200 Subject: [PATCH 123/133] bump version --- CHANGELOG.md | 4 ++++ pubspec.yaml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffe5d7f3..41e38e41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1-alpha+8 + +- User queryMembers for mentions + ## 0.2.1-alpha+7 - Update llc dependency diff --git a/pubspec.yaml b/pubspec.yaml index 12e0e323..7335e45e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha+7 +version: 0.2.1-alpha+8 environment: sdk: ">=2.3.0 <3.0.0" @@ -19,9 +19,9 @@ dependencies: video_player: ^0.10.11+1 chewie: ^0.9.10 file_picker: ^1.9.0+1 - image_picker: ^0.6.6+5 + image_picker: ^0.6.7 flutter_keyboard_visibility: ^3.0.0 - stream_chat: ^0.2.0-alpha+18 + stream_chat: ^0.2.0-alpha+19 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From 7006aace129e08a4ddd7c5b8183a9da58c7b465e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 10 Jun 2020 15:21:04 +0200 Subject: [PATCH 124/133] add read indicators --- lib/src/message_list_view.dart | 13 ++++- lib/src/message_widget.dart | 91 ++++++++++++++++++++++++++-------- pubspec.yaml | 2 +- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 85a1b0b0..b3fb7924 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -425,6 +425,16 @@ class _MessageListViewState extends State { final isNextUser = index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id; + final readList = StreamChannel.of(context) + .channel + .state + .read + .where((read) => + read.lastRead.isAfter(message.createdAt) && + (index == 0 || + read.lastRead.isBefore(messages[index - 1].createdAt))) + .toList(); + return MessageWidget( message: message, reverse: isMyMessage, @@ -439,7 +449,7 @@ class _MessageListViewState extends State { (index == 0 || message.status != MessageSendingStatus.SENT) ? DisplayWidget.show : DisplayWidget.hide, - showTimestamp: !isNextUser, + showTimestamp: !isNextUser || readList.isNotEmpty, showEditMessage: isMyMessage, showDeleteMessage: isMyMessage, borderSide: isMyMessage ? BorderSide.none : null, @@ -455,6 +465,7 @@ class _MessageListViewState extends State { messageTheme: isMyMessage ? StreamChatTheme.of(context).ownMessageTheme : StreamChatTheme.of(context).otherMessageTheme, + readList: readList, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 83705894..f93aeaa0 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -97,6 +97,8 @@ class MessageWidget extends StatefulWidget { /// The function called when tapping on UserAvatar final void Function(User) onUserAvatarTap; + final List readList; + /// If true show the users username next to the timestamp of the message final bool showUsername; final bool showTimestamp; @@ -130,6 +132,7 @@ class MessageWidget extends StatefulWidget { this.editMessageInputBuilder, this.textBuilder, Map customAttachmentBuilders, + this.readList, this.padding, this.textPadding = const EdgeInsets.all(8.0), this.attachmentPadding = EdgeInsets.zero, @@ -298,8 +301,9 @@ class _MessageWidgetState extends State { ), if ((widget.message.createdAt != null && widget.showTimestamp) || - widget.showUsername) - _buildUsernameAndTimestamp(leftPadding), + widget.showUsername || + widget.readList?.isNotEmpty == true) + _buildBottomRow(leftPadding), ], ), ), @@ -360,36 +364,79 @@ class _MessageWidgetState extends State { ); } - Padding _buildUsernameAndTimestamp(double leftPadding) { + Padding _buildBottomRow(double leftPadding) { return Padding( padding: EdgeInsets.only( left: leftPadding, top: 2, ), - child: Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - child: RichText( - text: TextSpan( - style: widget.messageTheme.createdAt, - children: [ - if (widget.showUsername) - TextSpan( - text: widget.message.user.name, - style: TextStyle(fontWeight: FontWeight.bold), - ), - if (widget.message.createdAt != null && widget.showTimestamp) - TextSpan( - text: Jiffy(widget.message.createdAt.toLocal()) - .format(' HH:mm'), - ), - ], + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + child: RichText( + text: TextSpan( + style: widget.messageTheme.createdAt, + children: [ + if (widget.showUsername) + TextSpan( + text: widget.message.user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + if (widget.message.createdAt != null && widget.showTimestamp) + TextSpan( + text: Jiffy(widget.message.createdAt.toLocal()) + .format(' HH:mm'), + ), + ], + ), + ), ), - ), + if (widget.readList?.isNotEmpty == true) + SizedBox.fromSize( + size: Size(widget.readList.length * 20.0, 17), + child: Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + child: Padding( + padding: const EdgeInsets.only(left: 4.0), + child: _buildReadIndicator(), + ), + ), + ), + ], ), ); } + Widget _buildReadIndicator() { + var padding = 0.0; + return Stack( + children: widget.readList.map((e) { + padding += 10.0; + return Positioned( + left: padding - 10, + bottom: 0, + top: 0, + child: Material( + color: Colors.white, + shape: CircleBorder(), + child: Padding( + padding: const EdgeInsets.all(1.0), + child: UserAvatar( + user: e.user, + constraints: BoxConstraints.loose(Size.fromRadius(16)), + ), + ), + ), + ); + }).toList(), + ); + } + Widget _buildReactionIndicator(BuildContext context) { return AnimatedSwitcher( key: _reactionPickerKey, diff --git a/pubspec.yaml b/pubspec.yaml index 7335e45e..f3007463 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: file_picker: ^1.9.0+1 image_picker: ^0.6.7 flutter_keyboard_visibility: ^3.0.0 - stream_chat: ^0.2.0-alpha+19 + stream_chat: ^0.2.0-alpha+20 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From 1a72987dbf82ae1beccddbc45af3714a123e164b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 10 Jun 2020 15:35:41 +0200 Subject: [PATCH 125/133] fix read indicator ui --- lib/src/message_list_view.dart | 4 +++- lib/src/message_widget.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index b3fb7924..c4b5a390 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -419,7 +419,8 @@ class _MessageListViewState extends State { ); } - final isMyMessage = message.user.id == StreamChat.of(context).user.id; + final userId = StreamChat.of(context).user.id; + final isMyMessage = message.user.id == userId; final isLastUser = index + 1 < messages.length && message.user.id == messages[index + 1]?.user?.id; final isNextUser = @@ -429,6 +430,7 @@ class _MessageListViewState extends State { .channel .state .read + .where((element) => element.user.id != userId) .where((read) => read.lastRead.isAfter(message.createdAt) && (index == 0 || diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index f93aeaa0..3294ab17 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -397,7 +397,7 @@ class _MessageWidgetState extends State { ), if (widget.readList?.isNotEmpty == true) SizedBox.fromSize( - size: Size(widget.readList.length * 20.0, 17), + size: Size((widget.readList.length * 10.0) + 10, 17), child: Transform( alignment: Alignment.center, transform: Matrix4.rotationY(widget.reverse ? pi : 0), From 0a5bb5434a2fd8a5a18db5c437d5d54304369616 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 10 Jun 2020 15:52:13 +0200 Subject: [PATCH 126/133] version bump --- CHANGELOG.md | 5 +++++ pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e38e41..d0c72144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.1-alpha+9 + +- Add read indicators +- Update llc dependency + ## 0.2.1-alpha+8 - User queryMembers for mentions diff --git a/pubspec.yaml b/pubspec.yaml index f3007463..0a55458d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha+8 +version: 0.2.1-alpha+9 environment: sdk: ">=2.3.0 <3.0.0" From f35321a7d9c377b5148eaf66d0b6990941fd8c45 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 22 Jun 2020 15:18:03 +0200 Subject: [PATCH 127/133] fix message list when readlist is null --- lib/src/message_list_view.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index c4b5a390..a48fee83 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -429,13 +429,13 @@ class _MessageListViewState extends State { final readList = StreamChannel.of(context) .channel .state - .read - .where((element) => element.user.id != userId) - .where((read) => + ?.read + ?.where((element) => element.user.id != userId) + ?.where((read) => read.lastRead.isAfter(message.createdAt) && (index == 0 || read.lastRead.isBefore(messages[index - 1].createdAt))) - .toList(); + ?.toList(); return MessageWidget( message: message, @@ -451,7 +451,7 @@ class _MessageListViewState extends State { (index == 0 || message.status != MessageSendingStatus.SENT) ? DisplayWidget.show : DisplayWidget.hide, - showTimestamp: !isNextUser || readList.isNotEmpty, + showTimestamp: !isNextUser || readList?.isNotEmpty == true, showEditMessage: isMyMessage, showDeleteMessage: isMyMessage, borderSide: isMyMessage ? BorderSide.none : null, From b5b844e16fa67e75c183bd3259ffa24d6ae1b712 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 22 Jun 2020 17:05:33 +0200 Subject: [PATCH 128/133] version bump --- CHANGELOG.md | 4 ++++ example/lib/customize_message_widget.dart | 3 +++ pubspec.yaml | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c72144..eaa92c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1-alpha+10 + +- Update llc dependency + ## 0.2.1-alpha+9 - Add read indicators diff --git a/example/lib/customize_message_widget.dart b/example/lib/customize_message_widget.dart index c963f7ba..fc614125 100644 --- a/example/lib/customize_message_widget.dart +++ b/example/lib/customize_message_widget.dart @@ -98,6 +98,9 @@ class ChannelPage extends StatelessWidget { ) { final message = details.message; final color = details.isMyMessage ? Colors.blueGrey : Colors.blue; + if (message.isSystem) { + return SizedBox(); + } return MessageWidget( message: message, messageTheme: details.isMyMessage diff --git a/pubspec.yaml b/pubspec.yaml index 0a55458d..3ba95c17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha+9 +version: 0.2.1-alpha+10 environment: sdk: ">=2.3.0 <3.0.0" @@ -21,7 +21,7 @@ dependencies: file_picker: ^1.9.0+1 image_picker: ^0.6.7 flutter_keyboard_visibility: ^3.0.0 - stream_chat: ^0.2.0-alpha+20 + stream_chat: ^0.2.0-alpha+22 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From a5bb7072b346db8f6b3d8d38bdf8b7f9b79828ff Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 23 Jun 2020 13:03:23 +0200 Subject: [PATCH 129/133] update deprecated dependency --- lib/src/message_input.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 9926aa8e..6358e39e 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -161,6 +161,7 @@ class MessageInputState extends State { final _focusNode = FocusNode(); final List _mentionedUsers = []; + final _imagePicker = ImagePicker(); bool _inputEnabled = true; bool _messageIsPresent = false; bool _typingStarted = false; @@ -721,11 +722,13 @@ class MessageInputState extends State { } if (camera) { + PickedFile pickedFile; if (fileType == DefaultAttachmentTypes.image) { - file = await ImagePicker.pickImage(source: ImageSource.camera); + pickedFile = await _imagePicker.getImage(source: ImageSource.camera); } else if (fileType == DefaultAttachmentTypes.video) { - file = await ImagePicker.pickVideo(source: ImageSource.camera); + pickedFile = await _imagePicker.getVideo(source: ImageSource.camera); } + file = File(pickedFile.path); } else { FileType type; if (fileType == DefaultAttachmentTypes.image) { From 73aab693c74570e9101b9c11811724c34f2e628e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 24 Jun 2020 10:34:03 +0200 Subject: [PATCH 130/133] use last message getter --- lib/src/channel_preview.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 1e86f4fb..5a3432aa 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -122,11 +122,7 @@ class ChannelPreview extends StatelessWidget { stream: channel.state.messagesStream, initialData: channel.state.messages, builder: (context, snapshot) { - final messages = snapshot.data; - final lastMessage = messages?.isNotEmpty == true - ? messages.lastWhere((m) => - !(m.isDeleted && m.status == MessageSendingStatus.FAILED)) - : null; + final lastMessage = channel.state.lastMessage; if (lastMessage == null) { return SizedBox(); } From 1d2ad50b93a2a1780941bef75e3d8089bd192fdf Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 24 Jun 2020 11:54:57 +0200 Subject: [PATCH 131/133] update dependencies --- pubspec.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 3ba95c17..12531197 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,13 +14,13 @@ dependencies: jiffy: ^3.0.1 flutter_portal: ^0.1.0 cached_network_image: ^2.2.0+1 - flutter_markdown: ^0.4.1 - url_launcher: ^5.4.10 + flutter_markdown: ^0.4.2 + url_launcher: ^5.4.11 video_player: ^0.10.11+1 chewie: ^0.9.10 - file_picker: ^1.9.0+1 - image_picker: ^0.6.7 - flutter_keyboard_visibility: ^3.0.0 + file_picker: ^1.12.0 + image_picker: ^0.6.7+2 + flutter_keyboard_visibility: ^3.2.1 stream_chat: ^0.2.0-alpha+22 mime: ^0.9.6+3 visibility_detector: ^0.1.5 From 90ff138dece578eadf20144b88bce0ffa7fff3c5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 24 Jun 2020 15:54:00 +0200 Subject: [PATCH 132/133] version bump --- CHANGELOG.md | 4 ++++ pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa92c3f..d0054b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1-alpha+11 + +- Update llc dependency + ## 0.2.1-alpha+10 - Update llc dependency diff --git a/pubspec.yaml b/pubspec.yaml index 12531197..77840411 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.1-alpha+10 +version: 0.2.1-alpha+11 environment: sdk: ">=2.3.0 <3.0.0" @@ -21,7 +21,7 @@ dependencies: file_picker: ^1.12.0 image_picker: ^0.6.7+2 flutter_keyboard_visibility: ^3.2.1 - stream_chat: ^0.2.0-alpha+22 + stream_chat: ^0.2.0-alpha+23 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4 From 57d4638b154288acf4866e1b3e6f7e2338434f8c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 1 Jul 2020 10:00:29 +0200 Subject: [PATCH 133/133] update examples --- example/lib/multiple_conversation.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/example/lib/multiple_conversation.dart b/example/lib/multiple_conversation.dart index 17646bd3..647ae7dd 100644 --- a/example/lib/multiple_conversation.dart +++ b/example/lib/multiple_conversation.dart @@ -44,9 +44,7 @@ class MyApp extends StatelessWidget { client: client, child: child, ), - home: Container( - child: ChannelListPage(), - ), + home: ChannelListPage(), ); } }