Merge pull request #119 from GetStream/feature/reactions

Feature/reactions
This commit is contained in:
Deven Joshi
2020-10-30 19:25:35 +05:30
committed by GitHub
9 changed files with 162 additions and 126 deletions
+4
View File
@@ -14,6 +14,10 @@ import Flutter
sharedDefaults?.removeObject(forKey: "messageQueue")
}
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
+4
View File
@@ -58,5 +58,9 @@
<array>
<string>remote-notification</string>
</array>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>
-30
View File
@@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:example/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
+1
View File
@@ -78,6 +78,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel;
return AppBar(
brightness: Theme.of(context).brightness,
elevation: 1,
leading: showBackButton
? StreamBackButton(onPressed: onBackPressed)
+21 -6
View File
@@ -25,6 +25,7 @@ class MessageActionsModal extends StatelessWidget {
final bool showReply;
final bool reverse;
final ShapeBorder messageShape;
final DisplayWidget showUserAvatar;
const MessageActionsModal({
Key key,
@@ -36,6 +37,7 @@ class MessageActionsModal extends StatelessWidget {
this.onThreadTap,
this.showCopyMessage = true,
this.showReply = true,
this.showUserAvatar = DisplayWidget.show,
this.editMessageInputBuilder,
this.messageShape,
this.reverse = false,
@@ -43,7 +45,15 @@ class MessageActionsModal extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ownId = StreamChat.of(context).user.id;
var size = MediaQuery.of(context).size;
var user = StreamChat.of(context).user;
var roughMaxSize = 2 * size.width / 3;
var roughSentenceSize =
message.text.length * messageTheme.messageText.fontSize * 1.2;
var divFactor =
roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
@@ -58,7 +68,7 @@ class MessageActionsModal extends StatelessWidget {
sigmaY: 10.8731,
),
child: Container(
color: Colors.black.withOpacity(0.2),
color: Colors.black.withOpacity(0.1),
),
),
),
@@ -73,7 +83,12 @@ class MessageActionsModal extends StatelessWidget {
if (showReactions &&
(message.status == MessageSendingStatus.SENT ||
message.status == null))
Center(
Align(
alignment: Alignment(
user.id == message.user.id
? (divFactor > 1.0 ? 0.0 : (1.0 - divFactor))
: (divFactor > 1.0 ? 0.0 : -(1.0 - divFactor)),
0.0),
child: ReactionPicker(
message: message,
messageTheme: messageTheme,
@@ -92,10 +107,10 @@ class MessageActionsModal extends StatelessWidget {
showReactions: false,
showUsername: false,
showReplyIndicator: false,
showUserAvatar: message.user.id == ownId
? DisplayWidget.gone
: DisplayWidget.show,
showUserAvatar: showUserAvatar,
showTimestamp: false,
translateUserAvatar: false,
showReactionPickerIndicator: true,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
),
+21 -6
View File
@@ -17,6 +17,7 @@ class MessageReactionsModal extends StatelessWidget {
final MessageTheme messageTheme;
final bool reverse;
final bool showReactions;
final DisplayWidget showUserAvatar;
final ShapeBorder messageShape;
final void Function(User) onUserAvatarTap;
@@ -29,12 +30,21 @@ class MessageReactionsModal extends StatelessWidget {
this.editMessageInputBuilder,
this.messageShape,
this.reverse = false,
this.showUserAvatar = DisplayWidget.show,
this.onUserAvatarTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final ownId = StreamChat.of(context).user.id;
var size = MediaQuery.of(context).size;
var user = StreamChat.of(context).user;
var roughMaxSize = 2 * size.width / 3;
var roughSentenceSize =
message.text.length * messageTheme.messageText.fontSize * 1.2;
var divFactor =
roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
@@ -49,7 +59,7 @@ class MessageReactionsModal extends StatelessWidget {
sigmaY: 10.8731,
),
child: Container(
color: Colors.black.withOpacity(0.2),
color: Colors.black.withOpacity(0.1),
),
),
),
@@ -64,7 +74,12 @@ class MessageReactionsModal extends StatelessWidget {
if (showReactions &&
(message.status == MessageSendingStatus.SENT ||
message.status == null))
Center(
Align(
alignment: Alignment(
user.id == message.user.id
? (divFactor > 1.0 ? 0.0 : (1.0 - divFactor))
: (divFactor > 1.0 ? 0.0 : -(1.0 - divFactor)),
0.0),
child: ReactionPicker(
message: message,
messageTheme: messageTheme,
@@ -82,13 +97,13 @@ class MessageReactionsModal extends StatelessWidget {
messageTheme: messageTheme,
showReactions: false,
showUsername: false,
showUserAvatar: message.user.id == ownId
? DisplayWidget.gone
: DisplayWidget.show,
showUserAvatar: showUserAvatar,
showReplyIndicator: false,
showTimestamp: false,
translateUserAvatar: false,
showSendingIndicator: DisplayWidget.gone,
shape: messageShape,
showReactionPickerIndicator: true,
),
),
SizedBox(
+73 -30
View File
@@ -106,6 +106,9 @@ class MessageWidget extends StatefulWidget {
/// The function called when tapping on a link
final void Function(String) onLinkTap;
/// Used in [MessageReactionsModal] and [MessageActionsModal]
final bool showReactionPickerIndicator;
final List<Read> readList;
/// If true show the users username next to the timestamp of the message
@@ -115,11 +118,15 @@ class MessageWidget extends StatefulWidget {
final bool showEditMessage;
final Map<String, AttachmentBuilder> attachmentBuilders;
/// Center user avatar with bottom of the message
final bool translateUserAvatar;
MessageWidget({
Key key,
@required this.message,
@required this.messageTheme,
this.reverse = false,
this.translateUserAvatar = true,
this.shape,
this.attachmentShape,
this.borderSide,
@@ -127,6 +134,7 @@ class MessageWidget extends StatefulWidget {
this.borderRadiusGeometry,
this.attachmentBorderRadiusGeometry,
this.onMentionTap,
this.showReactionPickerIndicator = false,
this.showUserAvatar = DisplayWidget.show,
this.showSendingIndicator = DisplayWidget.show,
this.showReplyIndicator = true,
@@ -247,39 +255,62 @@ class _MessageWidgetState extends State<MessageWidget> {
),
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)
? Transform(
alignment: Alignment.center,
child: Stack(
clipBehavior: Clip.none,
children: [
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)
? Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(
widget.reverse ? pi : 0),
child: DeletedMessage(
messageTheme: widget.messageTheme,
),
)
: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
..._parseAttachments(context),
if (widget.message.text
.trim()
.isNotEmpty)
_buildTextBubble(context),
],
),
),
if (widget.showReactionPickerIndicator)
Positioned(
right: 0,
top: -6,
child: Transform(
transform: Matrix4.rotationY(
widget.reverse ? pi : 0),
child: DeletedMessage(
messageTheme: widget.messageTheme,
child: CustomPaint(
painter: ReactionBubblePainter(
widget.messageTheme
.reactionsBackgroundColor,
widget.messageTheme
.reactionsBorderColor,
),
),
)
: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
..._parseAttachments(context),
if (widget.message.text
.trim()
.isNotEmpty)
_buildTextBubble(context),
],
),
),
],
),
),
),
@@ -423,6 +454,10 @@ class _MessageWidgetState extends State<MessageWidget> {
return StreamChannel(
channel: channel,
child: MessageActionsModal(
showUserAvatar:
widget.message.user.id == channel.client.state.user.id
? DisplayWidget.gone
: DisplayWidget.show,
messageTheme: widget.messageTheme,
messageShape: widget.shape ?? _getDefaultShape(context),
reverse: widget.reverse,
@@ -447,6 +482,10 @@ class _MessageWidgetState extends State<MessageWidget> {
return StreamChannel(
channel: channel,
child: MessageReactionsModal(
showUserAvatar:
widget.message.user.id == channel.client.state.user.id
? DisplayWidget.gone
: DisplayWidget.show,
onUserAvatarTap: widget.onUserAvatarTap,
messageTheme: widget.messageTheme,
messageShape: widget.shape ?? _getDefaultShape(context),
@@ -614,7 +653,11 @@ class _MessageWidgetState extends State<MessageWidget> {
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Transform.translate(
offset: Offset(
0, widget.messageTheme.avatarTheme.constraints.maxHeight / 2),
0,
widget.translateUserAvatar
? widget.messageTheme.avatarTheme.constraints.maxHeight / 2
: 0,
),
child: UserAvatar(
user: widget.message.user,
onTap: widget.onUserAvatarTap,
+37 -53
View File
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import '../stream_chat_flutter.dart';
@@ -22,59 +21,44 @@ class ReactionPicker extends StatelessWidget {
@override
Widget build(BuildContext context) {
final reactionIcons = StreamChatTheme.of(context).reactionIcons;
return Stack(
fit: StackFit.passthrough,
children: [
Material(
color: messageTheme.reactionsBackgroundColor,
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.withOpacity(.5),
),
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.reactionsBackgroundColor,
messageTheme.reactionsBorderColor,
return Material(
color: messageTheme.reactionsBackgroundColor,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
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.withOpacity(.5),
),
),
),
],
onPressed: () {
if (ownReactionIndex != -1) {
removeReaction(
context,
message.ownReactions[ownReactionIndex],
);
} else {
sendReaction(
context,
reactionIcon.type,
);
}
},
);
}).toList(),
),
);
}
+1 -1
View File
@@ -24,7 +24,7 @@ dependencies:
file_picker: ^2.0.8+1
image_picker: ^0.6.7+2
flutter_keyboard_visibility: ^3.2.1
stream_chat: ^0.2.10
stream_chat: ^0.2.10+1
mime: ^0.9.6+3
visibility_detector: ^0.1.5
http_parser: ^3.1.4