This commit is contained in:
Salvatore Giordano
2020-10-27 09:58:15 +01:00
parent f4dcd69b1e
commit ba37258427
8 changed files with 171 additions and 196 deletions
+8 -4
View File
@@ -79,10 +79,14 @@ class MessageActionsModal extends StatelessWidget {
child: MessageWidget( child: MessageWidget(
key: Key('MessageWidget'), key: Key('MessageWidget'),
reverse: reverse, reverse: reverse,
message: message.text.length > 200 message: message.copyWith(
? message.copyWith( text: message.text.length > 200
text: '${message.text.substring(0, 200)}...') ? '${message.text.substring(0, 200)}...'
: message, : message.text,
attachments: message.attachments.length > 1
? [message.attachments[0]]
: message.attachments,
),
messageTheme: messageTheme, messageTheme: messageTheme,
showReactions: false, showReactions: false,
showUsername: false, showUsername: false,
+1 -1
View File
@@ -543,7 +543,7 @@ class _MessageListViewState extends State<MessageListView> {
topRight: Radius.circular(16), topRight: Radius.circular(16),
bottomRight: Radius.circular(16), bottomRight: Radius.circular(16),
), ),
showUserAvatar: isMyMessage showUserAvatar: !isMyMessage
? DisplayWidget.gone ? DisplayWidget.gone
: (isNextUser ? DisplayWidget.hide : DisplayWidget.show), : (isNextUser ? DisplayWidget.hide : DisplayWidget.show),
messageTheme: isMyMessage messageTheme: isMyMessage
+18 -13
View File
@@ -70,10 +70,14 @@ class MessageReactionsModal extends StatelessWidget {
child: MessageWidget( child: MessageWidget(
key: Key('MessageWidget'), key: Key('MessageWidget'),
reverse: reverse, reverse: reverse,
message: message.text.length > 200 message: message.copyWith(
? message.copyWith( text: message.text.length > 200
text: '${message.text.substring(0, 200)}...') ? '${message.text.substring(0, 200)}...'
: message, : message.text,
attachments: message.attachments.length > 1
? [message.attachments[0]]
: message.attachments,
),
messageTheme: messageTheme, messageTheme: messageTheme,
showReactions: false, showReactions: false,
showUsername: false, showUsername: false,
@@ -175,15 +179,16 @@ class MessageReactionsModal extends StatelessWidget {
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
), ),
Positioned( Positioned(
child: ReactionBubble( child: Align(
reactions: [reaction], alignment: Alignment.centerLeft,
borderColor: isCurrentUser child: Container(
? messageTheme.ownReactionsBorderColor child: ReactionBubble(
: messageTheme.otherReactionsBorderColor, reactions: [reaction],
backgroundColor: isCurrentUser borderColor: messageTheme.reactionsBorderColor,
? messageTheme.ownReactionsBackgroundColor backgroundColor: messageTheme.reactionsBackgroundColor,
: messageTheme.otherReactionsBackgroundColor, flipTail: !isCurrentUser,
flipTail: !isCurrentUser, ),
),
), ),
bottom: 0, bottom: 0,
left: isCurrentUser ? 0 : null, left: isCurrentUser ? 0 : null,
+39 -77
View File
@@ -239,20 +239,23 @@ class _MessageWidgetState extends State<MessageWidget> {
8, 8,
), ),
Flexible( Flexible(
child: Padding( child: PortalEntry(
padding: widget.showReactions portal: ConstrainedBox(
? EdgeInsets.only( child: _buildReactionIndicator(context),
top: widget.message.reactionCounts constraints: BoxConstraints(maxWidth: 22 * 6.0),
?.isNotEmpty == ),
true portalAnchor: Alignment(-1.0, -1.0),
? 12 childAnchor: Alignment(1, -1.0),
: 0, child: Padding(
) padding: widget.showReactions
: EdgeInsets.zero, ? EdgeInsets.only(
child: PortalEntry( top: widget.message.reactionCounts
portalAnchor: Alignment(-0.81, 0), ?.isNotEmpty ==
childAnchor: Alignment.topRight, true
portal: _buildReactionIndicator(context), ? 18
: 0,
)
: EdgeInsets.zero,
child: (widget.message.isDeleted && child: (widget.message.isDeleted &&
widget.message.status != widget.message.status !=
MessageSendingStatus.FAILED_DELETE) MessageSendingStatus.FAILED_DELETE)
@@ -378,74 +381,33 @@ class _MessageWidgetState extends State<MessageWidget> {
); );
} }
Widget _buildReactionIndicator(BuildContext context) { Widget _buildReactionIndicator(
final otherReactions = widget.message.latestReactions BuildContext context,
?.where( ) {
(element) => element.user.id != StreamChat.of(context).user.id) final ownId = StreamChat.of(context).user.id;
?.toList() ?? final reactionsMap = <String, Reaction>{};
[]; widget.message.latestReactions.forEach((element) {
if (!reactionsMap.containsKey(element.type) || element.user.id == ownId) {
reactionsMap[element.type] = element;
}
});
var rowChildren = [
if (widget.message.ownReactions?.isNotEmpty == true) ...[
Transform.translate(
offset: Offset(
widget.reverse ? -6 : 0,
0,
),
child: ReactionBubble(
key: ValueKey('${widget.message.id}.own'),
reverse: widget.reverse,
backgroundColor: widget.messageTheme.ownReactionsBackgroundColor,
borderColor: widget.messageTheme.ownReactionsBorderColor,
reactions: widget.message.ownReactions,
),
),
if (otherReactions.isEmpty)
Container(
width: 18,
),
],
if (otherReactions?.isNotEmpty == true) ...[
if (widget.message.ownReactions?.isEmpty == true)
Container(
width: 18,
),
Transform.translate(
offset: Offset(
widget.reverse ? 0 : -6,
0,
),
child: ReactionBubble(
key: ValueKey('${widget.message.id}.other'),
reactions: otherReactions,
reverse: widget.reverse,
flipTail: true,
backgroundColor: widget.messageTheme.otherReactionsBackgroundColor,
borderColor: widget.messageTheme.otherReactionsBorderColor,
),
),
]
];
if (widget.reverse) {
rowChildren = rowChildren.reversed.toList();
}
return AnimatedSwitcher( return AnimatedSwitcher(
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
child: (widget.showReactions && child: (widget.showReactions &&
(widget.message.reactionCounts?.isNotEmpty == true || (widget.message.reactionCounts?.isNotEmpty == true) &&
otherReactions.isNotEmpty) &&
!widget.message.isDeleted) !widget.message.isDeleted)
? Container( ? GestureDetector(
child: GestureDetector( onTap: () => _showMessageReactionsModalBottomSheet(context),
onTap: () => _showMessageReactionsModalBottomSheet(context), child: Transform.translate(
child: FractionallySizedBox( offset: Offset(-16, 0),
widthFactor: 0.5, child: ReactionBubble(
child: Row( key: ValueKey('${widget.message.id}.reactions'),
mainAxisAlignment: MainAxisAlignment.start, reverse: widget.reverse,
mainAxisSize: MainAxisSize.min, flipTail: widget.reverse,
children: rowChildren, backgroundColor: widget.messageTheme.reactionsBackgroundColor,
), borderColor: widget.messageTheme.reactionsBorderColor,
reactions: reactionsMap.values.toList(),
), ),
), ),
) )
+82 -50
View File
@@ -1,6 +1,8 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:stream_chat_flutter/src/reaction_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class ReactionBubble extends StatelessWidget { class ReactionBubble extends StatelessWidget {
@@ -25,60 +27,92 @@ class ReactionBubble extends StatelessWidget {
return Transform( return Transform(
transform: Matrix4.rotationY(reverse ? pi : 0), transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center, alignment: Alignment.center,
child: Column( child: Stack(
crossAxisAlignment: alignment: Alignment.center,
flipTail ? CrossAxisAlignment.start : CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Transform.translate(
padding: const EdgeInsets.all(4), offset: Offset(reverse ? 5 : -5, 0),
decoration: BoxDecoration( child: Container(
border: Border.all( padding: const EdgeInsets.symmetric(vertical: 6),
color: borderColor, decoration: BoxDecoration(
border: Border.all(
color: borderColor,
),
color: backgroundColor,
borderRadius: BorderRadius.all(Radius.circular(14)),
), ),
color: backgroundColor, child: LayoutBuilder(
borderRadius: BorderRadius.all(Radius.circular(14)), builder: (context, constraints) {
), return Flex(
child: Wrap( direction: Axis.horizontal,
spacing: 8, mainAxisSize: MainAxisSize.min,
children: [ children: [
...reactions.map((reaction) { if (constraints.maxWidth < double.infinity)
final reactionIcon = reactionIcons.firstWhere( ...reactions
(r) => r.type == reaction.type, .take((constraints.maxWidth) ~/ 22)
orElse: () => null, .map((reaction) {
return _buildReaction(
reactionIcons,
reaction,
context,
);
}).toList(),
if (constraints.maxWidth == double.infinity)
...reactions.map((reaction) {
return _buildReaction(
reactionIcons,
reaction,
context,
);
}).toList(),
],
); );
if (reactionIcon == null) { },
return Text( ),
'?',
style: TextStyle(
color: StreamChatTheme.of(context).accentColor,
),
);
}
return Icon(
reactionIcon.iconData,
size: 16,
color: StreamChatTheme.of(context).accentColor,
);
}).toList(),
],
), ),
), ),
_buildReactionsTail(context), Positioned(
bottom: 0,
left: reverse ? null : 16,
right: !reverse ? null : 16,
child: _buildReactionsTail(context),
),
], ],
), ),
); );
} }
Widget _buildReaction(
List<ReactionIcon> reactionIcons,
Reaction reaction,
BuildContext context,
) {
final reactionIcon = reactionIcons.firstWhere(
(r) => r.type == reaction.type,
orElse: () => null,
);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4.0,
),
child: Icon(
reactionIcon?.iconData ?? Icons.help_outline_rounded,
size: 16,
color: reaction.user.id == StreamChat.of(context).user.id
? StreamChatTheme.of(context).accentColor
: Colors.black.withOpacity(.5),
),
);
}
Widget _buildReactionsTail(BuildContext context) { Widget _buildReactionsTail(BuildContext context) {
final tail = Transform.translate( final tail = Transform.translate(
offset: Offset(reactions.length > 1 ? -9 : -9, 0), offset: Offset(-5, 0),
child: CustomPaint( child: CustomPaint(
painter: ReactionBubblePainter( painter: ReactionBubblePainter(
backgroundColor, backgroundColor,
borderColor, borderColor,
reactions.length,
), ),
), ),
); );
@@ -98,12 +132,10 @@ class ReactionBubble extends StatelessWidget {
class ReactionBubblePainter extends CustomPainter { class ReactionBubblePainter extends CustomPainter {
final Color color; final Color color;
final Color borderColor; final Color borderColor;
final int reactionsCount;
ReactionBubblePainter( ReactionBubblePainter(
this.color, this.color,
this.borderColor, this.borderColor,
this.reactionsCount,
); );
@override @override
@@ -126,7 +158,7 @@ class ReactionBubblePainter extends CustomPainter {
final path = Path(); final path = Path();
path.addOval( path.addOval(
Rect.fromCircle( Rect.fromCircle(
center: Offset(6, 2), center: Offset(4, 3),
radius: 2, radius: 2,
), ),
); );
@@ -140,7 +172,7 @@ class ReactionBubblePainter extends CustomPainter {
final path = Path(); final path = Path();
path.addOval(Rect.fromCircle( path.addOval(Rect.fromCircle(
center: Offset(6, 2), center: Offset(4, 3),
radius: 2, radius: 2,
)); ));
canvas.drawPath(path, paint); canvas.drawPath(path, paint);
@@ -152,13 +184,13 @@ class ReactionBubblePainter extends CustomPainter {
..strokeWidth = 1 ..strokeWidth = 1
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
final dy = reactionsCount > 1 ? -2.0 : -3.0; final dy = -2.2;
final startAngle = reactionsCount > 1 ? 1.08 : 1.16; final startAngle = 1.1;
final sweepAngle = reactionsCount > 1 ? 0.95 : 1.1; final sweepAngle = 1.2;
final path = Path(); final path = Path();
path.addArc( path.addArc(
Rect.fromCircle( Rect.fromCircle(
center: Offset(0, dy), center: Offset(1, dy),
radius: 4, radius: 4,
), ),
-pi * startAngle, -pi * startAngle,
@@ -172,13 +204,13 @@ class ReactionBubblePainter extends CustomPainter {
..color = color ..color = color
..strokeWidth = 1; ..strokeWidth = 1;
final dy = reactionsCount > 1 ? -2.0 : -3.0; final dy = -2.2;
final startAngle = reactionsCount > 1 ? 1 : 1.16; final startAngle = 1;
final sweepAngle = reactionsCount > 1 ? 1.2 : 1; final sweepAngle = 1.3;
final path = Path(); final path = Path();
path.addArc( path.addArc(
Rect.fromCircle( Rect.fromCircle(
center: Offset(0, dy), center: Offset(1, dy),
radius: 4, radius: 4,
), ),
-pi * startAngle, -pi * startAngle,
+3 -4
View File
@@ -26,7 +26,7 @@ class ReactionPicker extends StatelessWidget {
fit: StackFit.passthrough, fit: StackFit.passthrough,
children: [ children: [
Material( Material(
color: messageTheme.ownReactionsBackgroundColor, color: messageTheme.reactionsBackgroundColor,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -69,9 +69,8 @@ class ReactionPicker extends StatelessWidget {
bottom: 0, bottom: 0,
child: CustomPaint( child: CustomPaint(
painter: ReactionBubblePainter( painter: ReactionBubblePainter(
messageTheme.ownReactionsBackgroundColor, messageTheme.reactionsBackgroundColor,
messageTheme.ownReactionsBorderColor, messageTheme.reactionsBorderColor,
2,
), ),
), ),
), ),
+7 -16
View File
@@ -129,14 +129,9 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
constraints: themeData?.ownMessageTheme?.avatarTheme?.constraints, constraints: themeData?.ownMessageTheme?.avatarTheme?.constraints,
borderRadius: themeData?.ownMessageTheme?.avatarTheme?.borderRadius, borderRadius: themeData?.ownMessageTheme?.avatarTheme?.borderRadius,
), ),
otherReactionsBorderColor: reactionsBorderColor: themeData?.ownMessageTheme?.reactionsBorderColor,
themeData?.ownMessageTheme?.otherReactionsBorderColor, reactionsBackgroundColor:
otherReactionsBackgroundColor: themeData?.ownMessageTheme?.reactionsBackgroundColor,
themeData?.ownMessageTheme?.otherReactionsBackgroundColor,
ownReactionsBackgroundColor:
themeData?.ownMessageTheme?.ownReactionsBackgroundColor,
ownReactionsBorderColor:
themeData?.ownMessageTheme?.ownReactionsBorderColor,
), ),
otherMessageTheme: defaultTheme.otherMessageTheme.copyWith( otherMessageTheme: defaultTheme.otherMessageTheme.copyWith(
replies: themeData?.otherMessageTheme?.replies, replies: themeData?.otherMessageTheme?.replies,
@@ -150,14 +145,10 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
constraints: themeData?.otherMessageTheme?.avatarTheme?.constraints, constraints: themeData?.otherMessageTheme?.avatarTheme?.constraints,
borderRadius: themeData?.otherMessageTheme?.avatarTheme?.borderRadius, borderRadius: themeData?.otherMessageTheme?.avatarTheme?.borderRadius,
), ),
otherReactionsBorderColor: reactionsBorderColor:
themeData?.otherMessageTheme?.otherReactionsBorderColor, themeData?.otherMessageTheme?.reactionsBorderColor,
otherReactionsBackgroundColor: reactionsBackgroundColor:
themeData?.otherMessageTheme?.otherReactionsBackgroundColor, themeData?.otherMessageTheme?.reactionsBackgroundColor,
ownReactionsBackgroundColor:
themeData?.otherMessageTheme?.ownReactionsBackgroundColor,
ownReactionsBorderColor:
themeData?.otherMessageTheme?.ownReactionsBorderColor,
), ),
accentColor: themeData?.accentColor, accentColor: themeData?.accentColor,
secondaryColor: themeData?.secondaryColor, secondaryColor: themeData?.secondaryColor,
+13 -31
View File
@@ -302,12 +302,8 @@ class StreamChatThemeData {
fontSize: 12, fontSize: 12,
), ),
messageBackgroundColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA), messageBackgroundColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
ownReactionsBackgroundColor: reactionsBackgroundColor: isDark ? Colors.black : Colors.white,
isDark ? Color(0xff191919) : Color(0xffEAEAEA), reactionsBorderColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
ownReactionsBorderColor: isDark ? Colors.black : Colors.white,
otherReactionsBackgroundColor: isDark ? Colors.black : Colors.white,
otherReactionsBorderColor:
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
avatarTheme: AvatarTheme( avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
constraints: BoxConstraints.tightFor( constraints: BoxConstraints.tightFor(
@@ -320,12 +316,9 @@ class StreamChatThemeData {
), ),
), ),
otherMessageTheme: MessageTheme( otherMessageTheme: MessageTheme(
ownReactionsBackgroundColor: reactionsBackgroundColor:
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
ownReactionsBorderColor: isDark ? Colors.black : Colors.white,
otherReactionsBackgroundColor: isDark ? Colors.black : Colors.white,
otherReactionsBorderColor:
isDark ? Color(0xff191919) : Color(0xffEAEAEA), isDark ? Color(0xff191919) : Color(0xffEAEAEA),
reactionsBorderColor: isDark ? Colors.black : Colors.white,
messageText: TextStyle( messageText: TextStyle(
fontSize: 15, fontSize: 15,
color: isDark ? Colors.white : Colors.black, color: isDark ? Colors.white : Colors.black,
@@ -457,10 +450,8 @@ class MessageTheme {
final TextStyle createdAt; final TextStyle createdAt;
final TextStyle replies; final TextStyle replies;
final Color messageBackgroundColor; final Color messageBackgroundColor;
final Color ownReactionsBackgroundColor; final Color reactionsBackgroundColor;
final Color ownReactionsBorderColor; final Color reactionsBorderColor;
final Color otherReactionsBackgroundColor;
final Color otherReactionsBorderColor;
final AvatarTheme avatarTheme; final AvatarTheme avatarTheme;
const MessageTheme({ const MessageTheme({
@@ -469,10 +460,8 @@ class MessageTheme {
this.messageAuthor, this.messageAuthor,
this.messageLinks, this.messageLinks,
this.messageBackgroundColor, this.messageBackgroundColor,
this.ownReactionsBackgroundColor, this.reactionsBackgroundColor,
this.ownReactionsBorderColor, this.reactionsBorderColor,
this.otherReactionsBackgroundColor,
this.otherReactionsBorderColor,
this.avatarTheme, this.avatarTheme,
this.createdAt, this.createdAt,
}); });
@@ -485,10 +474,8 @@ class MessageTheme {
TextStyle replies, TextStyle replies,
Color messageBackgroundColor, Color messageBackgroundColor,
AvatarTheme avatarTheme, AvatarTheme avatarTheme,
Color ownReactionsBackgroundColor, Color reactionsBackgroundColor,
Color ownReactionsBorderColor, Color reactionsBorderColor,
Color otherReactionsBackgroundColor,
Color otherReactionsBorderColor,
}) => }) =>
MessageTheme( MessageTheme(
messageText: messageText ?? this.messageText, messageText: messageText ?? this.messageText,
@@ -499,14 +486,9 @@ class MessageTheme {
messageBackgroundColor ?? this.messageBackgroundColor, messageBackgroundColor ?? this.messageBackgroundColor,
avatarTheme: avatarTheme ?? this.avatarTheme, avatarTheme: avatarTheme ?? this.avatarTheme,
replies: replies ?? this.replies, replies: replies ?? this.replies,
ownReactionsBackgroundColor: reactionsBackgroundColor:
ownReactionsBackgroundColor ?? this.ownReactionsBackgroundColor, reactionsBackgroundColor ?? this.reactionsBackgroundColor,
ownReactionsBorderColor: reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
ownReactionsBorderColor ?? this.ownReactionsBorderColor,
otherReactionsBackgroundColor:
otherReactionsBackgroundColor ?? this.otherReactionsBackgroundColor,
otherReactionsBorderColor:
otherReactionsBorderColor ?? this.otherReactionsBorderColor,
); );
} }