Merge pull request #105 from GetStream/feature/reactions
Feature/reactions
This commit is contained in:
@@ -1 +1 @@
|
|||||||
06fe8d0d3d89937a6d33b1033e75ae96
|
0289cdadbd29bab804f275e3c5907d07
|
||||||
@@ -75,7 +75,9 @@ void main() async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await client.setUser(
|
await client.setUser(
|
||||||
User(id: 'super-band-9'),
|
User(id: 'super-band-9', extraData: {
|
||||||
|
'name': 'John Doe',
|
||||||
|
}),
|
||||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
|
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,6 +1,8 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_channel.dart';
|
import 'package:stream_chat_flutter/src/stream_channel.dart';
|
||||||
@@ -18,6 +20,7 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
final MessageTheme messageTheme;
|
final MessageTheme messageTheme;
|
||||||
final bool showReactions;
|
final bool showReactions;
|
||||||
final bool showDeleteMessage;
|
final bool showDeleteMessage;
|
||||||
|
final bool showCopyMessage;
|
||||||
final bool showEditMessage;
|
final bool showEditMessage;
|
||||||
final bool showReply;
|
final bool showReply;
|
||||||
final bool reverse;
|
final bool reverse;
|
||||||
@@ -27,19 +30,19 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
Key key,
|
Key key,
|
||||||
@required this.message,
|
@required this.message,
|
||||||
@required this.messageTheme,
|
@required this.messageTheme,
|
||||||
this.showReactions,
|
this.showReactions = true,
|
||||||
this.showDeleteMessage,
|
this.showDeleteMessage = true,
|
||||||
this.showEditMessage,
|
this.showEditMessage = true,
|
||||||
this.onThreadTap,
|
this.onThreadTap,
|
||||||
this.showReply,
|
this.showCopyMessage = true,
|
||||||
|
this.showReply = true,
|
||||||
this.editMessageInputBuilder,
|
this.editMessageInputBuilder,
|
||||||
this.messageShape,
|
this.messageShape,
|
||||||
this.reverse,
|
this.reverse = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final channel = StreamChannel.of(context).channel;
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
@@ -68,13 +71,13 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
message.status == null))
|
message.status == null))
|
||||||
Center(
|
Center(
|
||||||
child: ReactionPicker(
|
child: ReactionPicker(
|
||||||
channel: channel,
|
|
||||||
message: message,
|
message: message,
|
||||||
messageTheme: messageTheme,
|
messageTheme: messageTheme,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AbsorbPointer(
|
AbsorbPointer(
|
||||||
child: MessageWidget(
|
child: MessageWidget(
|
||||||
|
key: Key('MessageWidget'),
|
||||||
reverse: reverse,
|
reverse: reverse,
|
||||||
message: message,
|
message: message,
|
||||||
messageTheme: messageTheme,
|
messageTheme: messageTheme,
|
||||||
@@ -103,13 +106,14 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
children: ListTile.divideTiles(
|
children: ListTile.divideTiles(
|
||||||
context: context,
|
context: context,
|
||||||
tiles: [
|
tiles: [
|
||||||
if (showEditMessage) _buildEditMessage(context),
|
|
||||||
if (showReply &&
|
if (showReply &&
|
||||||
(message.status == MessageSendingStatus.SENT ||
|
(message.status == MessageSendingStatus.SENT ||
|
||||||
message.status == null) &&
|
message.status == null) &&
|
||||||
message.parentId == null)
|
message.parentId == null)
|
||||||
_buildReplyButton(context),
|
_buildReplyButton(context),
|
||||||
|
if (showEditMessage) _buildEditMessage(context),
|
||||||
if (showDeleteMessage) _buildDeleteButton(context),
|
if (showDeleteMessage) _buildDeleteButton(context),
|
||||||
|
if (showCopyMessage) _buildCopyButton(context),
|
||||||
],
|
],
|
||||||
).toList(),
|
).toList(),
|
||||||
),
|
),
|
||||||
@@ -142,6 +146,23 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCopyButton(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
'Copy message',
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
|
leading: Icon(
|
||||||
|
StreamIcons.copy,
|
||||||
|
color: StreamChatTheme.of(context).primaryIconTheme.color,
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
await Clipboard.setData(ClipboardData(text: message.text));
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildEditMessage(BuildContext context) {
|
Widget _buildEditMessage(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
@@ -256,7 +277,7 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
style: Theme.of(context).textTheme.headline6,
|
style: Theme.of(context).textTheme.headline6,
|
||||||
),
|
),
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
StreamIcons.Thread_Reply,
|
StreamIcons.sorting_up,
|
||||||
color: StreamChatTheme.of(context).primaryIconTheme.color,
|
color: StreamChatTheme.of(context).primaryIconTheme.color,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/stream_chat.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
||||||
|
|
||||||
|
import 'message_widget.dart';
|
||||||
|
import 'stream_chat_theme.dart';
|
||||||
|
|
||||||
|
class MessageReactionsModal extends StatelessWidget {
|
||||||
|
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
||||||
|
final void Function(Message) onThreadTap;
|
||||||
|
final Message message;
|
||||||
|
final MessageTheme messageTheme;
|
||||||
|
final bool reverse;
|
||||||
|
final bool showReactions;
|
||||||
|
final ShapeBorder messageShape;
|
||||||
|
final void Function(User) onUserAvatarTap;
|
||||||
|
|
||||||
|
const MessageReactionsModal({
|
||||||
|
Key key,
|
||||||
|
@required this.message,
|
||||||
|
@required this.messageTheme,
|
||||||
|
this.showReactions = true,
|
||||||
|
this.onThreadTap,
|
||||||
|
this.editMessageInputBuilder,
|
||||||
|
this.messageShape,
|
||||||
|
this.reverse = false,
|
||||||
|
this.onUserAvatarTap,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(
|
||||||
|
sigmaX: 10,
|
||||||
|
sigmaY: 10,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Padding _buildReactionCard(BuildContext context) {
|
||||||
|
final currentUser = StreamChat.of(context).user;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
),
|
||||||
|
child: Card(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
'Message Reactions',
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
itemCount: message.latestReactions.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final reaction = message.latestReactions[i];
|
||||||
|
|
||||||
|
return _buildReaction(
|
||||||
|
reaction,
|
||||||
|
currentUser,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Column _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,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
child: ReactionBubble(
|
||||||
|
reactions: [reaction],
|
||||||
|
borderColor: isCurrentUser
|
||||||
|
? messageTheme.ownReactionsBorderColor
|
||||||
|
: messageTheme.otherReactionsBorderColor,
|
||||||
|
backgroundColor: isCurrentUser
|
||||||
|
? messageTheme.ownReactionsBackgroundColor
|
||||||
|
: messageTheme.otherReactionsBackgroundColor,
|
||||||
|
flipTail: !isCurrentUser,
|
||||||
|
),
|
||||||
|
bottom: 0,
|
||||||
|
left: isCurrentUser ? 0 : null,
|
||||||
|
right: isCurrentUser ? 0 : null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
reaction.user.name,
|
||||||
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:flutter_portal/flutter_portal.dart';
|
import 'package:flutter_portal/flutter_portal.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_actions_modal.dart';
|
import 'package:stream_chat_flutter/src/message_actions_modal.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
|
||||||
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
@@ -244,7 +245,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
top: widget.message.reactionCounts
|
top: widget.message.reactionCounts
|
||||||
?.isNotEmpty ==
|
?.isNotEmpty ==
|
||||||
true
|
true
|
||||||
? 16
|
? 12
|
||||||
: 0,
|
: 0,
|
||||||
)
|
)
|
||||||
: EdgeInsets.zero,
|
: EdgeInsets.zero,
|
||||||
@@ -437,7 +438,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
!widget.message.isDeleted)
|
!widget.message.isDeleted)
|
||||||
? Container(
|
? Container(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => onLongPress(context),
|
onTap: () => _showMessageReactionsModalBottomSheet(context),
|
||||||
child: FractionallySizedBox(
|
child: FractionallySizedBox(
|
||||||
widthFactor: 0.5,
|
widthFactor: 0.5,
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -452,7 +453,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showMessageModalBottomSheet(BuildContext context) {
|
void _showMessageActionModalBottomSheet(BuildContext context) {
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -476,6 +477,27 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showMessageReactionsModalBottomSheet(BuildContext context) {
|
||||||
|
final channel = StreamChannel.of(context).channel;
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: MessageReactionsModal(
|
||||||
|
onUserAvatarTap: widget.onUserAvatarTap,
|
||||||
|
messageTheme: widget.messageTheme,
|
||||||
|
messageShape: widget.shape ?? _getDefaultShape(context),
|
||||||
|
reverse: widget.reverse,
|
||||||
|
message: widget.message,
|
||||||
|
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||||
|
onThreadTap: widget.onThreadTap,
|
||||||
|
showReactions: widget.showReactions,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ContinuousRectangleBorder _getDefaultShape(BuildContext context) {
|
ContinuousRectangleBorder _getDefaultShape(BuildContext context) {
|
||||||
return ContinuousRectangleBorder(
|
return ContinuousRectangleBorder(
|
||||||
side: widget.attachmentBorderSide ??
|
side: widget.attachmentBorderSide ??
|
||||||
@@ -493,8 +515,9 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
|
|
||||||
List<Widget> _parseAttachments(BuildContext context) {
|
List<Widget> _parseAttachments(BuildContext context) {
|
||||||
final images = widget.message.attachments
|
final images = widget.message.attachments
|
||||||
.where((element) => element.type == 'image')
|
?.where((element) => element.type == 'image')
|
||||||
.toList();
|
?.toList() ??
|
||||||
|
[];
|
||||||
|
|
||||||
if (images.length > 1) {
|
if (images.length > 1) {
|
||||||
return [
|
return [
|
||||||
@@ -581,7 +604,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
if (widget.onMessageActions != null) {
|
if (widget.onMessageActions != null) {
|
||||||
widget.onMessageActions(context, widget.message);
|
widget.onMessageActions(context, widget.message);
|
||||||
} else {
|
} else {
|
||||||
_showMessageModalBottomSheet(context);
|
_showMessageActionModalBottomSheet(context);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,13 @@ class ReactionBubble extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final reactionAssets = StreamChatTheme.of(context).reactionIcons;
|
final reactionIcons = StreamChatTheme.of(context).reactionIcons;
|
||||||
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: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment:
|
||||||
|
flipTail ? CrossAxisAlignment.start : CrossAxisAlignment.end,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
@@ -41,11 +42,11 @@ class ReactionBubble extends StatelessWidget {
|
|||||||
child: Wrap(
|
child: Wrap(
|
||||||
children: [
|
children: [
|
||||||
...reactions.map((reaction) {
|
...reactions.map((reaction) {
|
||||||
final reactionAsset = reactionAssets.firstWhere(
|
final reactionIcon = reactionIcons.firstWhere(
|
||||||
(reactionAsset) => reactionAsset.type == reaction.type,
|
(r) => r.type == reaction.type,
|
||||||
orElse: () => null,
|
orElse: () => null,
|
||||||
);
|
);
|
||||||
if (reactionAsset == null) {
|
if (reactionIcon == null) {
|
||||||
return Text(
|
return Text(
|
||||||
'?',
|
'?',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -55,7 +56,7 @@ class ReactionBubble extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Icon(
|
return Icon(
|
||||||
reactionAsset.iconData,
|
reactionIcon.iconData,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
color: StreamChatTheme.of(context).accentColor,
|
||||||
);
|
);
|
||||||
@@ -71,7 +72,7 @@ class ReactionBubble extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildReactionsTail(BuildContext context) {
|
Widget _buildReactionsTail(BuildContext context) {
|
||||||
final tail = Transform.translate(
|
final tail = Transform.translate(
|
||||||
offset: Offset(4, 0),
|
offset: Offset(reactions.length > 1 ? -9 : -9, 0),
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: ReactionBubblePainter(
|
painter: ReactionBubblePainter(
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
@@ -152,7 +153,7 @@ class ReactionBubblePainter extends CustomPainter {
|
|||||||
|
|
||||||
final dy = reactionsCount > 1 ? -2.0 : -3.0;
|
final dy = reactionsCount > 1 ? -2.0 : -3.0;
|
||||||
final startAngle = reactionsCount > 1 ? 1.08 : 1.16;
|
final startAngle = reactionsCount > 1 ? 1.08 : 1.16;
|
||||||
final sweepAngle = reactionsCount > 1 ? 1.18 : 1.1;
|
final sweepAngle = reactionsCount > 1 ? 0.95 : 1.1;
|
||||||
final path = Path();
|
final path = Path();
|
||||||
path.addArc(
|
path.addArc(
|
||||||
Rect.fromCircle(
|
Rect.fromCircle(
|
||||||
@@ -171,7 +172,8 @@ class ReactionBubblePainter extends CustomPainter {
|
|||||||
..strokeWidth = 1;
|
..strokeWidth = 1;
|
||||||
|
|
||||||
final dy = reactionsCount > 1 ? -2.0 : -3.0;
|
final dy = reactionsCount > 1 ? -2.0 : -3.0;
|
||||||
final startAngle = reactionsCount > 1 ? 1.05 : 1.16;
|
final startAngle = reactionsCount > 1 ? 1 : 1.16;
|
||||||
|
final sweepAngle = reactionsCount > 1 ? 1.2 : 1;
|
||||||
final path = Path();
|
final path = Path();
|
||||||
path.addArc(
|
path.addArc(
|
||||||
Rect.fromCircle(
|
Rect.fromCircle(
|
||||||
@@ -179,7 +181,7 @@ class ReactionBubblePainter extends CustomPainter {
|
|||||||
radius: 4,
|
radius: 4,
|
||||||
),
|
),
|
||||||
-pi * startAngle,
|
-pi * startAngle,
|
||||||
-pi,
|
-pi * sweepAngle,
|
||||||
);
|
);
|
||||||
canvas.drawPath(path, paint);
|
canvas.drawPath(path, paint);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
|
|
||||||
@@ -12,67 +13,81 @@ class ReactionPicker extends StatelessWidget {
|
|||||||
const ReactionPicker({
|
const ReactionPicker({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.message,
|
@required this.message,
|
||||||
@required this.channel,
|
|
||||||
@required this.messageTheme,
|
@required this.messageTheme,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Message message;
|
final Message message;
|
||||||
final MessageTheme messageTheme;
|
final MessageTheme messageTheme;
|
||||||
final Channel channel;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final reactionAssets = StreamChatTheme.of(context).reactionIcons;
|
final reactionIcons = StreamChatTheme.of(context).reactionIcons;
|
||||||
return Material(
|
return Stack(
|
||||||
color: messageTheme.ownReactionsBackgroundColor,
|
fit: StackFit.passthrough,
|
||||||
clipBehavior: Clip.hardEdge,
|
children: [
|
||||||
shape: RoundedRectangleBorder(
|
Material(
|
||||||
borderRadius: BorderRadius.circular(24),
|
color: messageTheme.ownReactionsBackgroundColor,
|
||||||
),
|
clipBehavior: Clip.hardEdge,
|
||||||
child: Row(
|
shape: RoundedRectangleBorder(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
borderRadius: BorderRadius.circular(24),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Row(
|
||||||
children: reactionAssets.map((reactionIcon) {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
final ownReactionIndex = message.ownReactions?.indexWhere(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
(reaction) => reaction.type == reactionIcon.type) ??
|
mainAxisSize: MainAxisSize.min,
|
||||||
-1;
|
children: reactionIcons.map((reactionIcon) {
|
||||||
return IconButton(
|
final ownReactionIndex = message.ownReactions?.indexWhere(
|
||||||
iconSize: 24,
|
(reaction) => reaction.type == reactionIcon.type) ??
|
||||||
icon: Icon(
|
-1;
|
||||||
reactionIcon.iconData,
|
return IconButton(
|
||||||
color: ownReactionIndex != -1
|
iconSize: 24,
|
||||||
? StreamChatTheme.of(context).accentColor
|
icon: Icon(
|
||||||
: Theme.of(context).iconTheme.color,
|
reactionIcon.iconData,
|
||||||
|
color: ownReactionIndex != -1
|
||||||
|
? StreamChatTheme.of(context).accentColor
|
||||||
|
: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (ownReactionIndex != -1) {
|
||||||
|
removeReaction(
|
||||||
|
context,
|
||||||
|
message.ownReactions[ownReactionIndex],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sendReaction(
|
||||||
|
context,
|
||||||
|
reactionIcon.type,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 14,
|
||||||
|
bottom: 0,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: ReactionBubblePainter(
|
||||||
|
messageTheme.ownReactionsBackgroundColor,
|
||||||
|
messageTheme.ownReactionsBorderColor,
|
||||||
|
2,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
),
|
||||||
if (ownReactionIndex != -1) {
|
),
|
||||||
removeReaction(
|
],
|
||||||
context,
|
|
||||||
message.ownReactions[ownReactionIndex],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
sendReaction(
|
|
||||||
context,
|
|
||||||
reactionIcon.type,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a reaction to the message
|
/// Add a reaction to the message
|
||||||
void sendReaction(BuildContext context, String reactionType) {
|
void sendReaction(BuildContext context, String reactionType) {
|
||||||
channel.sendReaction(message, reactionType);
|
StreamChannel.of(context).channel.sendReaction(message, reactionType);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a reaction from the message
|
/// Remove a reaction from the message
|
||||||
void removeReaction(BuildContext context, Reaction reaction) {
|
void removeReaction(BuildContext context, Reaction reaction) {
|
||||||
channel.deleteReaction(message, reaction);
|
StreamChannel.of(context).channel.deleteReaction(message, reaction);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class StreamChatThemeData {
|
|||||||
Widget Function(BuildContext, Channel) defaultChannelImage,
|
Widget Function(BuildContext, Channel) defaultChannelImage,
|
||||||
Widget Function(BuildContext, User) defaultUserImage,
|
Widget Function(BuildContext, User) defaultUserImage,
|
||||||
IconThemeData primaryIconTheme,
|
IconThemeData primaryIconTheme,
|
||||||
List<ReactionIcon> reactionAssets,
|
List<ReactionIcon> reactionIcons,
|
||||||
}) =>
|
}) =>
|
||||||
StreamChatThemeData(
|
StreamChatThemeData(
|
||||||
primaryColor: primaryColor ?? this.primaryColor,
|
primaryColor: primaryColor ?? this.primaryColor,
|
||||||
@@ -211,7 +211,7 @@ class StreamChatThemeData {
|
|||||||
this.otherMessageTheme.avatarTheme,
|
this.otherMessageTheme.avatarTheme,
|
||||||
) ??
|
) ??
|
||||||
this.otherMessageTheme,
|
this.otherMessageTheme,
|
||||||
reactionIcons: reactionAssets ?? this.reactionIcons,
|
reactionIcons: reactionIcons ?? this.reactionIcons,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Get the default Stream Chat theme
|
/// Get the default Stream Chat theme
|
||||||
@@ -365,7 +365,7 @@ class StreamChatThemeData {
|
|||||||
),
|
),
|
||||||
ReactionIcon(
|
ReactionIcon(
|
||||||
type: 'thumbs_down',
|
type: 'thumbs_down',
|
||||||
iconData: StreamIcons.thumbs_up_reaction_1,
|
iconData: StreamIcons.thumbs_down_reaction,
|
||||||
),
|
),
|
||||||
ReactionIcon(
|
ReactionIcon(
|
||||||
type: 'lol',
|
type: 'lol',
|
||||||
|
|||||||
+60
-65
@@ -461,32 +461,32 @@ class StreamIcons {
|
|||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData grey600 = IconData(
|
static const IconData grid = IconData(
|
||||||
0xe95b,
|
0xe95b,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData grid = IconData(
|
static const IconData group = IconData(
|
||||||
0xe95c,
|
0xe95c,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData heart_outline_add = IconData(
|
static const IconData heart_1 = IconData(
|
||||||
0xe95d,
|
0xe95d,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData heart_outline_delete = IconData(
|
static const IconData heart_outline_add = IconData(
|
||||||
0xe95e,
|
0xe95e,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData heart_pill = IconData(
|
static const IconData heart_outline_delete = IconData(
|
||||||
0xe95f,
|
0xe95f,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData heart_1 = IconData(
|
static const IconData heart_pill = IconData(
|
||||||
0xe960,
|
0xe960,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
@@ -521,127 +521,127 @@ class StreamIcons {
|
|||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData hourglass_full_1 = IconData(
|
static const IconData hourglass_full = IconData(
|
||||||
0xe967,
|
0xe967,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData hourglass_full = IconData(
|
static const IconData hourglass = IconData(
|
||||||
0xe968,
|
0xe968,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData hourglass = IconData(
|
static const IconData insurance_outline = IconData(
|
||||||
0xe969,
|
0xe969,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData insurance_outline = IconData(
|
static const IconData insurance = IconData(
|
||||||
0xe96a,
|
0xe96a,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData insurance = IconData(
|
static const IconData left_right = IconData(
|
||||||
0xe96b,
|
0xe96b,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData left_right = IconData(
|
static const IconData left = IconData(
|
||||||
0xe96c,
|
0xe96c,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData left = IconData(
|
static const IconData light = IconData(
|
||||||
0xe96d,
|
0xe96d,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData light = IconData(
|
static const IconData lightning = IconData(
|
||||||
0xe96e,
|
0xe96e,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData lightning = IconData(
|
static const IconData link = IconData(
|
||||||
0xe96f,
|
0xe96f,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData link = IconData(
|
static const IconData location_map = IconData(
|
||||||
0xe970,
|
0xe970,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData location_map = IconData(
|
static const IconData location = IconData(
|
||||||
0xe971,
|
0xe971,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData location = IconData(
|
static const IconData LOL_reaction = IconData(
|
||||||
0xe972,
|
0xe972,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData LOL_reaction = IconData(
|
static const IconData love_reaction = IconData(
|
||||||
0xe973,
|
0xe973,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData love_reaction = IconData(
|
static const IconData love = IconData(
|
||||||
0xe974,
|
0xe974,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData love = IconData(
|
static const IconData lungs = IconData(
|
||||||
0xe975,
|
0xe975,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData lungs = IconData(
|
static const IconData mail_open = IconData(
|
||||||
0xe976,
|
0xe976,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData mail_open = IconData(
|
static const IconData mail = IconData(
|
||||||
0xe977,
|
0xe977,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData mail = IconData(
|
static const IconData male = IconData(
|
||||||
0xe978,
|
0xe978,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData male = IconData(
|
static const IconData medical_bed = IconData(
|
||||||
0xe979,
|
0xe979,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData medical_bed = IconData(
|
static const IconData medical_blank = IconData(
|
||||||
0xe97a,
|
0xe97a,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData medical_blank = IconData(
|
static const IconData medical_card_add = IconData(
|
||||||
0xe97b,
|
0xe97b,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData medical_card_add = IconData(
|
static const IconData medical_card = IconData(
|
||||||
0xe97c,
|
0xe97c,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData medical_card = IconData(
|
static const IconData medical_cross = IconData(
|
||||||
0xe97d,
|
0xe97d,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData medical_cross = IconData(
|
static const IconData medical_load = IconData(
|
||||||
0xe97e,
|
0xe97e,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData medical_load = IconData(
|
static const IconData menu_point_1 = IconData(
|
||||||
0xe97f,
|
0xe97f,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
@@ -651,22 +651,22 @@ class StreamIcons {
|
|||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData menu_point_1 = IconData(
|
static const IconData menu_point = IconData(
|
||||||
0xe981,
|
0xe981,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData menu_point = IconData(
|
static const IconData menu = IconData(
|
||||||
0xe982,
|
0xe982,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData menu = IconData(
|
static const IconData message = IconData(
|
||||||
0xe983,
|
0xe983,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData message = IconData(
|
static const IconData mic_1 = IconData(
|
||||||
0xe984,
|
0xe984,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
@@ -886,12 +886,12 @@ class StreamIcons {
|
|||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData save_check = IconData(
|
static const IconData save_1 = IconData(
|
||||||
0xe9b0,
|
0xe9b0,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData save_1 = IconData(
|
static const IconData save_check = IconData(
|
||||||
0xe9b1,
|
0xe9b1,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
@@ -1061,17 +1061,17 @@ class StreamIcons {
|
|||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData Thread_Reply = IconData(
|
static const IconData thumbs_down_reaction = IconData(
|
||||||
0xe9d3,
|
0xe9d3,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData thumbs_up_reaction_1 = IconData(
|
static const IconData thumbs_up_reaction = IconData(
|
||||||
0xe9d4,
|
0xe9d4,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData thumbs_up_reaction = IconData(
|
static const IconData time_1 = IconData(
|
||||||
0xe9d5,
|
0xe9d5,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
@@ -1081,109 +1081,104 @@ class StreamIcons {
|
|||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData time_1 = IconData(
|
static const IconData time = IconData(
|
||||||
0xe9d7,
|
0xe9d7,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData time = IconData(
|
static const IconData tooth = IconData(
|
||||||
0xe9d8,
|
0xe9d8,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData tooth = IconData(
|
static const IconData tube_full = IconData(
|
||||||
0xe9d9,
|
0xe9d9,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData tube_full = IconData(
|
static const IconData tv_check = IconData(
|
||||||
0xe9da,
|
0xe9da,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData tv_check = IconData(
|
static const IconData tv_rating = IconData(
|
||||||
0xe9db,
|
0xe9db,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData tv_rating = IconData(
|
static const IconData unfold_less_h = IconData(
|
||||||
0xe9dc,
|
0xe9dc,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData unfold_less_h = IconData(
|
static const IconData unfold_less = IconData(
|
||||||
0xe9dd,
|
0xe9dd,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData unfold_less = IconData(
|
static const IconData unfold_more_h = IconData(
|
||||||
0xe9de,
|
0xe9de,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData unfold_more_h = IconData(
|
static const IconData unfold_more = IconData(
|
||||||
0xe9df,
|
0xe9df,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData unfold_more = IconData(
|
static const IconData unread = IconData(
|
||||||
0xe9e0,
|
0xe9e0,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData unread = IconData(
|
static const IconData up_down = IconData(
|
||||||
0xe9e1,
|
0xe9e1,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData up_down = IconData(
|
static const IconData up = IconData(
|
||||||
0xe9e2,
|
0xe9e2,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData up = IconData(
|
static const IconData upload = IconData(
|
||||||
0xe9e3,
|
0xe9e3,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData upload = IconData(
|
static const IconData user_add = IconData(
|
||||||
0xe9e4,
|
0xe9e4,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData user_add = IconData(
|
static const IconData user_delete = IconData(
|
||||||
0xe9e5,
|
0xe9e5,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData user_delete = IconData(
|
static const IconData user_minus = IconData(
|
||||||
0xe9e6,
|
0xe9e6,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData user_minus = IconData(
|
static const IconData user = IconData(
|
||||||
0xe9e7,
|
0xe9e7,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData user = IconData(
|
static const IconData voice = IconData(
|
||||||
0xe9e8,
|
0xe9e8,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData voice = IconData(
|
static const IconData world = IconData(
|
||||||
0xe9e9,
|
0xe9e9,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData world = IconData(
|
static const IconData wut_reaction = IconData(
|
||||||
0xe9ea,
|
0xe9ea,
|
||||||
fontFamily: _fontFamily,
|
fontFamily: _fontFamily,
|
||||||
fontPackage: _fontPackage,
|
fontPackage: _fontPackage,
|
||||||
);
|
);
|
||||||
static const IconData wut_reaction = IconData(
|
|
||||||
0xe9eb,
|
|
||||||
fontFamily: _fontFamily,
|
|
||||||
fontPackage: _fontPackage,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class UserAvatar extends StatelessWidget {
|
|||||||
: StreamChatTheme.of(context).defaultUserImage(context, user),
|
: StreamChatTheme.of(context).defaultUserImage(context, user),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showOnlineStatus && user.online)
|
if (showOnlineStatus && user.online == true)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ dependencies:
|
|||||||
http_parser: ^3.1.4
|
http_parser: ^3.1.4
|
||||||
flutter_slidable: ^0.5.4
|
flutter_slidable: ^0.5.4
|
||||||
carousel_slider: ^2.2.1
|
carousel_slider: ^2.2.1
|
||||||
|
clipboard: ^0.1.2+8
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
fonts:
|
fonts:
|
||||||
|
|||||||
@@ -3,13 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
class MockClient extends Mock implements Client {}
|
import 'mocks.dart';
|
||||||
|
|
||||||
class MockClientState extends Mock implements ClientState {}
|
|
||||||
|
|
||||||
class MockChannel extends Mock implements Channel {}
|
|
||||||
|
|
||||||
class MockChannelState extends Mock implements ChannelClientState {}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets(
|
testWidgets(
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_actions_modal.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'mocks.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets(
|
||||||
|
'it should show the all actions',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final client = MockClient();
|
||||||
|
final clientState = MockClientState();
|
||||||
|
|
||||||
|
when(client.state).thenReturn(clientState);
|
||||||
|
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
|
||||||
|
|
||||||
|
final themeData = ThemeData();
|
||||||
|
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: themeData,
|
||||||
|
home: StreamChat(
|
||||||
|
streamChatThemeData: streamTheme,
|
||||||
|
client: client,
|
||||||
|
child: Container(
|
||||||
|
child: MessageActionsModal(
|
||||||
|
message: Message(
|
||||||
|
text: 'test',
|
||||||
|
user: User(
|
||||||
|
id: 'user-id',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
messageTheme: streamTheme.ownMessageTheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
|
||||||
|
expect(find.byIcon(StreamIcons.sorting_up), findsOneWidget);
|
||||||
|
expect(find.byIcon(StreamIcons.edit), findsOneWidget);
|
||||||
|
expect(find.byIcon(StreamIcons.delete), findsOneWidget);
|
||||||
|
expect(find.byIcon(StreamIcons.copy), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
testWidgets(
|
||||||
|
'it should show some actions',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final client = MockClient();
|
||||||
|
final clientState = MockClientState();
|
||||||
|
|
||||||
|
when(client.state).thenReturn(clientState);
|
||||||
|
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
|
||||||
|
|
||||||
|
final themeData = ThemeData();
|
||||||
|
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: themeData,
|
||||||
|
home: StreamChat(
|
||||||
|
streamChatThemeData: streamTheme,
|
||||||
|
client: client,
|
||||||
|
child: Container(
|
||||||
|
child: MessageActionsModal(
|
||||||
|
showEditMessage: false,
|
||||||
|
showCopyMessage: false,
|
||||||
|
showDeleteMessage: false,
|
||||||
|
showReply: false,
|
||||||
|
message: Message(
|
||||||
|
text: 'test',
|
||||||
|
user: User(
|
||||||
|
id: 'user-id',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
messageTheme: streamTheme.ownMessageTheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
|
||||||
|
expect(find.byIcon(StreamIcons.sorting_up), findsNothing);
|
||||||
|
expect(find.byIcon(StreamIcons.edit), findsNothing);
|
||||||
|
expect(find.byIcon(StreamIcons.delete), findsNothing);
|
||||||
|
expect(find.byIcon(StreamIcons.copy), findsNothing);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'mocks.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets(
|
||||||
|
'it should show one thumbs from the picker',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final themeData = ThemeData();
|
||||||
|
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: themeData,
|
||||||
|
home: StreamChatTheme(
|
||||||
|
data: streamTheme,
|
||||||
|
child: MessageReactionsModal(
|
||||||
|
message: Message(
|
||||||
|
id: 'test',
|
||||||
|
text: 'test message',
|
||||||
|
user: User(
|
||||||
|
id: 'test-user',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
messageTheme: streamTheme.ownMessageTheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
|
||||||
|
expect(find.byIcon(StreamIcons.thumbs_up_reaction), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'it should show two reactions',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final client = MockClient();
|
||||||
|
final clientState = MockClientState();
|
||||||
|
|
||||||
|
when(client.state).thenReturn(clientState);
|
||||||
|
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
|
||||||
|
|
||||||
|
final themeData = ThemeData();
|
||||||
|
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
|
||||||
|
final testUserId = 'test user';
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: themeData,
|
||||||
|
home: StreamChat(
|
||||||
|
streamChatThemeData: streamTheme,
|
||||||
|
client: client,
|
||||||
|
child: MessageReactionsModal(
|
||||||
|
message: Message(
|
||||||
|
text: 'test message',
|
||||||
|
user: User(
|
||||||
|
id: 'test-user',
|
||||||
|
),
|
||||||
|
latestReactions: [
|
||||||
|
Reaction(
|
||||||
|
type: 'thumbs_up',
|
||||||
|
user: User(id: testUserId),
|
||||||
|
),
|
||||||
|
Reaction(
|
||||||
|
type: 'love',
|
||||||
|
user: User(id: testUserId),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
messageTheme: streamTheme.ownMessageTheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
|
||||||
|
expect(find.byIcon(StreamIcons.thumbs_up_reaction), findsNWidgets(2));
|
||||||
|
expect(find.byIcon(StreamIcons.love_reaction), findsNWidgets(2));
|
||||||
|
expect(find.text(testUserId), findsNWidgets(2));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
|
class MockClient extends Mock implements Client {}
|
||||||
|
|
||||||
|
class MockClientState extends Mock implements ClientState {}
|
||||||
|
|
||||||
|
class MockChannel extends Mock implements Channel {}
|
||||||
|
|
||||||
|
class MockChannelState extends Mock implements ChannelClientState {}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets(
|
||||||
|
'it should show no reactions',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: StreamChatTheme(
|
||||||
|
data: StreamChatThemeData(),
|
||||||
|
child: Container(
|
||||||
|
child: ReactionBubble(
|
||||||
|
reactions: [],
|
||||||
|
borderColor: Colors.black,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(StreamIcons.thumbs_up_reaction), findsNothing);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'it should show a thumb up',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final themeData = ThemeData();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: themeData,
|
||||||
|
home: StreamChatTheme(
|
||||||
|
data: StreamChatThemeData.getDefaultTheme(themeData),
|
||||||
|
child: Container(
|
||||||
|
child: ReactionBubble(
|
||||||
|
reactions: [
|
||||||
|
Reaction(
|
||||||
|
type: 'thumbs_up',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
borderColor: Colors.black,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(StreamIcons.thumbs_up_reaction), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
testWidgets(
|
||||||
|
'it should show two reactions',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final themeData = ThemeData();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: themeData,
|
||||||
|
home: StreamChatTheme(
|
||||||
|
data: StreamChatThemeData.getDefaultTheme(themeData),
|
||||||
|
child: Container(
|
||||||
|
child: ReactionBubble(
|
||||||
|
reactions: [
|
||||||
|
Reaction(
|
||||||
|
type: 'thumbs_up',
|
||||||
|
),
|
||||||
|
Reaction(
|
||||||
|
type: 'love',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
borderColor: Colors.black,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(StreamIcons.thumbs_up_reaction), findsOneWidget);
|
||||||
|
expect(find.byIcon(StreamIcons.love_reaction), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user