Merge pull request #105 from GetStream/feature/reactions

Feature/reactions
This commit is contained in:
Salvatore Giordano
2020-10-19 15:18:23 +02:00
committed by GitHub
17 changed files with 676 additions and 145 deletions
+1 -1
View File
@@ -1 +1 @@
06fe8d0d3d89937a6d33b1033e75ae96
0289cdadbd29bab804f275e3c5907d07
+3 -1
View File
@@ -75,7 +75,9 @@ void main() async {
);
await client.setUser(
User(id: 'super-band-9'),
User(id: 'super-band-9', extraData: {
'name': 'John Doe',
}),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
);
Binary file not shown.
+30 -9
View File
@@ -1,6 +1,8 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_channel.dart';
@@ -18,6 +20,7 @@ class MessageActionsModal extends StatelessWidget {
final MessageTheme messageTheme;
final bool showReactions;
final bool showDeleteMessage;
final bool showCopyMessage;
final bool showEditMessage;
final bool showReply;
final bool reverse;
@@ -27,19 +30,19 @@ class MessageActionsModal extends StatelessWidget {
Key key,
@required this.message,
@required this.messageTheme,
this.showReactions,
this.showDeleteMessage,
this.showEditMessage,
this.showReactions = true,
this.showDeleteMessage = true,
this.showEditMessage = true,
this.onThreadTap,
this.showReply,
this.showCopyMessage = true,
this.showReply = true,
this.editMessageInputBuilder,
this.messageShape,
this.reverse,
this.reverse = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel;
return Stack(
children: [
Positioned.fill(
@@ -68,13 +71,13 @@ class MessageActionsModal extends StatelessWidget {
message.status == null))
Center(
child: ReactionPicker(
channel: channel,
message: message,
messageTheme: messageTheme,
),
),
AbsorbPointer(
child: MessageWidget(
key: Key('MessageWidget'),
reverse: reverse,
message: message,
messageTheme: messageTheme,
@@ -103,13 +106,14 @@ class MessageActionsModal extends StatelessWidget {
children: ListTile.divideTiles(
context: context,
tiles: [
if (showEditMessage) _buildEditMessage(context),
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(),
),
@@ -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) {
return ListTile(
title: Text(
@@ -256,7 +277,7 @@ class MessageActionsModal extends StatelessWidget {
style: Theme.of(context).textTheme.headline6,
),
leading: Icon(
StreamIcons.Thread_Reply,
StreamIcons.sorting_up,
color: StreamChatTheme.of(context).primaryIconTheme.color,
),
onTap: () {
+199
View File
@@ -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,
),
],
);
}
}
+29 -6
View File
@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:jiffy/jiffy.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/stream_chat_flutter.dart';
@@ -244,7 +245,7 @@ class _MessageWidgetState extends State<MessageWidget> {
top: widget.message.reactionCounts
?.isNotEmpty ==
true
? 16
? 12
: 0,
)
: EdgeInsets.zero,
@@ -437,7 +438,7 @@ class _MessageWidgetState extends State<MessageWidget> {
!widget.message.isDeleted)
? Container(
child: GestureDetector(
onTap: () => onLongPress(context),
onTap: () => _showMessageReactionsModalBottomSheet(context),
child: FractionallySizedBox(
widthFactor: 0.5,
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;
showDialog(
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) {
return ContinuousRectangleBorder(
side: widget.attachmentBorderSide ??
@@ -493,8 +515,9 @@ class _MessageWidgetState extends State<MessageWidget> {
List<Widget> _parseAttachments(BuildContext context) {
final images = widget.message.attachments
.where((element) => element.type == 'image')
.toList();
?.where((element) => element.type == 'image')
?.toList() ??
[];
if (images.length > 1) {
return [
@@ -581,7 +604,7 @@ class _MessageWidgetState extends State<MessageWidget> {
if (widget.onMessageActions != null) {
widget.onMessageActions(context, widget.message);
} else {
_showMessageModalBottomSheet(context);
_showMessageActionModalBottomSheet(context);
}
return;
}
+12 -10
View File
@@ -21,12 +21,13 @@ class ReactionBubble extends StatelessWidget {
@override
Widget build(BuildContext context) {
final reactionAssets = StreamChatTheme.of(context).reactionIcons;
final reactionIcons = StreamChatTheme.of(context).reactionIcons;
return Transform(
transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment:
flipTail ? CrossAxisAlignment.start : CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Container(
@@ -41,11 +42,11 @@ class ReactionBubble extends StatelessWidget {
child: Wrap(
children: [
...reactions.map((reaction) {
final reactionAsset = reactionAssets.firstWhere(
(reactionAsset) => reactionAsset.type == reaction.type,
final reactionIcon = reactionIcons.firstWhere(
(r) => r.type == reaction.type,
orElse: () => null,
);
if (reactionAsset == null) {
if (reactionIcon == null) {
return Text(
'?',
style: TextStyle(
@@ -55,7 +56,7 @@ class ReactionBubble extends StatelessWidget {
}
return Icon(
reactionAsset.iconData,
reactionIcon.iconData,
size: 16,
color: StreamChatTheme.of(context).accentColor,
);
@@ -71,7 +72,7 @@ class ReactionBubble extends StatelessWidget {
Widget _buildReactionsTail(BuildContext context) {
final tail = Transform.translate(
offset: Offset(4, 0),
offset: Offset(reactions.length > 1 ? -9 : -9, 0),
child: CustomPaint(
painter: ReactionBubblePainter(
backgroundColor,
@@ -152,7 +153,7 @@ class ReactionBubblePainter extends CustomPainter {
final dy = reactionsCount > 1 ? -2.0 : -3.0;
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();
path.addArc(
Rect.fromCircle(
@@ -171,7 +172,8 @@ class ReactionBubblePainter extends CustomPainter {
..strokeWidth = 1;
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();
path.addArc(
Rect.fromCircle(
@@ -179,7 +181,7 @@ class ReactionBubblePainter extends CustomPainter {
radius: 4,
),
-pi * startAngle,
-pi,
-pi * sweepAngle,
);
canvas.drawPath(path, paint);
}
+57 -42
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import '../stream_chat_flutter.dart';
@@ -12,67 +13,81 @@ class ReactionPicker extends StatelessWidget {
const ReactionPicker({
Key key,
@required this.message,
@required this.channel,
@required this.messageTheme,
}) : super(key: key);
final Message message;
final MessageTheme messageTheme;
final Channel channel;
@override
Widget build(BuildContext context) {
final reactionAssets = StreamChatTheme.of(context).reactionIcons;
return Material(
color: messageTheme.ownReactionsBackgroundColor,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: reactionAssets.map((reactionIcon) {
final ownReactionIndex = message.ownReactions?.indexWhere(
(reaction) => reaction.type == reactionIcon.type) ??
-1;
return IconButton(
iconSize: 24,
icon: Icon(
reactionIcon.iconData,
color: ownReactionIndex != -1
? StreamChatTheme.of(context).accentColor
: Theme.of(context).iconTheme.color,
final reactionIcons = StreamChatTheme.of(context).reactionIcons;
return Stack(
fit: StackFit.passthrough,
children: [
Material(
color: messageTheme.ownReactionsBackgroundColor,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: reactionIcons.map((reactionIcon) {
final ownReactionIndex = message.ownReactions?.indexWhere(
(reaction) => reaction.type == reactionIcon.type) ??
-1;
return IconButton(
iconSize: 24,
icon: Icon(
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
void sendReaction(BuildContext context, String reactionType) {
channel.sendReaction(message, reactionType);
StreamChannel.of(context).channel.sendReaction(message, reactionType);
Navigator.of(context).pop();
}
/// Remove a reaction from the message
void removeReaction(BuildContext context, Reaction reaction) {
channel.deleteReaction(message, reaction);
StreamChannel.of(context).channel.deleteReaction(message, reaction);
Navigator.of(context).pop();
}
}
+3 -3
View File
@@ -142,7 +142,7 @@ class StreamChatThemeData {
Widget Function(BuildContext, Channel) defaultChannelImage,
Widget Function(BuildContext, User) defaultUserImage,
IconThemeData primaryIconTheme,
List<ReactionIcon> reactionAssets,
List<ReactionIcon> reactionIcons,
}) =>
StreamChatThemeData(
primaryColor: primaryColor ?? this.primaryColor,
@@ -211,7 +211,7 @@ class StreamChatThemeData {
this.otherMessageTheme.avatarTheme,
) ??
this.otherMessageTheme,
reactionIcons: reactionAssets ?? this.reactionIcons,
reactionIcons: reactionIcons ?? this.reactionIcons,
);
/// Get the default Stream Chat theme
@@ -365,7 +365,7 @@ class StreamChatThemeData {
),
ReactionIcon(
type: 'thumbs_down',
iconData: StreamIcons.thumbs_up_reaction_1,
iconData: StreamIcons.thumbs_down_reaction,
),
ReactionIcon(
type: 'lol',
+60 -65
View File
@@ -461,32 +461,32 @@ class StreamIcons {
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData grey600 = IconData(
static const IconData grid = IconData(
0xe95b,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData grid = IconData(
static const IconData group = IconData(
0xe95c,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData heart_outline_add = IconData(
static const IconData heart_1 = IconData(
0xe95d,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData heart_outline_delete = IconData(
static const IconData heart_outline_add = IconData(
0xe95e,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData heart_pill = IconData(
static const IconData heart_outline_delete = IconData(
0xe95f,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData heart_1 = IconData(
static const IconData heart_pill = IconData(
0xe960,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
@@ -521,127 +521,127 @@ class StreamIcons {
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData hourglass_full_1 = IconData(
static const IconData hourglass_full = IconData(
0xe967,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData hourglass_full = IconData(
static const IconData hourglass = IconData(
0xe968,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData hourglass = IconData(
static const IconData insurance_outline = IconData(
0xe969,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData insurance_outline = IconData(
static const IconData insurance = IconData(
0xe96a,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData insurance = IconData(
static const IconData left_right = IconData(
0xe96b,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData left_right = IconData(
static const IconData left = IconData(
0xe96c,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData left = IconData(
static const IconData light = IconData(
0xe96d,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData light = IconData(
static const IconData lightning = IconData(
0xe96e,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData lightning = IconData(
static const IconData link = IconData(
0xe96f,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData link = IconData(
static const IconData location_map = IconData(
0xe970,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData location_map = IconData(
static const IconData location = IconData(
0xe971,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData location = IconData(
static const IconData LOL_reaction = IconData(
0xe972,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData LOL_reaction = IconData(
static const IconData love_reaction = IconData(
0xe973,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData love_reaction = IconData(
static const IconData love = IconData(
0xe974,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData love = IconData(
static const IconData lungs = IconData(
0xe975,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData lungs = IconData(
static const IconData mail_open = IconData(
0xe976,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData mail_open = IconData(
static const IconData mail = IconData(
0xe977,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData mail = IconData(
static const IconData male = IconData(
0xe978,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData male = IconData(
static const IconData medical_bed = IconData(
0xe979,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData medical_bed = IconData(
static const IconData medical_blank = IconData(
0xe97a,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData medical_blank = IconData(
static const IconData medical_card_add = IconData(
0xe97b,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData medical_card_add = IconData(
static const IconData medical_card = IconData(
0xe97c,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData medical_card = IconData(
static const IconData medical_cross = IconData(
0xe97d,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData medical_cross = IconData(
static const IconData medical_load = IconData(
0xe97e,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData medical_load = IconData(
static const IconData menu_point_1 = IconData(
0xe97f,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
@@ -651,22 +651,22 @@ class StreamIcons {
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData menu_point_1 = IconData(
static const IconData menu_point = IconData(
0xe981,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData menu_point = IconData(
static const IconData menu = IconData(
0xe982,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData menu = IconData(
static const IconData message = IconData(
0xe983,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData message = IconData(
static const IconData mic_1 = IconData(
0xe984,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
@@ -886,12 +886,12 @@ class StreamIcons {
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData save_check = IconData(
static const IconData save_1 = IconData(
0xe9b0,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData save_1 = IconData(
static const IconData save_check = IconData(
0xe9b1,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
@@ -1061,17 +1061,17 @@ class StreamIcons {
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData Thread_Reply = IconData(
static const IconData thumbs_down_reaction = IconData(
0xe9d3,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData thumbs_up_reaction_1 = IconData(
static const IconData thumbs_up_reaction = IconData(
0xe9d4,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData thumbs_up_reaction = IconData(
static const IconData time_1 = IconData(
0xe9d5,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
@@ -1081,109 +1081,104 @@ class StreamIcons {
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData time_1 = IconData(
static const IconData time = IconData(
0xe9d7,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData time = IconData(
static const IconData tooth = IconData(
0xe9d8,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData tooth = IconData(
static const IconData tube_full = IconData(
0xe9d9,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData tube_full = IconData(
static const IconData tv_check = IconData(
0xe9da,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData tv_check = IconData(
static const IconData tv_rating = IconData(
0xe9db,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData tv_rating = IconData(
static const IconData unfold_less_h = IconData(
0xe9dc,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData unfold_less_h = IconData(
static const IconData unfold_less = IconData(
0xe9dd,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData unfold_less = IconData(
static const IconData unfold_more_h = IconData(
0xe9de,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData unfold_more_h = IconData(
static const IconData unfold_more = IconData(
0xe9df,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData unfold_more = IconData(
static const IconData unread = IconData(
0xe9e0,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData unread = IconData(
static const IconData up_down = IconData(
0xe9e1,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData up_down = IconData(
static const IconData up = IconData(
0xe9e2,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData up = IconData(
static const IconData upload = IconData(
0xe9e3,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData upload = IconData(
static const IconData user_add = IconData(
0xe9e4,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData user_add = IconData(
static const IconData user_delete = IconData(
0xe9e5,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData user_delete = IconData(
static const IconData user_minus = IconData(
0xe9e6,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData user_minus = IconData(
static const IconData user = IconData(
0xe9e7,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData user = IconData(
static const IconData voice = IconData(
0xe9e8,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData voice = IconData(
static const IconData world = IconData(
0xe9e9,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData world = IconData(
static const IconData wut_reaction = IconData(
0xe9ea,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
static const IconData wut_reaction = IconData(
0xe9eb,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
}
+1 -1
View File
@@ -70,7 +70,7 @@ class UserAvatar extends StatelessWidget {
: StreamChatTheme.of(context).defaultUserImage(context, user),
),
),
if (showOnlineStatus && user.online)
if (showOnlineStatus && user.online == true)
Positioned(
top: 0,
right: 0,
+1
View File
@@ -29,6 +29,7 @@ dependencies:
http_parser: ^3.1.4
flutter_slidable: ^0.5.4
carousel_slider: ^2.2.1
clipboard: ^0.1.2+8
flutter:
fonts:
+1 -7
View File
@@ -3,13 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.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 {}
import 'mocks.dart';
void main() {
testWidgets(
+94
View File
@@ -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);
},
);
}
+88
View File
@@ -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));
},
);
}
+10
View File
@@ -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 {}
+87
View File
@@ -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);
},
);
}