Skip to content

Commit 87bc225

Browse files
authored
Merge pull request #105 from GetStream/feature/reactions
Feature/reactions
2 parents 8f73d3e + f209797 commit 87bc225

17 files changed

+676
-145
lines changed

example/ios/Flutter/.last_build_id

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
06fe8d0d3d89937a6d33b1033e75ae96
1+
0289cdadbd29bab804f275e3c5907d07

example/lib/main.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ void main() async {
7575
);
7676

7777
await client.setUser(
78-
User(id: 'super-band-9'),
78+
User(id: 'super-band-9', extraData: {
79+
'name': 'John Doe',
80+
}),
7981
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
8082
);
8183

fonts/stream-icons.ttf

-32 Bytes
Binary file not shown.

lib/src/message_actions_modal.dart

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'dart:ui';
22

3+
import 'package:flutter/foundation.dart';
34
import 'package:flutter/material.dart';
5+
import 'package:flutter/services.dart';
46
import 'package:stream_chat/stream_chat.dart';
57
import 'package:stream_chat_flutter/src/reaction_picker.dart';
68
import 'package:stream_chat_flutter/src/stream_channel.dart';
@@ -18,6 +20,7 @@ class MessageActionsModal extends StatelessWidget {
1820
final MessageTheme messageTheme;
1921
final bool showReactions;
2022
final bool showDeleteMessage;
23+
final bool showCopyMessage;
2124
final bool showEditMessage;
2225
final bool showReply;
2326
final bool reverse;
@@ -27,19 +30,19 @@ class MessageActionsModal extends StatelessWidget {
2730
Key key,
2831
@required this.message,
2932
@required this.messageTheme,
30-
this.showReactions,
31-
this.showDeleteMessage,
32-
this.showEditMessage,
33+
this.showReactions = true,
34+
this.showDeleteMessage = true,
35+
this.showEditMessage = true,
3336
this.onThreadTap,
34-
this.showReply,
37+
this.showCopyMessage = true,
38+
this.showReply = true,
3539
this.editMessageInputBuilder,
3640
this.messageShape,
37-
this.reverse,
41+
this.reverse = false,
3842
}) : super(key: key);
3943

4044
@override
4145
Widget build(BuildContext context) {
42-
final channel = StreamChannel.of(context).channel;
4346
return Stack(
4447
children: [
4548
Positioned.fill(
@@ -68,13 +71,13 @@ class MessageActionsModal extends StatelessWidget {
6871
message.status == null))
6972
Center(
7073
child: ReactionPicker(
71-
channel: channel,
7274
message: message,
7375
messageTheme: messageTheme,
7476
),
7577
),
7678
AbsorbPointer(
7779
child: MessageWidget(
80+
key: Key('MessageWidget'),
7881
reverse: reverse,
7982
message: message,
8083
messageTheme: messageTheme,
@@ -103,13 +106,14 @@ class MessageActionsModal extends StatelessWidget {
103106
children: ListTile.divideTiles(
104107
context: context,
105108
tiles: [
106-
if (showEditMessage) _buildEditMessage(context),
107109
if (showReply &&
108110
(message.status == MessageSendingStatus.SENT ||
109111
message.status == null) &&
110112
message.parentId == null)
111113
_buildReplyButton(context),
114+
if (showEditMessage) _buildEditMessage(context),
112115
if (showDeleteMessage) _buildDeleteButton(context),
116+
if (showCopyMessage) _buildCopyButton(context),
113117
],
114118
).toList(),
115119
),
@@ -142,6 +146,23 @@ class MessageActionsModal extends StatelessWidget {
142146
);
143147
}
144148

149+
Widget _buildCopyButton(BuildContext context) {
150+
return ListTile(
151+
title: Text(
152+
'Copy message',
153+
style: Theme.of(context).textTheme.headline6,
154+
),
155+
leading: Icon(
156+
StreamIcons.copy,
157+
color: StreamChatTheme.of(context).primaryIconTheme.color,
158+
),
159+
onTap: () async {
160+
await Clipboard.setData(ClipboardData(text: message.text));
161+
Navigator.pop(context);
162+
},
163+
);
164+
}
165+
145166
Widget _buildEditMessage(BuildContext context) {
146167
return ListTile(
147168
title: Text(
@@ -256,7 +277,7 @@ class MessageActionsModal extends StatelessWidget {
256277
style: Theme.of(context).textTheme.headline6,
257278
),
258279
leading: Icon(
259-
StreamIcons.Thread_Reply,
280+
StreamIcons.sorting_up,
260281
color: StreamChatTheme.of(context).primaryIconTheme.color,
261282
),
262283
onTap: () {
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import 'dart:ui';
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:stream_chat/stream_chat.dart';
5+
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
6+
import 'package:stream_chat_flutter/src/reaction_picker.dart';
7+
import 'package:stream_chat_flutter/src/stream_chat.dart';
8+
import 'package:stream_chat_flutter/src/user_avatar.dart';
9+
10+
import 'message_widget.dart';
11+
import 'stream_chat_theme.dart';
12+
13+
class MessageReactionsModal extends StatelessWidget {
14+
final Widget Function(BuildContext, Message) editMessageInputBuilder;
15+
final void Function(Message) onThreadTap;
16+
final Message message;
17+
final MessageTheme messageTheme;
18+
final bool reverse;
19+
final bool showReactions;
20+
final ShapeBorder messageShape;
21+
final void Function(User) onUserAvatarTap;
22+
23+
const MessageReactionsModal({
24+
Key key,
25+
@required this.message,
26+
@required this.messageTheme,
27+
this.showReactions = true,
28+
this.onThreadTap,
29+
this.editMessageInputBuilder,
30+
this.messageShape,
31+
this.reverse = false,
32+
this.onUserAvatarTap,
33+
}) : super(key: key);
34+
35+
@override
36+
Widget build(BuildContext context) {
37+
return Stack(
38+
children: [
39+
Positioned.fill(
40+
child: GestureDetector(
41+
behavior: HitTestBehavior.translucent,
42+
onTap: () {
43+
Navigator.pop(context);
44+
},
45+
child: BackdropFilter(
46+
filter: ImageFilter.blur(
47+
sigmaX: 10,
48+
sigmaY: 10,
49+
),
50+
child: Container(
51+
color: Colors.transparent,
52+
),
53+
),
54+
),
55+
),
56+
Column(
57+
mainAxisAlignment: MainAxisAlignment.center,
58+
crossAxisAlignment: CrossAxisAlignment.stretch,
59+
children: <Widget>[
60+
if (showReactions &&
61+
(message.status == MessageSendingStatus.SENT ||
62+
message.status == null))
63+
Center(
64+
child: ReactionPicker(
65+
message: message,
66+
messageTheme: messageTheme,
67+
),
68+
),
69+
AbsorbPointer(
70+
child: MessageWidget(
71+
key: Key('MessageWidget'),
72+
reverse: reverse,
73+
message: message,
74+
messageTheme: messageTheme,
75+
showReactions: false,
76+
showUsername: false,
77+
showReplyIndicator: false,
78+
showTimestamp: false,
79+
showSendingIndicator: DisplayWidget.gone,
80+
shape: messageShape,
81+
),
82+
),
83+
SizedBox(
84+
height: 16,
85+
),
86+
if (message.latestReactions?.isNotEmpty == true)
87+
Container(
88+
constraints: BoxConstraints.loose(Size.fromHeight(400)),
89+
child: _buildReactionCard(context),
90+
),
91+
],
92+
),
93+
],
94+
);
95+
}
96+
97+
Padding _buildReactionCard(BuildContext context) {
98+
final currentUser = StreamChat.of(context).user;
99+
return Padding(
100+
padding: const EdgeInsets.symmetric(
101+
horizontal: 8.0,
102+
),
103+
child: Card(
104+
clipBehavior: Clip.hardEdge,
105+
shape: RoundedRectangleBorder(
106+
borderRadius: BorderRadius.circular(16),
107+
),
108+
child: Column(
109+
mainAxisSize: MainAxisSize.min,
110+
children: [
111+
Padding(
112+
padding: const EdgeInsets.all(16.0),
113+
child: Text(
114+
'Message Reactions',
115+
style: Theme.of(context).textTheme.headline6,
116+
),
117+
),
118+
Flexible(
119+
child: Padding(
120+
padding: const EdgeInsets.only(
121+
left: 16.0,
122+
right: 16,
123+
bottom: 16,
124+
),
125+
child: GridView.builder(
126+
shrinkWrap: true,
127+
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
128+
crossAxisCount: 4,
129+
crossAxisSpacing: 16,
130+
childAspectRatio: 0.75,
131+
mainAxisSpacing: 22,
132+
),
133+
itemCount: message.latestReactions.length,
134+
itemBuilder: (context, i) {
135+
final reaction = message.latestReactions[i];
136+
137+
return _buildReaction(
138+
reaction,
139+
currentUser,
140+
context,
141+
);
142+
},
143+
),
144+
),
145+
),
146+
],
147+
),
148+
),
149+
);
150+
}
151+
152+
Column _buildReaction(
153+
Reaction reaction,
154+
User currentUser,
155+
BuildContext context,
156+
) {
157+
final isCurrentUser = reaction.user.id == currentUser.id;
158+
return Column(
159+
mainAxisSize: MainAxisSize.min,
160+
mainAxisAlignment: MainAxisAlignment.start,
161+
crossAxisAlignment: CrossAxisAlignment.center,
162+
children: [
163+
Stack(
164+
children: [
165+
UserAvatar(
166+
onTap: onUserAvatarTap,
167+
user: reaction.user,
168+
constraints: BoxConstraints.tightFor(
169+
height: 64,
170+
width: 64,
171+
),
172+
borderRadius: BorderRadius.circular(32),
173+
),
174+
Positioned(
175+
child: ReactionBubble(
176+
reactions: [reaction],
177+
borderColor: isCurrentUser
178+
? messageTheme.ownReactionsBorderColor
179+
: messageTheme.otherReactionsBorderColor,
180+
backgroundColor: isCurrentUser
181+
? messageTheme.ownReactionsBackgroundColor
182+
: messageTheme.otherReactionsBackgroundColor,
183+
flipTail: !isCurrentUser,
184+
),
185+
bottom: 0,
186+
left: isCurrentUser ? 0 : null,
187+
right: isCurrentUser ? 0 : null,
188+
),
189+
],
190+
),
191+
Text(
192+
reaction.user.name,
193+
style: Theme.of(context).textTheme.subtitle2,
194+
textAlign: TextAlign.center,
195+
),
196+
],
197+
);
198+
}
199+
}

0 commit comments

Comments
 (0)