add message tap callbacks

This commit is contained in:
Salvatore Giordano
2020-11-25 10:19:45 +01:00
parent 3781408371
commit 362be0b704
3 changed files with 237 additions and 192 deletions
+15
View File
@@ -103,6 +103,9 @@ class MessageListView extends StatefulWidget {
this.threadBuilder, this.threadBuilder,
this.onThreadTap, this.onThreadTap,
this.dateDividerBuilder, this.dateDividerBuilder,
this.onMessageTap,
this.onSystemMessageTap,
this.onParentMessageTap,
this.scrollPhysics = const AlwaysScrollableScrollPhysics(), this.scrollPhysics = const AlwaysScrollableScrollPhysics(),
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : super(key: key); }) : super(key: key);
@@ -120,6 +123,15 @@ class MessageListView extends StatefulWidget {
/// By default it calls [Navigator.push] using the widget built using [threadBuilder] /// By default it calls [Navigator.push] using the widget built using [threadBuilder]
final ThreadTapCallback onThreadTap; 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 /// Parent message in case of a thread
final Message parentMessage; final Message parentMessage;
@@ -412,6 +424,7 @@ class _MessageListViewState extends State<MessageListView> {
topRight: Radius.circular(16), topRight: Radius.circular(16),
bottomRight: Radius.circular(16), bottomRight: Radius.circular(16),
), ),
onMessageTap: widget.onParentMessageTap,
borderSide: isMyMessage ? BorderSide.none : null, borderSide: isMyMessage ? BorderSide.none : null,
showUserAvatar: DisplayWidget.show, showUserAvatar: DisplayWidget.show,
messageTheme: isMyMessage messageTheme: isMyMessage
@@ -427,6 +440,7 @@ class _MessageListViewState extends State<MessageListView> {
) { ) {
if (message.type == 'system' && message.text?.isNotEmpty == true) { if (message.type == 'system' && message.text?.isNotEmpty == true) {
return SystemMessage( return SystemMessage(
onMessageTap: widget.onSystemMessageTap,
message: message, message: message,
); );
} }
@@ -464,6 +478,7 @@ class _MessageListViewState extends State<MessageListView> {
(index == 0 || message.status != MessageSendingStatus.SENT) (index == 0 || message.status != MessageSendingStatus.SENT)
? DisplayWidget.show ? DisplayWidget.show
: DisplayWidget.hide, : DisplayWidget.hide,
onMessageTap: widget.onMessageTap,
showTimestamp: !isNextUser || readList?.isNotEmpty == true, showTimestamp: !isNextUser || readList?.isNotEmpty == true,
showEditMessage: isMyMessage, showEditMessage: isMyMessage,
showDeleteMessage: isMyMessage, showDeleteMessage: isMyMessage,
+162 -148
View File
@@ -40,7 +40,14 @@ class MessageWidget extends StatefulWidget {
/// The function called when tapping on replies /// The function called when tapping on replies
final void Function(Message) onThreadTap; 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; final Widget Function(BuildContext, Message) editMessageInputBuilder;
/// The builder of the text of the message
final Widget Function(BuildContext, Message) textBuilder; final Widget Function(BuildContext, Message) textBuilder;
/// Function called on long press /// Function called on long press
@@ -113,6 +120,7 @@ class MessageWidget extends StatefulWidget {
Key key, Key key,
@required this.message, @required this.message,
@required this.messageTheme, @required this.messageTheme,
this.onMessageTap,
this.reverse = false, this.reverse = false,
this.shape, this.shape,
this.attachmentShape, this.attachmentShape,
@@ -210,97 +218,108 @@ class _MessageWidgetState extends State<MessageWidget> {
if (widget.showSendingIndicator == DisplayWidget.gone) { if (widget.showSendingIndicator == DisplayWidget.gone) {
leftPadding -= 7; leftPadding -= 7;
} }
return Portal( return GestureDetector(
child: Padding( onTap: () => onMessageTap(context),
padding: widget.padding ?? EdgeInsets.all(8), onLongPress: () => onLongPress(context),
child: Transform( behavior: HitTestBehavior.opaque,
alignment: Alignment.center, child: Container(
transform: Matrix4.rotationY(widget.reverse ? pi : 0), width: double.infinity,
child: FractionallySizedBox( child: Portal(
alignment: Alignment.centerLeft, child: Padding(
widthFactor: 0.75, padding: widget.padding ?? EdgeInsets.all(8),
child: Column( child: Transform(
crossAxisAlignment: CrossAxisAlignment.start, alignment: Alignment.center,
mainAxisSize: MainAxisSize.min, transform: Matrix4.rotationY(widget.reverse ? pi : 0),
children: <Widget>[ child: FractionallySizedBox(
Column( alignment: Alignment.centerLeft,
widthFactor: 0.75,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Row( Column(
mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
if (widget.showSendingIndicator == DisplayWidget.show) Row(
_buildSendingIndicator(), mainAxisAlignment: MainAxisAlignment.start,
SizedBox( crossAxisAlignment: CrossAxisAlignment.end,
width: 2, mainAxisSize: MainAxisSize.min,
), children: <Widget>[
if (widget.showSendingIndicator == DisplayWidget.hide) if (widget.showSendingIndicator ==
SizedBox( DisplayWidget.show)
width: 8, _buildSendingIndicator(),
), SizedBox(
if (widget.showUserAvatar == DisplayWidget.show) width: 2,
_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: <Widget>[
..._parseAttachments(context),
if (widget.message.text
.trim()
.isNotEmpty)
_buildTextBubble(context),
],
),
), ),
), 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: <Widget>[
..._parseAttachments(context),
if (widget.message.text
.trim()
.isNotEmpty)
_buildTextBubble(context),
],
),
),
),
),
],
), ),
if (widget.showReplyIndicator &&
widget.message.replyCount > 0)
_buildReplyIndicator(leftPadding),
], ],
), ),
if (widget.showReplyIndicator && if ((widget.message.createdAt != null &&
widget.message.replyCount > 0) widget.showTimestamp) ||
_buildReplyIndicator(leftPadding), 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<MessageWidget> {
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: 4, bottom: 4,
), ),
child: GestureDetector( child: Material(
onTap: () => retryMessage(context), color: _getBackgroundColor(),
onLongPress: () => onLongPress(context), clipBehavior: Clip.hardEdge,
child: Material( shape: widget.attachmentShape ??
color: _getBackgroundColor(), widget.shape ??
clipBehavior: Clip.hardEdge, ContinuousRectangleBorder(
shape: widget.attachmentShape ?? side: widget.attachmentBorderSide ??
widget.shape ?? widget.borderSide ??
ContinuousRectangleBorder( BorderSide(
side: widget.attachmentBorderSide ?? color: Theme.of(context).brightness == Brightness.dark
widget.borderSide ?? ? Colors.white.withAlpha(24)
BorderSide( : Colors.black.withAlpha(24),
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: <Widget>[
getFailedMessageWidget(
context,
padding: const EdgeInsets.all(8.0),
), ),
attachmentBuilder( borderRadius: widget.attachmentBorderRadiusGeometry ??
context, widget.borderRadiusGeometry ??
widget.message, BorderRadius.zero,
attachment, ),
), 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: <Widget>[
getFailedMessageWidget(
context,
padding: const EdgeInsets.all(8.0),
),
attachmentBuilder(
context,
widget.message,
attachment,
),
],
), ),
), ),
), ),
@@ -700,33 +714,29 @@ class _MessageWidgetState extends State<MessageWidget> {
} }
Widget _buildTextBubble(BuildContext context) { Widget _buildTextBubble(BuildContext context) {
return GestureDetector( return Material(
onTap: () => retryMessage(context), shape: widget.shape ??
onLongPress: () => onLongPress(context), ContinuousRectangleBorder(
child: Material( side: widget.borderSide ??
shape: widget.shape ?? BorderSide(
ContinuousRectangleBorder( color: Theme.of(context).brightness == Brightness.dark
side: widget.borderSide ?? ? Colors.white.withAlpha(24)
BorderSide( : Colors.black.withAlpha(24),
color: Theme.of(context).brightness == Brightness.dark ),
? Colors.white.withAlpha(24) borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero,
: Colors.black.withAlpha(24), ),
), color: _getBackgroundColor(),
borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero, child: Transform(
), transform: Matrix4.rotationY(widget.reverse ? pi : 0),
color: _getBackgroundColor(), alignment: Alignment.center,
child: Transform( child: Padding(
transform: Matrix4.rotationY(widget.reverse ? pi : 0), padding: widget.textPadding,
alignment: Alignment.center, child: Column(
child: Padding( crossAxisAlignment: CrossAxisAlignment.end,
padding: widget.textPadding, children: [
child: Column( getFailedMessageWidget(context),
crossAxisAlignment: CrossAxisAlignment.end, _buildText(context),
children: [ ],
getFailedMessageWidget(context),
_buildText(context),
],
),
), ),
), ),
), ),
@@ -741,7 +751,7 @@ class _MessageWidgetState extends State<MessageWidget> {
: widget.messageTheme.messageBackgroundColor; : widget.messageTheme.messageBackgroundColor;
} }
void retryMessage(BuildContext context) { void onMessageTap(BuildContext context) {
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
if (widget.message.status == MessageSendingStatus.FAILED) { if (widget.message.status == MessageSendingStatus.FAILED) {
channel.sendMessage(widget.message); channel.sendMessage(widget.message);
@@ -762,6 +772,10 @@ class _MessageWidgetState extends State<MessageWidget> {
); );
return; return;
} }
if (widget.onMessageTap != null) {
widget.onMessageTap(widget.message);
}
} }
Widget _buildText(BuildContext context) { Widget _buildText(BuildContext context) {
+60 -44
View File
@@ -4,11 +4,16 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// It shows a date divider depending on the date difference /// It shows a date divider depending on the date difference
class SystemMessage extends StatelessWidget { class SystemMessage extends StatelessWidget {
/// This message
final Message 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({ const SystemMessage({
Key key, Key key,
@required this.message, @required this.message,
this.onMessageTap,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -44,57 +49,68 @@ class SystemMessage extends StatelessWidget {
dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase(); dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase();
} }
return Row( return GestureDetector(
crossAxisAlignment: CrossAxisAlignment.start, behavior: HitTestBehavior.opaque,
children: <Widget>[ onTap: () {
divider, if (onMessageTap != null) {
Padding( onMessageTap(message);
padding: const EdgeInsets.symmetric(horizontal: 32.0), }
child: Column( },
crossAxisAlignment: CrossAxisAlignment.center, child: Container(
children: <Widget>[ width: double.infinity,
Text( child: Row(
message.text, crossAxisAlignment: CrossAxisAlignment.start,
style: TextStyle( children: <Widget>[
fontSize: 10, divider,
color: Theme.of(context) Padding(
.textTheme padding: const EdgeInsets.symmetric(horizontal: 32.0),
.headline6 child: Column(
.color crossAxisAlignment: CrossAxisAlignment.center,
.withOpacity(.5), children: <Widget>[
fontWeight: FontWeight.bold, Text(
), message.text,
), style: TextStyle(
Text.rich( fontSize: 10,
TextSpan( color: Theme.of(context)
children: [ .textTheme
.headline6
.color
.withOpacity(.5),
fontWeight: FontWeight.bold,
),
),
Text.rich(
TextSpan( TextSpan(
text: dayInfo, children: [
TextSpan(
text: dayInfo,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
TextSpan(text: ' AT'),
TextSpan(text: ' $hourInfo'),
],
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.normal,
), ),
), ),
TextSpan(text: ' AT'), style: TextStyle(
TextSpan(text: ' $hourInfo'), fontSize: 10,
], color: Theme.of(context)
style: TextStyle( .textTheme
fontWeight: FontWeight.normal, .headline6
.color
.withOpacity(.5),
),
), ),
), ],
style: TextStyle(
fontSize: 10,
color: Theme.of(context)
.textTheme
.headline6
.color
.withOpacity(.5),
),
), ),
], ),
), divider,
],
), ),
divider, ),
],
); );
} }
} }