From 8f73d3e40ba75a752e36cf7af22f07c6e657ec9b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 16 Oct 2020 17:25:40 +0200 Subject: [PATCH 1/3] updatep podfile.lock --- example/ios/Podfile.lock | 26 +++++++++++++------------- example/pubspec.yaml | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f289e1b7..d22f1e0d 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -38,10 +38,10 @@ PODS: - Firebase/Messaging (6.26.0): - Firebase/CoreOnly - FirebaseMessaging (~> 4.4.1) - - firebase_core (0.5.0): + - firebase_core (0.5.0-1): - Firebase/CoreOnly (~> 6.26.0) - Flutter - - firebase_messaging (7.0.2): + - firebase_messaging (7.0.3): - Firebase/CoreOnly (~> 6.26.0) - Firebase/Messaging (~> 6.26.0) - firebase_core @@ -87,7 +87,7 @@ PODS: - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) - - GoogleDataTransport (7.4.0): + - GoogleDataTransport (7.5.1): - nanopb (~> 1.30906.0) - GoogleUtilities/AppDelegateSwizzler (6.7.2): - GoogleUtilities/Environment @@ -115,11 +115,11 @@ PODS: - nanopb/encode (1.30906.0) - path_provider (0.0.1): - Flutter - - PromisesObjC (1.2.10) + - PromisesObjC (1.2.11) - Protobuf (3.13.0) - - SDWebImage (5.9.2): - - SDWebImage/Core (= 5.9.2) - - SDWebImage/Core (5.9.2) + - SDWebImage (5.9.3): + - SDWebImage/Core (= 5.9.3) + - SDWebImage/Core (5.9.3) - shared_preferences (0.0.1): - Flutter - sqflite (0.0.1): @@ -233,8 +233,8 @@ SPEC CHECKSUMS: DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Firebase: 7cf5f9c67f03cb3b606d1d6535286e1080e57eb6 - firebase_core: 3134fe79d257d430f163b558caf52a10a87efe8a - firebase_messaging: 2844c37f9ce87c0904b38fe435223161b1a71528 + firebase_core: 00e54a4744164a6b5a250b96dd1ad5afaba7a342 + firebase_messaging: 666d9994651b1ecf8c582b52dd913f3fa58c17ef FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae FirebaseCore: f42e5e5f382cdcf6b617ed737bf6c871a6947b17 FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1 @@ -247,14 +247,14 @@ SPEC CHECKSUMS: flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - GoogleDataTransport: b7f406340a291370045a270c599e53c6fa6ec20f + GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833 GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c - PromisesObjC: b14b1c6b68e306650688599de8a45e49fae81151 + PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f Protobuf: 3dac39b34a08151c6d949560efe3f86134a3f748 - SDWebImage: 0b42b8719ab0c5257177d5894306e8a336b21cbb + SDWebImage: a31ee8e90a97303529e03fb0c333eae0eacb88e9 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 sqlite3: 8f7d2078ae27778699a622a94b853285793422a2 @@ -268,4 +268,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: eb001256612a59f8f9e4d083ad8b9671e69dd184 -COCOAPODS: 1.8.4 +COCOAPODS: 1.10.0.rc.1 diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c3ab3b46..a5a2f73f 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,7 +1,7 @@ name: example description: A new Flutter project. -version: 1.0.28+30 +version: 1.0.30+32 environment: sdk: ">=2.2.2 <3.0.0" From 9be191b856e9681bc657d61b74f4c851764ce943 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Oct 2020 10:11:43 +0200 Subject: [PATCH 2/3] add scroll to bottom fab --- lib/src/message_list_view.dart | 282 +++++++++++------- ...reaction_asset.dart => reaction_icon.dart} | 0 lib/src/stream_chat_theme.dart | 3 +- 3 files changed, 176 insertions(+), 109 deletions(-) rename lib/src/{reaction_asset.dart => reaction_icon.dart} (100%) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 85fba5a8..7c529f95 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -97,6 +97,7 @@ class MessageListView extends StatefulWidget { /// Instantiate a new MessageListView MessageListView({ Key key, + this.showScrollToBottom = true, this.messageBuilder, this.parentMessageBuilder, this.parentMessage, @@ -120,6 +121,9 @@ class MessageListView extends StatefulWidget { /// By default it calls [Navigator.push] using the widget built using [threadBuilder] final ThreadTapCallback onThreadTap; + /// If true will show a scroll to bottom message when there are new messages and the scroll offset is not zero + final bool showScrollToBottom; + /// Parent message in case of a thread final Message parentMessage; @@ -144,132 +148,193 @@ class _MessageListViewState extends State { List _messages = []; List _newMessageList = []; Function _onThreadTap; + bool _showScrollToBottom = false; @override Widget build(BuildContext context) { final streamChannel = StreamChannel.of(context); /// TODO: find a better solution when (https://github.com/flutter/flutter/issues/21023) is fixed - return NotificationListener( - onNotification: (_) { - if (_scrollController.offset < 150 && _newMessageList.isNotEmpty) { - setState(() { - _messages.insertAll(0, _newMessageList); - _newMessageList.clear(); - }); - } - return true; - }, - child: ListView.custom( - key: Key('messageListView'), - physics: widget.scrollPhysics, - keyboardDismissBehavior: widget.keyboardDismissBehavior, - controller: _scrollController, - reverse: true, - childrenDelegate: SliverChildBuilderDelegate( - (context, i) { - if (i == _messages.length + 1) { - if (widget.parentMessage != null) { - if (widget.parentMessageBuilder != null) { - return widget.parentMessageBuilder( + return Stack( + children: [ + NotificationListener( + onNotification: (_) { + if (_scrollController.offset < 150 && _newMessageList.isNotEmpty) { + setState(() { + _messages.insertAll(0, _newMessageList); + _newMessageList.clear(); + }); + } + return true; + }, + child: ListView.custom( + key: Key('messageListView'), + physics: widget.scrollPhysics, + keyboardDismissBehavior: widget.keyboardDismissBehavior, + controller: _scrollController, + reverse: true, + childrenDelegate: SliverChildBuilderDelegate( + (context, i) { + if (i == _messages.length + 1) { + if (widget.parentMessage != null) { + if (widget.parentMessageBuilder != null) { + return widget.parentMessageBuilder( + context, + widget.parentMessage, + ); + } else { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + buildParentMessage(widget.parentMessage), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Container( + padding: const EdgeInsets.all(8), + child: Text( + 'Start of thread', + textAlign: TextAlign.center, + ), + color: + Theme.of(context).accentColor.withAlpha(50), + ), + ), + ], + ); + } + } else { + return SizedBox(); + } + } + + if (i == _messages.length) { + return _buildLoadingIndicator(streamChannel); + } + final message = _messages[i]; + final nextMessage = i > 0 ? _messages[i - 1] : null; + + Widget messageWidget; + + if (i == 0) { + messageWidget = _buildBottomMessage( context, - widget.parentMessage, + message, + _messages, + streamChannel, + ); + } else if (i == _messages.length - 1) { + messageWidget = _buildTopMessage( + context, + message, + _messages, + streamChannel, ); } else { + if (widget.messageBuilder != null) { + messageWidget = Builder( + key: ValueKey('MESSAGE-${message.id}'), + builder: (_) => widget.messageBuilder( + context, + MessageDetails( + context, + message, + _messages, + i, + ), + _messages), + ); + } else { + messageWidget = buildMessage(message, _messages, i); + } + } + + if (nextMessage != null && + !Jiffy(message.createdAt.toLocal()) + .isSame(nextMessage.createdAt.toLocal(), Units.DAY)) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - buildParentMessage(widget.parentMessage), + messageWidget, Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Container( - padding: const EdgeInsets.all(8), - child: Text( - 'Start of thread', - textAlign: TextAlign.center, - ), - color: Theme.of(context).accentColor.withAlpha(50), - ), + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: widget.dateDividerBuilder != null + ? widget.dateDividerBuilder( + nextMessage.createdAt.toLocal()) + : DateDivider( + dateTime: nextMessage.createdAt.toLocal(), + ), ), ], ); } - } else { - return SizedBox(); - } - } - if (i == _messages.length) { - return _buildLoadingIndicator(streamChannel); - } - final message = _messages[i]; - final nextMessage = i > 0 ? _messages[i - 1] : null; - - Widget messageWidget; - - if (i == 0) { - messageWidget = _buildBottomMessage( - context, - message, - _messages, - streamChannel, - ); - } else if (i == _messages.length - 1) { - messageWidget = _buildTopMessage( - context, - message, - _messages, - streamChannel, - ); - } else { - if (widget.messageBuilder != null) { - messageWidget = Builder( - key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder( - context, - MessageDetails( - context, - message, - _messages, - i, - ), - _messages), - ); - } else { - messageWidget = buildMessage(message, _messages, i); - } - } - - if (nextMessage != null && - !Jiffy(message.createdAt.toLocal()) - .isSame(nextMessage.createdAt.toLocal(), Units.DAY)) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - messageWidget, - Padding( - padding: const EdgeInsets.symmetric(vertical: 12.0), - child: widget.dateDividerBuilder != null - ? widget - .dateDividerBuilder(nextMessage.createdAt.toLocal()) - : DateDivider( - dateTime: nextMessage.createdAt.toLocal(), - ), - ), - ], - ); - } - - return messageWidget; - }, - childCount: _messages.length + 2, - findChildIndexCallback: (key) { - final ValueKey valueKey = key; - final index = _messages - .indexWhere((m) => 'MESSAGE-${m.id}' == valueKey.value); - return index != -1 ? index : null; - }, + return messageWidget; + }, + childCount: _messages.length + 2, + findChildIndexCallback: (key) { + final ValueKey valueKey = key; + final index = _messages + .indexWhere((m) => 'MESSAGE-${m.id}' == valueKey.value); + return index != -1 ? index : null; + }, + ), + ), ), + if (widget.showScrollToBottom) + StreamBuilder( + stream: streamChannel.channel.on( + EventType.messageNew, + ), + builder: (context, _) { + if (!_showScrollToBottom || + streamChannel.channel.state.unreadCount == 0) { + return SizedBox(); + } + return _buildScrollToBottom(streamChannel); + }), + ], + ); + } + + Widget _buildScrollToBottom(StreamChannelState streamChannel) { + return Positioned( + bottom: 8, + right: 8, + width: 40, + height: 40, + child: Stack( + clipBehavior: Clip.none, + children: [ + FloatingActionButton( + backgroundColor: Colors.white, + child: Icon( + StreamIcons.down, + color: Colors.black, + ), + onPressed: () { + Future.delayed(Duration(milliseconds: 500), () { + setState(() { + _showScrollToBottom = false; + }); + }); + _scrollController.animateTo( + 0, + duration: Duration(seconds: 1), + curve: Curves.easeInOut, + ); + }, + ), + Positioned( + width: 20, + height: 20, + left: 10, + top: -10, + child: CircleAvatar( + radius: 20, + child: Text(streamChannel.channel.state.unreadCount.toString()), + ), + ), + ], ), ); } @@ -381,6 +446,9 @@ class _MessageListViewState extends State { } } _bottomWasVisible = isVisible; + setState(() { + _showScrollToBottom = !isVisible; + }); }, child: messageWidget, ); diff --git a/lib/src/reaction_asset.dart b/lib/src/reaction_icon.dart similarity index 100% rename from lib/src/reaction_asset.dart rename to lib/src/reaction_icon.dart diff --git a/lib/src/stream_chat_theme.dart b/lib/src/stream_chat_theme.dart index 540cd7e1..359ab102 100644 --- a/lib/src/stream_chat_theme.dart +++ b/lib/src/stream_chat_theme.dart @@ -3,10 +3,9 @@ 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'; +import 'package:stream_chat_flutter/src/reaction_icon.dart'; import 'package:stream_chat_flutter/src/stream_icons.dart'; -import 'reaction_asset.dart'; - /// Inherited widget providing the [StreamChatThemeData] to the widget tree class StreamChatTheme extends InheritedWidget { final StreamChatThemeData data; From 71a80286f7d422f62a2d54a8bbc3fbafaff19520 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 20 Oct 2020 12:21:35 +0200 Subject: [PATCH 3/3] clip message in actions if too long --- lib/src/message_actions_modal.dart | 5 ++++- lib/src/message_reactions_modal.dart | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index 890761d7..714966ce 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -79,7 +79,10 @@ class MessageActionsModal extends StatelessWidget { child: MessageWidget( key: Key('MessageWidget'), reverse: reverse, - message: message, + message: message.text.length > 200 + ? message.copyWith( + text: '${message.text.substring(0, 200)}...') + : message, messageTheme: messageTheme, showReactions: false, showUsername: false, diff --git a/lib/src/message_reactions_modal.dart b/lib/src/message_reactions_modal.dart index d3486f1b..b30f92b3 100644 --- a/lib/src/message_reactions_modal.dart +++ b/lib/src/message_reactions_modal.dart @@ -70,7 +70,10 @@ class MessageReactionsModal extends StatelessWidget { child: MessageWidget( key: Key('MessageWidget'), reverse: reverse, - message: message, + message: message.text.length > 200 + ? message.copyWith( + text: '${message.text.substring(0, 200)}...') + : message, messageTheme: messageTheme, showReactions: false, showUsername: false,