@@ -9,13 +9,15 @@ import '../api/exception.dart';
9
9
import '../api/model/model.dart' ;
10
10
import '../api/route/messages.dart' ;
11
11
import '../generated/l10n/zulip_localizations.dart' ;
12
+ import '../model/emoji.dart' ;
12
13
import '../model/internal_link.dart' ;
13
14
import '../model/narrow.dart' ;
14
15
import 'actions.dart' ;
15
16
import 'clipboard.dart' ;
16
17
import 'color.dart' ;
17
18
import 'compose_box.dart' ;
18
19
import 'dialog.dart' ;
20
+ import 'emoji.dart' ;
19
21
import 'icons.dart' ;
20
22
import 'inset_shadow.dart' ;
21
23
import 'message_list.dart' ;
@@ -41,16 +43,8 @@ void showMessageActionSheet({required BuildContext context, required Message mes
41
43
final markAsUnreadSupported = store.connection.zulipFeatureLevel! >= 155 ; // TODO(server-6)
42
44
final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead;
43
45
44
- final hasThumbsUpReactionVote = message.reactions
45
- ? .aggregated.any ((reactionWithVotes) =>
46
- reactionWithVotes.reactionType == ReactionType .unicodeEmoji
47
- && reactionWithVotes.emojiCode == '1f44d'
48
- && reactionWithVotes.userIds.contains (store.selfUserId))
49
- ?? false ;
50
-
51
46
final optionButtons = [
52
- if (! hasThumbsUpReactionVote)
53
- AddThumbsUpButton (message: message, pageContext: context),
47
+ ReactionButtons (message: message, pageContext: context, popularEmojis: zulipPopularEmojis),
54
48
StarButton (message: message, pageContext: context),
55
49
if (isComposeBoxOffered)
56
50
QuoteAndReplyButton (message: message, pageContext: context),
@@ -183,27 +177,39 @@ class MessageActionSheetCancelButton extends StatelessWidget {
183
177
}
184
178
}
185
179
186
- // This button is very temporary, to complete #125 before we have a way to
187
- // choose an arbitrary reaction (#388). So, skipping i18n.
188
- class AddThumbsUpButton extends MessageActionSheetMenuItemButton {
189
- AddThumbsUpButton ({super .key, required super .message, required super .pageContext});
180
+ class ReactionButtons extends StatelessWidget {
181
+ const ReactionButtons ({
182
+ super .key,
183
+ required this .message,
184
+ required this .pageContext,
185
+ required this .popularEmojis,
186
+ });
190
187
191
- @override IconData get icon => ZulipIcons .smile ;
188
+ final Message message ;
192
189
193
- @override
194
- String label (ZulipLocalizations zulipLocalizations) {
195
- return 'React with 👍' ; // TODO(i18n) skip translation for now
196
- }
190
+ /// A context within the [MessageListPage] this action sheet was
191
+ /// triggered from.
192
+ final BuildContext pageContext;
197
193
198
- @override void onPressed () async {
194
+ /// List of popular emoji reaction buttons to display.
195
+ /// Each emoji must be a unicode emoji.
196
+ final List <EmojiCandidate > popularEmojis;
197
+
198
+ void _onPressed (
199
+ EmojiCandidate emoji,
200
+ bool selfVoted,
201
+ ZulipLocalizations zulipLocalizations,
202
+ ) async {
199
203
String ? errorMessage;
200
204
try {
201
- await addReaction (PerAccountStoreWidget .of (pageContext).connection,
205
+ await (selfVoted ? removeReaction : addReaction).call (
206
+ PerAccountStoreWidget .of (pageContext).connection,
202
207
messageId: message.id,
203
- reactionType: ReactionType .unicodeEmoji ,
204
- emojiCode: '1f44d' ,
205
- emojiName: '+1' ,
208
+ reactionType: emoji.emojiType ,
209
+ emojiCode: emoji.emojiCode ,
210
+ emojiName: emoji.emojiName ,
206
211
);
212
+ if (pageContext.mounted) Navigator .pop (pageContext);
207
213
} catch (e) {
208
214
if (! pageContext.mounted) return ;
209
215
@@ -216,9 +222,57 @@ class AddThumbsUpButton extends MessageActionSheetMenuItemButton {
216
222
}
217
223
218
224
showErrorDialog (context: pageContext,
219
- title: 'Adding reaction failed' , message: errorMessage);
225
+ title: selfVoted
226
+ ? zulipLocalizations.errorReactionRemovingFailedTitle
227
+ : zulipLocalizations.errorReactionAddingFailedTitle,
228
+ message: errorMessage);
220
229
}
221
230
}
231
+
232
+ @override
233
+ Widget build (BuildContext context) {
234
+ assert (popularEmojis.every (
235
+ (emoji) => emoji.emojiType == ReactionType .unicodeEmoji));
236
+
237
+ final zulipLocalizations = ZulipLocalizations .of (context);
238
+ final store = PerAccountStoreWidget .of (pageContext);
239
+ final designVariables = DesignVariables .of (context);
240
+
241
+ bool hasSelfVote (EmojiCandidate emoji) {
242
+ return message.reactions? .aggregated.any ((reactionWithVotes) {
243
+ return reactionWithVotes.reactionType == ReactionType .unicodeEmoji
244
+ && reactionWithVotes.emojiCode == emoji.emojiCode
245
+ && reactionWithVotes.userIds.contains (store.selfUserId);
246
+ }) ?? false ;
247
+ }
248
+
249
+ return Container (
250
+ padding: const EdgeInsets .all (8 ),
251
+ decoration: BoxDecoration (color: designVariables.contextMenuItemBg.withFadedAlpha (0.12 )),
252
+ child: Row (
253
+ mainAxisAlignment: MainAxisAlignment .spaceAround,
254
+ children: List .unmodifiable (popularEmojis.map ((emoji) {
255
+ final selfVoted = hasSelfVote (emoji);
256
+ return IconButton (
257
+ onPressed: () => _onPressed (emoji, selfVoted, zulipLocalizations),
258
+ isSelected: selfVoted,
259
+ style: IconButton .styleFrom (
260
+ padding: EdgeInsets .zero,
261
+ splashFactory: NoSplash .splashFactory,
262
+ shape: RoundedRectangleBorder (borderRadius: BorderRadius .circular (3.5 )),
263
+ tapTargetSize: MaterialTapTargetSize .shrinkWrap,
264
+ visualDensity: VisualDensity .compact,
265
+ ).copyWith (backgroundColor: WidgetStateColor .resolveWith ((states) =>
266
+ states.any ((e) => e == WidgetState .pressed || e == WidgetState .selected)
267
+ ? designVariables.contextMenuItemBg.withFadedAlpha (0.20 )
268
+ : Colors .transparent)),
269
+ icon: UnicodeEmojiWidget (
270
+ emojiDisplay: emoji.emojiDisplay as UnicodeEmojiDisplay ,
271
+ notoColorEmojiTextSize: 20.1 ,
272
+ size: 24 ));
273
+ })))
274
+ );
275
+ }
222
276
}
223
277
224
278
class StarButton extends MessageActionSheetMenuItemButton {
0 commit comments