From 362be0b704b5ae3042dedf2d1be06bc04918e245 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 25 Nov 2020 10:19:45 +0100 Subject: [PATCH 1/2] add message tap callbacks --- lib/src/message_list_view.dart | 15 ++ lib/src/message_widget.dart | 310 +++++++++++++++++---------------- lib/src/system_message.dart | 104 ++++++----- 3 files changed, 237 insertions(+), 192 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 51b26c2aa..296656e6e 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -103,6 +103,9 @@ class MessageListView extends StatefulWidget { this.threadBuilder, this.onThreadTap, this.dateDividerBuilder, + this.onMessageTap, + this.onSystemMessageTap, + this.onParentMessageTap, this.scrollPhysics = const AlwaysScrollableScrollPhysics(), this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, }) : super(key: key); @@ -120,6 +123,15 @@ class MessageListView extends StatefulWidget { /// By default it calls [Navigator.push] using the widget built using [threadBuilder] final ThreadTapCallback onThreadTap; + /// The function called when tapping on the message when the message is not failed + final Function(Message) onMessageTap; + + /// The function called when tapping on a system message + final Function(Message) onSystemMessageTap; + + /// The function called when tapping on the parent message when the message is not failed + final Function(Message) onParentMessageTap; + /// Parent message in case of a thread final Message parentMessage; @@ -412,6 +424,7 @@ class _MessageListViewState extends State { topRight: Radius.circular(16), bottomRight: Radius.circular(16), ), + onMessageTap: widget.onParentMessageTap, borderSide: isMyMessage ? BorderSide.none : null, showUserAvatar: DisplayWidget.show, messageTheme: isMyMessage @@ -427,6 +440,7 @@ class _MessageListViewState extends State { ) { if (message.type == 'system' && message.text?.isNotEmpty == true) { return SystemMessage( + onMessageTap: widget.onSystemMessageTap, message: message, ); } @@ -464,6 +478,7 @@ class _MessageListViewState extends State { (index == 0 || message.status != MessageSendingStatus.SENT) ? DisplayWidget.show : DisplayWidget.hide, + onMessageTap: widget.onMessageTap, showTimestamp: !isNextUser || readList?.isNotEmpty == true, showEditMessage: isMyMessage, showDeleteMessage: isMyMessage, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 056e433a5..d634b0b82 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -40,7 +40,14 @@ class MessageWidget extends StatefulWidget { /// The function called when tapping on replies final void Function(Message) onThreadTap; + + /// The function called when tapping on the message when the message is not failed + final void Function(Message) onMessageTap; + + /// The builder of MessageInput used while editing this message final Widget Function(BuildContext, Message) editMessageInputBuilder; + + /// The builder of the text of the message final Widget Function(BuildContext, Message) textBuilder; /// Function called on long press @@ -113,6 +120,7 @@ class MessageWidget extends StatefulWidget { Key key, @required this.message, @required this.messageTheme, + this.onMessageTap, this.reverse = false, this.shape, this.attachmentShape, @@ -210,97 +218,108 @@ class _MessageWidgetState extends State { if (widget.showSendingIndicator == DisplayWidget.gone) { leftPadding -= 7; } - return Portal( - child: Padding( - padding: widget.padding ?? EdgeInsets.all(8), - child: Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - child: FractionallySizedBox( - alignment: Alignment.centerLeft, - widthFactor: 0.75, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Column( + return GestureDetector( + onTap: () => onMessageTap(context), + onLongPress: () => onLongPress(context), + behavior: HitTestBehavior.opaque, + child: Container( + width: double.infinity, + child: Portal( + child: Padding( + padding: widget.padding ?? EdgeInsets.all(8), + child: Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: 0.75, + child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.end, + Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - if (widget.showSendingIndicator == DisplayWidget.show) - _buildSendingIndicator(), - SizedBox( - width: 2, - ), - if (widget.showSendingIndicator == DisplayWidget.hide) - SizedBox( - width: 8, - ), - if (widget.showUserAvatar == DisplayWidget.show) - _buildUserAvatar(), - SizedBox( - width: 6, - ), - if (widget.showUserAvatar == DisplayWidget.hide) - SizedBox( - width: widget.messageTheme.avatarTheme.constraints - .maxWidth + - 8, - ), - Flexible( - child: Padding( - padding: widget.showReactions - ? EdgeInsets.only( - top: _reactionPadding, - ) - : EdgeInsets.zero, - child: PortalEntry( - portalAnchor: Alignment(0, 1), - childAnchor: Alignment.topRight, - portal: _buildReactionIndicator(context), - child: (widget.message.isDeleted && - widget.message.status != - MessageSendingStatus.FAILED_DELETE) - ? Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY( - widget.reverse ? pi : 0), - child: DeletedMessage( - messageTheme: widget.messageTheme, - ), - ) - : Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - ..._parseAttachments(context), - if (widget.message.text - .trim() - .isNotEmpty) - _buildTextBubble(context), - ], - ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.showSendingIndicator == + DisplayWidget.show) + _buildSendingIndicator(), + SizedBox( + width: 2, ), - ), + if (widget.showSendingIndicator == + DisplayWidget.hide) + SizedBox( + width: 8, + ), + if (widget.showUserAvatar == DisplayWidget.show) + _buildUserAvatar(), + SizedBox( + width: 6, + ), + if (widget.showUserAvatar == DisplayWidget.hide) + SizedBox( + width: widget.messageTheme.avatarTheme + .constraints.maxWidth + + 8, + ), + Flexible( + child: Padding( + padding: widget.showReactions + ? EdgeInsets.only( + top: _reactionPadding, + ) + : EdgeInsets.zero, + child: PortalEntry( + portalAnchor: Alignment(0, 1), + childAnchor: Alignment.topRight, + portal: _buildReactionIndicator(context), + child: (widget.message.isDeleted && + widget.message.status != + MessageSendingStatus + .FAILED_DELETE) + ? Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY( + widget.reverse ? pi : 0), + child: DeletedMessage( + messageTheme: widget.messageTheme, + ), + ) + : Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + ..._parseAttachments(context), + if (widget.message.text + .trim() + .isNotEmpty) + _buildTextBubble(context), + ], + ), + ), + ), + ), + ], ), + if (widget.showReplyIndicator && + widget.message.replyCount > 0) + _buildReplyIndicator(leftPadding), ], ), - if (widget.showReplyIndicator && - widget.message.replyCount > 0) - _buildReplyIndicator(leftPadding), + if ((widget.message.createdAt != null && + widget.showTimestamp) || + widget.showUsername || + widget.readList?.isNotEmpty == true) + _buildBottomRow(leftPadding), ], ), - if ((widget.message.createdAt != null && - widget.showTimestamp) || - widget.showUsername || - widget.readList?.isNotEmpty == true) - _buildBottomRow(leftPadding), - ], + ), ), ), ), @@ -531,47 +550,42 @@ class _MessageWidgetState extends State { padding: EdgeInsets.only( bottom: 4, ), - child: GestureDetector( - onTap: () => retryMessage(context), - onLongPress: () => onLongPress(context), - child: Material( - color: _getBackgroundColor(), - clipBehavior: Clip.hardEdge, - shape: widget.attachmentShape ?? - widget.shape ?? - ContinuousRectangleBorder( - side: widget.attachmentBorderSide ?? - widget.borderSide ?? - BorderSide( - color: - Theme.of(context).brightness == Brightness.dark - ? Colors.white.withAlpha(24) - : Colors.black.withAlpha(24), - ), - borderRadius: widget.attachmentBorderRadiusGeometry ?? - widget.borderRadiusGeometry ?? - BorderRadius.zero, - ), - child: Padding( - padding: widget.attachmentPadding, - child: Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - getFailedMessageWidget( - context, - padding: const EdgeInsets.all(8.0), + child: Material( + color: _getBackgroundColor(), + clipBehavior: Clip.hardEdge, + shape: widget.attachmentShape ?? + widget.shape ?? + ContinuousRectangleBorder( + side: widget.attachmentBorderSide ?? + widget.borderSide ?? + BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withAlpha(24) + : Colors.black.withAlpha(24), ), - attachmentBuilder( - context, - widget.message, - attachment, - ), - ], - ), + borderRadius: widget.attachmentBorderRadiusGeometry ?? + widget.borderRadiusGeometry ?? + BorderRadius.zero, + ), + child: Padding( + padding: widget.attachmentPadding, + child: Transform( + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + getFailedMessageWidget( + context, + padding: const EdgeInsets.all(8.0), + ), + attachmentBuilder( + context, + widget.message, + attachment, + ), + ], ), ), ), @@ -700,33 +714,29 @@ class _MessageWidgetState extends State { } Widget _buildTextBubble(BuildContext context) { - return GestureDetector( - onTap: () => retryMessage(context), - onLongPress: () => onLongPress(context), - child: Material( - shape: widget.shape ?? - ContinuousRectangleBorder( - side: widget.borderSide ?? - BorderSide( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withAlpha(24) - : Colors.black.withAlpha(24), - ), - borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: Padding( - padding: widget.textPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - getFailedMessageWidget(context), - _buildText(context), - ], - ), + return Material( + shape: widget.shape ?? + ContinuousRectangleBorder( + side: widget.borderSide ?? + BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withAlpha(24) + : Colors.black.withAlpha(24), + ), + borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: Transform( + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + alignment: Alignment.center, + child: Padding( + padding: widget.textPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + getFailedMessageWidget(context), + _buildText(context), + ], ), ), ), @@ -741,7 +751,7 @@ class _MessageWidgetState extends State { : widget.messageTheme.messageBackgroundColor; } - void retryMessage(BuildContext context) { + void onMessageTap(BuildContext context) { final channel = StreamChannel.of(context).channel; if (widget.message.status == MessageSendingStatus.FAILED) { channel.sendMessage(widget.message); @@ -762,6 +772,10 @@ class _MessageWidgetState extends State { ); return; } + + if (widget.onMessageTap != null) { + widget.onMessageTap(widget.message); + } } Widget _buildText(BuildContext context) { diff --git a/lib/src/system_message.dart b/lib/src/system_message.dart index 7454b0eec..7b4dc243d 100644 --- a/lib/src/system_message.dart +++ b/lib/src/system_message.dart @@ -4,11 +4,16 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// It shows a date divider depending on the date difference class SystemMessage extends StatelessWidget { + /// This message final Message message; + /// The function called when tapping on the message when the message is not failed + final void Function(Message) onMessageTap; + const SystemMessage({ Key key, @required this.message, + this.onMessageTap, }) : super(key: key); @override @@ -44,57 +49,68 @@ class SystemMessage extends StatelessWidget { 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: [ + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (onMessageTap != null) { + onMessageTap(message); + } + }, + child: Container( + width: double.infinity, + child: 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( - text: dayInfo, + children: [ + TextSpan( + text: dayInfo, + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + TextSpan(text: ' AT'), + TextSpan(text: ' $hourInfo'), + ], style: TextStyle( - fontWeight: FontWeight.bold, + fontWeight: FontWeight.normal, ), ), - TextSpan(text: ' AT'), - TextSpan(text: ' $hourInfo'), - ], - style: TextStyle( - fontWeight: FontWeight.normal, + style: TextStyle( + fontSize: 10, + color: Theme.of(context) + .textTheme + .headline6 + .color + .withOpacity(.5), + ), ), - ), - style: TextStyle( - fontSize: 10, - color: Theme.of(context) - .textTheme - .headline6 - .color - .withOpacity(.5), - ), + ], ), - ], - ), + ), + divider, + ], ), - divider, - ], + ), ); } } From 026edeb4805157e62c83813c6394aefa18c4602f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 25 Nov 2020 10:23:36 +0100 Subject: [PATCH 2/2] bump mp version --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15164d4fc..672b3da67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.14 + +- Add onMessageTap callbacks + ## 0.2.13+2 - Add debounce to on change messageinput listener diff --git a/pubspec.yaml b/pubspec.yaml index c7ebaba9e..069a6b995 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.13+2 +version: 0.2.14 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues