1
1
import 'dart:async' ;
2
2
3
+ import 'package:collection/collection.dart' ;
3
4
import 'package:flutter/foundation.dart' ;
4
5
import 'package:flutter/material.dart' ;
5
6
import 'package:flutter/services.dart' ;
@@ -9,13 +10,16 @@ import '../api/exception.dart';
9
10
import '../api/model/model.dart' ;
10
11
import '../api/route/messages.dart' ;
11
12
import '../generated/l10n/zulip_localizations.dart' ;
13
+ import '../model/emoji.dart' ;
12
14
import '../model/internal_link.dart' ;
13
15
import '../model/narrow.dart' ;
14
16
import 'actions.dart' ;
15
17
import 'clipboard.dart' ;
16
18
import 'color.dart' ;
17
19
import 'compose_box.dart' ;
18
20
import 'dialog.dart' ;
21
+ import 'emoji.dart' ;
22
+ import 'emoji_reaction.dart' ;
19
23
import 'icons.dart' ;
20
24
import 'inset_shadow.dart' ;
21
25
import 'message_list.dart' ;
@@ -25,7 +29,7 @@ import 'theme.dart';
25
29
26
30
void _showActionSheet (
27
31
BuildContext context, {
28
- required List <ActionSheetMenuItemButton > optionButtons,
32
+ required List <Widget > optionButtons,
29
33
}) {
30
34
showModalBottomSheet <void >(
31
35
context: context,
@@ -161,16 +165,8 @@ void showMessageActionSheet(BuildContext context, {required Message message}) {
161
165
final markAsUnreadSupported = store.connection.zulipFeatureLevel! >= 155 ; // TODO(server-6)
162
166
final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead;
163
167
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
-
171
168
final optionButtons = [
172
- if (! hasThumbsUpReactionVote)
173
- AddThumbsUpButton (message: message, pageContext: context),
169
+ ReactionButtons (message: message, pageContext: context),
174
170
StarButton (message: message, pageContext: context),
175
171
if (isComposeBoxOffered)
176
172
QuoteAndReplyButton (message: message, pageContext: context),
@@ -194,41 +190,82 @@ abstract class MessageActionSheetMenuItemButton extends ActionSheetMenuItemButto
194
190
final Message message;
195
191
}
196
192
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
+ });
201
199
202
- @override IconData get icon => ZulipIcons .smile ;
200
+ final Message message ;
203
201
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);
207
223
}
208
224
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));
220
229
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);
228
232
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 ;
231
239
}
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
+ }))));
232
269
}
233
270
}
234
271
0 commit comments