11import 'dart:async' ;
22
3+ import 'package:collection/collection.dart' ;
34import 'package:flutter/foundation.dart' ;
45import 'package:flutter/material.dart' ;
56import 'package:flutter/services.dart' ;
@@ -9,13 +10,16 @@ import '../api/exception.dart';
910import '../api/model/model.dart' ;
1011import '../api/route/messages.dart' ;
1112import '../generated/l10n/zulip_localizations.dart' ;
13+ import '../model/emoji.dart' ;
1214import '../model/internal_link.dart' ;
1315import '../model/narrow.dart' ;
1416import 'actions.dart' ;
1517import 'clipboard.dart' ;
1618import 'color.dart' ;
1719import 'compose_box.dart' ;
1820import 'dialog.dart' ;
21+ import 'emoji.dart' ;
22+ import 'emoji_reaction.dart' ;
1923import 'icons.dart' ;
2024import 'inset_shadow.dart' ;
2125import 'message_list.dart' ;
@@ -25,7 +29,7 @@ import 'theme.dart';
2529
2630void _showActionSheet (
2731 BuildContext context, {
28- required List <ActionSheetMenuItemButton > optionButtons,
32+ required List <Widget > optionButtons,
2933}) {
3034 showModalBottomSheet <void >(
3135 context: context,
@@ -161,16 +165,8 @@ void showMessageActionSheet(BuildContext context, {required Message message}) {
161165 final markAsUnreadSupported = store.connection.zulipFeatureLevel! >= 155 ; // TODO(server-6)
162166 final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead;
163167
164- final hasThumbsUpReactionVote = message.reactions
165- ? .aggregated.any ((reactionWithVotes) =>
166- reactionWithVotes.reactionType == ReactionType .unicodeEmoji
167- && reactionWithVotes.emojiCode == '1f44d'
168- && reactionWithVotes.userIds.contains (store.selfUserId))
169- ?? false ;
170-
171168 final optionButtons = [
172- if (! hasThumbsUpReactionVote)
173- AddThumbsUpButton (message: message, pageContext: context),
169+ ReactionButtons (message: message, pageContext: context),
174170 StarButton (message: message, pageContext: context),
175171 if (isComposeBoxOffered)
176172 QuoteAndReplyButton (message: message, pageContext: context),
@@ -194,41 +190,82 @@ abstract class MessageActionSheetMenuItemButton extends ActionSheetMenuItemButto
194190 final Message message;
195191}
196192
197- // This button is very temporary, to complete #125 before we have a way to
198- // choose an arbitrary reaction (#388). So, skipping i18n.
199- class AddThumbsUpButton extends MessageActionSheetMenuItemButton {
200- AddThumbsUpButton ({super .key, required super .message, required super .pageContext});
193+ class ReactionButtons extends StatelessWidget {
194+ const ReactionButtons ({
195+ super .key,
196+ required this .message,
197+ required this .pageContext,
198+ });
201199
202- @override IconData get icon => ZulipIcons .smile ;
200+ final Message message ;
203201
204- @override
205- String label (ZulipLocalizations zulipLocalizations) {
206- return 'React with 👍' ; // TODO(i18n) skip translation for now
202+ /// A context within the [MessageListPage] this action sheet was
203+ /// triggered from.
204+ final BuildContext pageContext;
205+
206+ void _onReactionPressed ({
207+ required EmojiCandidate emoji,
208+ required bool isSelfVoted,
209+ }) {
210+ // Dismiss the enclosing action sheet immediately,
211+ // for swift UI feedback that the user's selection was received.
212+ Navigator .pop (pageContext);
213+
214+ final zulipLocalizations = ZulipLocalizations .of (pageContext);
215+ doAddOrRemoveReaction (
216+ context: pageContext,
217+ doRemoveReaction: isSelfVoted,
218+ messageId: message.id,
219+ emoji: emoji,
220+ errorDialogTitle: isSelfVoted
221+ ? zulipLocalizations.errorReactionRemovingFailedTitle
222+ : zulipLocalizations.errorReactionAddingFailedTitle);
207223 }
208224
209- @override void onPressed () async {
210- String ? errorMessage;
211- try {
212- await addReaction (PerAccountStoreWidget .of (pageContext).connection,
213- messageId: message.id,
214- reactionType: ReactionType .unicodeEmoji,
215- emojiCode: '1f44d' ,
216- emojiName: '+1' ,
217- );
218- } catch (e) {
219- if (! pageContext.mounted) return ;
225+ @override
226+ Widget build (BuildContext context) {
227+ assert (EmojiStore .popularEmojiCandidates.every (
228+ (emoji) => emoji.emojiType == ReactionType .unicodeEmoji));
220229
221- switch (e) {
222- case ZulipApiException ():
223- errorMessage = e.message;
224- // TODO(#741) specific messages for common errors, like network errors
225- // (support with reusable code)
226- default :
227- }
230+ final store = PerAccountStoreWidget .of (pageContext);
231+ final designVariables = DesignVariables .of (context);
228232
229- showErrorDialog (context: pageContext,
230- title: 'Adding reaction failed' , message: errorMessage);
233+ bool hasSelfVote (EmojiCandidate emoji) {
234+ return message.reactions? .aggregated.any ((reactionWithVotes) {
235+ return reactionWithVotes.reactionType == ReactionType .unicodeEmoji
236+ && reactionWithVotes.emojiCode == emoji.emojiCode
237+ && reactionWithVotes.userIds.contains (store.selfUserId);
238+ }) ?? false ;
231239 }
240+
241+ return Container (
242+ decoration: BoxDecoration (color: designVariables.contextMenuCancelBg),
243+ child: Row (
244+ spacing: 1 ,
245+ children: List .unmodifiable (EmojiStore .popularEmojiCandidates.mapIndexed ((index, emoji) {
246+ final isSelfVoted = hasSelfVote (emoji);
247+ return Flexible (child: InkWell (
248+ onTap: () => _onReactionPressed (emoji: emoji, isSelfVoted: isSelfVoted),
249+ splashFactory: NoSplash .splashFactory,
250+ borderRadius: index == 0
251+ ? const BorderRadius .only (topLeft: Radius .circular (7 ))
252+ : null ,
253+ overlayColor: WidgetStateColor .resolveWith ((states) =>
254+ states.any ((e) => e == WidgetState .pressed)
255+ ? designVariables.contextMenuItemBg.withFadedAlpha (0.20 )
256+ : Colors .transparent),
257+ child: Container (
258+ width: double .infinity,
259+ padding: const EdgeInsets .symmetric (vertical: 12 , horizontal: 5 ),
260+ alignment: Alignment .center,
261+ color: isSelfVoted
262+ ? designVariables.contextMenuItemBg.withFadedAlpha (0.20 )
263+ : null ,
264+ child: UnicodeEmojiWidget (
265+ emojiDisplay: emoji.emojiDisplay as UnicodeEmojiDisplay ,
266+ notoColorEmojiTextSize: 20.1 ,
267+ size: 24 ))));
268+ }))));
232269 }
233270}
234271
0 commit comments