Merge pull request #114 from GetStream/feature/reactions
Feature/reactions
This commit is contained in:
@@ -76,7 +76,7 @@ void main() async {
|
||||
|
||||
await client.setUser(
|
||||
User(id: 'super-band-9', extraData: {
|
||||
'name': 'John Doe',
|
||||
'name': 'Jonathan Doe',
|
||||
}),
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
|
||||
);
|
||||
|
||||
@@ -43,88 +43,102 @@ class MessageActionsModal extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
final ownId = StreamChat.of(context).user.id;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 10,
|
||||
sigmaY: 10,
|
||||
sigmaX: 10.8731,
|
||||
sigmaY: 10.8731,
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (showReactions &&
|
||||
(message.status == MessageSendingStatus.SENT ||
|
||||
message.status == null))
|
||||
Center(
|
||||
child: ReactionPicker(
|
||||
message: message,
|
||||
messageTheme: messageTheme,
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
child: MessageWidget(
|
||||
key: Key('MessageWidget'),
|
||||
reverse: reverse,
|
||||
message: message.text.length > 200
|
||||
? message.copyWith(
|
||||
text: '${message.text.substring(0, 200)}...')
|
||||
: message,
|
||||
messageTheme: messageTheme,
|
||||
showReactions: false,
|
||||
showUsername: false,
|
||||
showReplyIndicator: false,
|
||||
showTimestamp: false,
|
||||
showSendingIndicator: DisplayWidget.gone,
|
||||
shape: messageShape,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 48.0,
|
||||
),
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: [
|
||||
if (showReply &&
|
||||
(message.status == MessageSendingStatus.SENT ||
|
||||
message.status == null) &&
|
||||
message.parentId == null)
|
||||
_buildReplyButton(context),
|
||||
if (showEditMessage) _buildEditMessage(context),
|
||||
if (showDeleteMessage) _buildDeleteButton(context),
|
||||
if (showCopyMessage) _buildCopyButton(context),
|
||||
],
|
||||
).toList(),
|
||||
children: <Widget>[
|
||||
if (showReactions &&
|
||||
(message.status == MessageSendingStatus.SENT ||
|
||||
message.status == null))
|
||||
Center(
|
||||
child: ReactionPicker(
|
||||
message: message,
|
||||
messageTheme: messageTheme,
|
||||
),
|
||||
),
|
||||
IgnorePointer(
|
||||
child: MessageWidget(
|
||||
key: Key('MessageWidget'),
|
||||
reverse: reverse,
|
||||
message: message.copyWith(
|
||||
text: message.text.length > 200
|
||||
? '${message.text.substring(0, 200)}...'
|
||||
: message.text,
|
||||
),
|
||||
messageTheme: messageTheme,
|
||||
showReactions: false,
|
||||
showUsername: false,
|
||||
showReplyIndicator: false,
|
||||
showUserAvatar: message.user.id == ownId
|
||||
? DisplayWidget.gone
|
||||
: DisplayWidget.show,
|
||||
showTimestamp: false,
|
||||
showSendingIndicator: DisplayWidget.gone,
|
||||
shape: messageShape,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 48.0,
|
||||
),
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: [
|
||||
if (showReply &&
|
||||
(message.status ==
|
||||
MessageSendingStatus.SENT ||
|
||||
message.status == null) &&
|
||||
message.parentId == null)
|
||||
_buildReplyButton(context),
|
||||
if (showEditMessage) _buildEditMessage(context),
|
||||
if (showDeleteMessage)
|
||||
_buildDeleteButton(context),
|
||||
if (showCopyMessage) _buildCopyButton(context),
|
||||
],
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,66 +34,75 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
final ownId = StreamChat.of(context).user.id;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 10,
|
||||
sigmaY: 10,
|
||||
sigmaX: 10.8731,
|
||||
sigmaY: 10.8731,
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (showReactions &&
|
||||
(message.status == MessageSendingStatus.SENT ||
|
||||
message.status == null))
|
||||
Center(
|
||||
child: ReactionPicker(
|
||||
message: message,
|
||||
messageTheme: messageTheme,
|
||||
Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (showReactions &&
|
||||
(message.status == MessageSendingStatus.SENT ||
|
||||
message.status == null))
|
||||
Center(
|
||||
child: ReactionPicker(
|
||||
message: message,
|
||||
messageTheme: messageTheme,
|
||||
),
|
||||
),
|
||||
IgnorePointer(
|
||||
child: MessageWidget(
|
||||
key: Key('MessageWidget'),
|
||||
reverse: reverse,
|
||||
message: message.copyWith(
|
||||
text: message.text.length > 200
|
||||
? '${message.text.substring(0, 200)}...'
|
||||
: message.text,
|
||||
),
|
||||
messageTheme: messageTheme,
|
||||
showReactions: false,
|
||||
showUsername: false,
|
||||
showUserAvatar: message.user.id == ownId
|
||||
? DisplayWidget.gone
|
||||
: DisplayWidget.show,
|
||||
showReplyIndicator: false,
|
||||
showTimestamp: false,
|
||||
showSendingIndicator: DisplayWidget.gone,
|
||||
shape: messageShape,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (message.latestReactions?.isNotEmpty == true)
|
||||
_buildReactionCard(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
child: MessageWidget(
|
||||
key: Key('MessageWidget'),
|
||||
reverse: reverse,
|
||||
message: message.text.length > 200
|
||||
? message.copyWith(
|
||||
text: '${message.text.substring(0, 200)}...')
|
||||
: message,
|
||||
messageTheme: messageTheme,
|
||||
showReactions: false,
|
||||
showUsername: false,
|
||||
showReplyIndicator: false,
|
||||
showTimestamp: false,
|
||||
showSendingIndicator: DisplayWidget.gone,
|
||||
shape: messageShape,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (message.latestReactions?.isNotEmpty == true)
|
||||
Container(
|
||||
constraints: BoxConstraints.loose(Size.fromHeight(400)),
|
||||
child: _buildReactionCard(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,30 +128,25 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16.0,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
),
|
||||
child: GridView.builder(
|
||||
shrinkWrap: true,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
crossAxisSpacing: 16,
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisSpacing: 22,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 18,
|
||||
right: 18,
|
||||
bottom: 26,
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 22,
|
||||
alignment: WrapAlignment.start,
|
||||
children: message.latestReactions
|
||||
.map((e) => _buildReaction(
|
||||
e,
|
||||
currentUser,
|
||||
context,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
itemCount: message.latestReactions.length,
|
||||
itemBuilder: (context, i) {
|
||||
final reaction = message.latestReactions[i];
|
||||
|
||||
return _buildReaction(
|
||||
reaction,
|
||||
currentUser,
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -152,51 +156,56 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Column _buildReaction(
|
||||
Widget _buildReaction(
|
||||
Reaction reaction,
|
||||
User currentUser,
|
||||
BuildContext context,
|
||||
) {
|
||||
final isCurrentUser = reaction.user.id == currentUser.id;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
UserAvatar(
|
||||
onTap: onUserAvatarTap,
|
||||
user: reaction.user,
|
||||
constraints: BoxConstraints.tightFor(
|
||||
height: 64,
|
||||
width: 64,
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints.loose(Size(
|
||||
64,
|
||||
98,
|
||||
)),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
UserAvatar(
|
||||
onTap: onUserAvatarTap,
|
||||
user: reaction.user,
|
||||
constraints: BoxConstraints.tightFor(
|
||||
height: 64,
|
||||
width: 64,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
Positioned(
|
||||
child: ReactionBubble(
|
||||
reactions: [reaction],
|
||||
borderColor: isCurrentUser
|
||||
? messageTheme.ownReactionsBorderColor
|
||||
: messageTheme.otherReactionsBorderColor,
|
||||
backgroundColor: isCurrentUser
|
||||
? messageTheme.ownReactionsBackgroundColor
|
||||
: messageTheme.otherReactionsBackgroundColor,
|
||||
flipTail: !isCurrentUser,
|
||||
Positioned(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: ReactionBubble(
|
||||
reactions: [reaction],
|
||||
borderColor: messageTheme.reactionsBorderColor,
|
||||
backgroundColor: messageTheme.reactionsBackgroundColor,
|
||||
highlightOwnReactions: false,
|
||||
),
|
||||
),
|
||||
bottom: 4,
|
||||
left: isCurrentUser ? 0 : null,
|
||||
right: isCurrentUser ? 0 : null,
|
||||
),
|
||||
bottom: 0,
|
||||
left: isCurrentUser ? 0 : null,
|
||||
right: isCurrentUser ? 0 : null,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
reaction.user.name,
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
Text(
|
||||
reaction.user.name,
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+43
-81
@@ -239,20 +239,24 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
8,
|
||||
),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: widget.showReactions
|
||||
? EdgeInsets.only(
|
||||
top: widget.message.reactionCounts
|
||||
?.isNotEmpty ==
|
||||
true
|
||||
? 12
|
||||
: 0,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
child: PortalEntry(
|
||||
portalAnchor: Alignment(-0.81, 0),
|
||||
childAnchor: Alignment.topRight,
|
||||
portal: _buildReactionIndicator(context),
|
||||
child: PortalEntry(
|
||||
portal: Container(
|
||||
transform: Matrix4.translationValues(-16, 2, 0),
|
||||
child: _buildReactionIndicator(context),
|
||||
constraints: BoxConstraints(maxWidth: 22 * 6.0),
|
||||
),
|
||||
portalAnchor: Alignment(-1.0, -1.0),
|
||||
childAnchor: Alignment(1, -1.0),
|
||||
child: Padding(
|
||||
padding: widget.showReactions
|
||||
? EdgeInsets.only(
|
||||
top: widget.message.reactionCounts
|
||||
?.isNotEmpty ==
|
||||
true
|
||||
? 18
|
||||
: 0,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
child: (widget.message.isDeleted &&
|
||||
widget.message.status !=
|
||||
MessageSendingStatus.FAILED_DELETE)
|
||||
@@ -378,75 +382,33 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildReactionIndicator(BuildContext context) {
|
||||
final otherReactions = widget.message.latestReactions
|
||||
?.where(
|
||||
(element) => element.user.id != StreamChat.of(context).user.id)
|
||||
?.toList() ??
|
||||
[];
|
||||
Widget _buildReactionIndicator(
|
||||
BuildContext context,
|
||||
) {
|
||||
final ownId = StreamChat.of(context).user.id;
|
||||
final reactionsMap = <String, Reaction>{};
|
||||
widget.message.latestReactions?.forEach((element) {
|
||||
if (!reactionsMap.containsKey(element.type) || element.user.id == ownId) {
|
||||
reactionsMap[element.type] = element;
|
||||
}
|
||||
});
|
||||
final reactionsList = reactionsMap.values.toList()
|
||||
..sort((a, b) => a.user.id == ownId ? 1 : -1);
|
||||
|
||||
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(
|
||||
duration: Duration(milliseconds: 300),
|
||||
child: (widget.showReactions &&
|
||||
(widget.message.reactionCounts?.isNotEmpty == true ||
|
||||
otherReactions.isNotEmpty) &&
|
||||
(widget.message.reactionCounts?.isNotEmpty == true) &&
|
||||
!widget.message.isDeleted)
|
||||
? Container(
|
||||
child: GestureDetector(
|
||||
onTap: () => _showMessageReactionsModalBottomSheet(context),
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: rowChildren,
|
||||
),
|
||||
),
|
||||
? GestureDetector(
|
||||
onTap: () => _showMessageReactionsModalBottomSheet(context),
|
||||
child: ReactionBubble(
|
||||
key: ValueKey('${widget.message.id}.reactions'),
|
||||
reverse: widget.reverse,
|
||||
flipTail: widget.reverse,
|
||||
backgroundColor: widget.messageTheme.reactionsBackgroundColor,
|
||||
borderColor: widget.messageTheme.reactionsBorderColor,
|
||||
reactions: reactionsList,
|
||||
),
|
||||
)
|
||||
: SizedBox(),
|
||||
@@ -498,8 +460,8 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
});
|
||||
}
|
||||
|
||||
ContinuousRectangleBorder _getDefaultShape(BuildContext context) {
|
||||
return ContinuousRectangleBorder(
|
||||
ShapeBorder _getDefaultShape(BuildContext context) {
|
||||
return RoundedRectangleBorder(
|
||||
side: widget.attachmentBorderSide ??
|
||||
widget.borderSide ??
|
||||
BorderSide(
|
||||
@@ -718,7 +680,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
onLongPress: () => onLongPress(context),
|
||||
child: Material(
|
||||
shape: widget.shape ??
|
||||
ContinuousRectangleBorder(
|
||||
RoundedRectangleBorder(
|
||||
side: widget.borderSide ??
|
||||
BorderSide(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:math';
|
||||
|
||||
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';
|
||||
|
||||
class ReactionBubble extends StatelessWidget {
|
||||
@@ -11,6 +13,7 @@ class ReactionBubble extends StatelessWidget {
|
||||
@required this.backgroundColor,
|
||||
this.reverse = false,
|
||||
this.flipTail = false,
|
||||
this.highlightOwnReactions = true,
|
||||
}) : super(key: key);
|
||||
|
||||
final List<Reaction> reactions;
|
||||
@@ -18,67 +21,100 @@ class ReactionBubble extends StatelessWidget {
|
||||
final Color backgroundColor;
|
||||
final bool reverse;
|
||||
final bool flipTail;
|
||||
final bool highlightOwnReactions;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final reactionIcons = StreamChatTheme.of(context).reactionIcons;
|
||||
final offset = reactions.length > 1 ? 16.0 : 2.0;
|
||||
return Transform(
|
||||
transform: Matrix4.rotationY(reverse ? pi : 0),
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
flipTail ? CrossAxisAlignment.start : CrossAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
Transform.translate(
|
||||
offset: Offset(reverse ? offset : -offset, 0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
),
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(14)),
|
||||
),
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(14)),
|
||||
),
|
||||
child: Wrap(
|
||||
children: [
|
||||
...reactions.map((reaction) {
|
||||
final reactionIcon = reactionIcons.firstWhere(
|
||||
(r) => r.type == reaction.type,
|
||||
orElse: () => null,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (constraints.maxWidth < double.infinity)
|
||||
...reactions
|
||||
.take((constraints.maxWidth) ~/ 22)
|
||||
.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 : 11,
|
||||
right: !reverse ? null : 11,
|
||||
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: (!highlightOwnReactions ||
|
||||
reaction.user.id == StreamChat.of(context).user.id)
|
||||
? StreamChatTheme.of(context).accentColor
|
||||
: Colors.black.withOpacity(.5),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildReactionsTail(BuildContext context) {
|
||||
final tail = Transform.translate(
|
||||
offset: Offset(reactions.length > 1 ? -9 : -9, 0),
|
||||
child: CustomPaint(
|
||||
painter: ReactionBubblePainter(
|
||||
backgroundColor,
|
||||
borderColor,
|
||||
reactions.length,
|
||||
),
|
||||
final tail = CustomPaint(
|
||||
painter: ReactionBubblePainter(
|
||||
backgroundColor,
|
||||
borderColor,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -97,12 +133,10 @@ class ReactionBubble extends StatelessWidget {
|
||||
class ReactionBubblePainter extends CustomPainter {
|
||||
final Color color;
|
||||
final Color borderColor;
|
||||
final int reactionsCount;
|
||||
|
||||
ReactionBubblePainter(
|
||||
this.color,
|
||||
this.borderColor,
|
||||
this.reactionsCount,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -125,7 +159,7 @@ class ReactionBubblePainter extends CustomPainter {
|
||||
final path = Path();
|
||||
path.addOval(
|
||||
Rect.fromCircle(
|
||||
center: Offset(6, 2),
|
||||
center: Offset(4, 3),
|
||||
radius: 2,
|
||||
),
|
||||
);
|
||||
@@ -139,7 +173,7 @@ class ReactionBubblePainter extends CustomPainter {
|
||||
|
||||
final path = Path();
|
||||
path.addOval(Rect.fromCircle(
|
||||
center: Offset(6, 2),
|
||||
center: Offset(4, 3),
|
||||
radius: 2,
|
||||
));
|
||||
canvas.drawPath(path, paint);
|
||||
@@ -151,13 +185,13 @@ class ReactionBubblePainter extends CustomPainter {
|
||||
..strokeWidth = 1
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final dy = reactionsCount > 1 ? -2.0 : -3.0;
|
||||
final startAngle = reactionsCount > 1 ? 1.08 : 1.16;
|
||||
final sweepAngle = reactionsCount > 1 ? 0.95 : 1.1;
|
||||
final dy = -2.2;
|
||||
final startAngle = 1.1;
|
||||
final sweepAngle = 1.2;
|
||||
final path = Path();
|
||||
path.addArc(
|
||||
Rect.fromCircle(
|
||||
center: Offset(0, dy),
|
||||
center: Offset(1, dy),
|
||||
radius: 4,
|
||||
),
|
||||
-pi * startAngle,
|
||||
@@ -171,13 +205,13 @@ class ReactionBubblePainter extends CustomPainter {
|
||||
..color = color
|
||||
..strokeWidth = 1;
|
||||
|
||||
final dy = reactionsCount > 1 ? -2.0 : -3.0;
|
||||
final startAngle = reactionsCount > 1 ? 1 : 1.16;
|
||||
final sweepAngle = reactionsCount > 1 ? 1.2 : 1;
|
||||
final dy = -2.2;
|
||||
final startAngle = 1;
|
||||
final sweepAngle = 1.3;
|
||||
final path = Path();
|
||||
path.addArc(
|
||||
Rect.fromCircle(
|
||||
center: Offset(0, dy),
|
||||
center: Offset(1, dy),
|
||||
radius: 4,
|
||||
),
|
||||
-pi * startAngle,
|
||||
|
||||
@@ -26,7 +26,7 @@ class ReactionPicker extends StatelessWidget {
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
Material(
|
||||
color: messageTheme.ownReactionsBackgroundColor,
|
||||
color: messageTheme.reactionsBackgroundColor,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
@@ -45,7 +45,7 @@ class ReactionPicker extends StatelessWidget {
|
||||
reactionIcon.iconData,
|
||||
color: ownReactionIndex != -1
|
||||
? StreamChatTheme.of(context).accentColor
|
||||
: Theme.of(context).iconTheme.color,
|
||||
: Theme.of(context).iconTheme.color.withOpacity(.5),
|
||||
),
|
||||
onPressed: () {
|
||||
if (ownReactionIndex != -1) {
|
||||
@@ -69,9 +69,8 @@ class ReactionPicker extends StatelessWidget {
|
||||
bottom: 0,
|
||||
child: CustomPaint(
|
||||
painter: ReactionBubblePainter(
|
||||
messageTheme.ownReactionsBackgroundColor,
|
||||
messageTheme.ownReactionsBorderColor,
|
||||
2,
|
||||
messageTheme.reactionsBackgroundColor,
|
||||
messageTheme.reactionsBorderColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -129,14 +129,9 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
|
||||
constraints: themeData?.ownMessageTheme?.avatarTheme?.constraints,
|
||||
borderRadius: themeData?.ownMessageTheme?.avatarTheme?.borderRadius,
|
||||
),
|
||||
otherReactionsBorderColor:
|
||||
themeData?.ownMessageTheme?.otherReactionsBorderColor,
|
||||
otherReactionsBackgroundColor:
|
||||
themeData?.ownMessageTheme?.otherReactionsBackgroundColor,
|
||||
ownReactionsBackgroundColor:
|
||||
themeData?.ownMessageTheme?.ownReactionsBackgroundColor,
|
||||
ownReactionsBorderColor:
|
||||
themeData?.ownMessageTheme?.ownReactionsBorderColor,
|
||||
reactionsBorderColor: themeData?.ownMessageTheme?.reactionsBorderColor,
|
||||
reactionsBackgroundColor:
|
||||
themeData?.ownMessageTheme?.reactionsBackgroundColor,
|
||||
),
|
||||
otherMessageTheme: defaultTheme.otherMessageTheme.copyWith(
|
||||
replies: themeData?.otherMessageTheme?.replies,
|
||||
@@ -150,14 +145,10 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
|
||||
constraints: themeData?.otherMessageTheme?.avatarTheme?.constraints,
|
||||
borderRadius: themeData?.otherMessageTheme?.avatarTheme?.borderRadius,
|
||||
),
|
||||
otherReactionsBorderColor:
|
||||
themeData?.otherMessageTheme?.otherReactionsBorderColor,
|
||||
otherReactionsBackgroundColor:
|
||||
themeData?.otherMessageTheme?.otherReactionsBackgroundColor,
|
||||
ownReactionsBackgroundColor:
|
||||
themeData?.otherMessageTheme?.ownReactionsBackgroundColor,
|
||||
ownReactionsBorderColor:
|
||||
themeData?.otherMessageTheme?.ownReactionsBorderColor,
|
||||
reactionsBorderColor:
|
||||
themeData?.otherMessageTheme?.reactionsBorderColor,
|
||||
reactionsBackgroundColor:
|
||||
themeData?.otherMessageTheme?.reactionsBackgroundColor,
|
||||
),
|
||||
accentColor: themeData?.accentColor,
|
||||
secondaryColor: themeData?.secondaryColor,
|
||||
|
||||
@@ -220,8 +220,8 @@ class StreamChatThemeData {
|
||||
return StreamChatThemeData(
|
||||
accentColor: accentColor,
|
||||
primaryColor: isDark ? Colors.black : Colors.white,
|
||||
primaryIconTheme:
|
||||
IconThemeData(color: isDark ? Colors.white : Colors.black),
|
||||
primaryIconTheme: IconThemeData(
|
||||
color: isDark ? Colors.white : Colors.black.withOpacity(.5)),
|
||||
defaultChannelImage: (context, channel) => SizedBox(),
|
||||
backgroundColor: isDark ? Colors.black : Colors.white,
|
||||
defaultUserImage: (context, user) => Center(
|
||||
@@ -287,7 +287,7 @@ class StreamChatThemeData {
|
||||
),
|
||||
ownMessageTheme: MessageTheme(
|
||||
messageText: TextStyle(
|
||||
fontSize: 15,
|
||||
fontSize: 14.5,
|
||||
color: isDark ? Colors.white : Colors.black,
|
||||
),
|
||||
createdAt: TextStyle(
|
||||
@@ -302,12 +302,8 @@ class StreamChatThemeData {
|
||||
fontSize: 12,
|
||||
),
|
||||
messageBackgroundColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
||||
ownReactionsBackgroundColor:
|
||||
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
||||
ownReactionsBorderColor: isDark ? Colors.black : Colors.white,
|
||||
otherReactionsBackgroundColor: isDark ? Colors.black : Colors.white,
|
||||
otherReactionsBorderColor:
|
||||
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
||||
reactionsBackgroundColor: isDark ? Colors.black : Colors.white,
|
||||
reactionsBorderColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
||||
avatarTheme: AvatarTheme(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: BoxConstraints.tightFor(
|
||||
@@ -320,14 +316,11 @@ class StreamChatThemeData {
|
||||
),
|
||||
),
|
||||
otherMessageTheme: MessageTheme(
|
||||
ownReactionsBackgroundColor:
|
||||
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
||||
ownReactionsBorderColor: isDark ? Colors.black : Colors.white,
|
||||
otherReactionsBackgroundColor: isDark ? Colors.black : Colors.white,
|
||||
otherReactionsBorderColor:
|
||||
reactionsBackgroundColor:
|
||||
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
||||
reactionsBorderColor: isDark ? Colors.black : Colors.white,
|
||||
messageText: TextStyle(
|
||||
fontSize: 15,
|
||||
fontSize: 14.5,
|
||||
color: isDark ? Colors.white : Colors.black,
|
||||
),
|
||||
createdAt: TextStyle(
|
||||
@@ -457,10 +450,8 @@ class MessageTheme {
|
||||
final TextStyle createdAt;
|
||||
final TextStyle replies;
|
||||
final Color messageBackgroundColor;
|
||||
final Color ownReactionsBackgroundColor;
|
||||
final Color ownReactionsBorderColor;
|
||||
final Color otherReactionsBackgroundColor;
|
||||
final Color otherReactionsBorderColor;
|
||||
final Color reactionsBackgroundColor;
|
||||
final Color reactionsBorderColor;
|
||||
final AvatarTheme avatarTheme;
|
||||
|
||||
const MessageTheme({
|
||||
@@ -469,10 +460,8 @@ class MessageTheme {
|
||||
this.messageAuthor,
|
||||
this.messageLinks,
|
||||
this.messageBackgroundColor,
|
||||
this.ownReactionsBackgroundColor,
|
||||
this.ownReactionsBorderColor,
|
||||
this.otherReactionsBackgroundColor,
|
||||
this.otherReactionsBorderColor,
|
||||
this.reactionsBackgroundColor,
|
||||
this.reactionsBorderColor,
|
||||
this.avatarTheme,
|
||||
this.createdAt,
|
||||
});
|
||||
@@ -485,10 +474,8 @@ class MessageTheme {
|
||||
TextStyle replies,
|
||||
Color messageBackgroundColor,
|
||||
AvatarTheme avatarTheme,
|
||||
Color ownReactionsBackgroundColor,
|
||||
Color ownReactionsBorderColor,
|
||||
Color otherReactionsBackgroundColor,
|
||||
Color otherReactionsBorderColor,
|
||||
Color reactionsBackgroundColor,
|
||||
Color reactionsBorderColor,
|
||||
}) =>
|
||||
MessageTheme(
|
||||
messageText: messageText ?? this.messageText,
|
||||
@@ -499,14 +486,9 @@ class MessageTheme {
|
||||
messageBackgroundColor ?? this.messageBackgroundColor,
|
||||
avatarTheme: avatarTheme ?? this.avatarTheme,
|
||||
replies: replies ?? this.replies,
|
||||
ownReactionsBackgroundColor:
|
||||
ownReactionsBackgroundColor ?? this.ownReactionsBackgroundColor,
|
||||
ownReactionsBorderColor:
|
||||
ownReactionsBorderColor ?? this.ownReactionsBorderColor,
|
||||
otherReactionsBackgroundColor:
|
||||
otherReactionsBackgroundColor ?? this.otherReactionsBackgroundColor,
|
||||
otherReactionsBorderColor:
|
||||
otherReactionsBorderColor ?? this.otherReactionsBorderColor,
|
||||
reactionsBackgroundColor:
|
||||
reactionsBackgroundColor ?? this.reactionsBackgroundColor,
|
||||
reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user