diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index f9242cf006..1b6c8b2e58 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -23,44 +23,10 @@ import 'store.dart'; import 'text.dart'; import 'theme.dart'; -/// Show a sheet of actions you can take on a message in the message list. -/// -/// Must have a [MessageListPage] ancestor. -void showMessageActionSheet({required BuildContext context, required Message message}) { - final store = PerAccountStoreWidget.of(context); - - // The UI that's conditioned on this won't live-update during this appearance - // of the action sheet (we avoid calling composeBoxControllerOf in a build - // method; see its doc). - // So we rely on the fact that isComposeBoxOffered for any given message list - // will be constant through the page's life. - final messageListPage = MessageListPage.ancestorOf(context); - final isComposeBoxOffered = messageListPage.composeBoxController != null; - - final isMessageRead = message.flags.contains(MessageFlag.read); - final markAsUnreadSupported = store.connection.zulipFeatureLevel! >= 155; // TODO(server-6) - final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead; - - final hasThumbsUpReactionVote = message.reactions - ?.aggregated.any((reactionWithVotes) => - reactionWithVotes.reactionType == ReactionType.unicodeEmoji - && reactionWithVotes.emojiCode == '1f44d' - && reactionWithVotes.userIds.contains(store.selfUserId)) - ?? false; - - final optionButtons = [ - if (!hasThumbsUpReactionVote) - AddThumbsUpButton(message: message, pageContext: context), - StarButton(message: message, pageContext: context), - if (isComposeBoxOffered) - QuoteAndReplyButton(message: message, pageContext: context), - if (showMarkAsUnreadButton) - MarkAsUnreadButton(message: message, pageContext: context), - CopyMessageTextButton(message: message, pageContext: context), - CopyMessageLinkButton(message: message, pageContext: context), - ShareButton(message: message, pageContext: context), - ]; - +void _showActionSheet( + BuildContext context, { + required List optionButtons, +}) { showModalBottomSheet( context: context, // Clip.hardEdge looks bad; Clip.antiAliasWithSaveLayer looks pixel-perfect @@ -88,17 +54,13 @@ void showMessageActionSheet({required BuildContext context, required Message mes borderRadius: BorderRadius.circular(7), child: Column(spacing: 1, children: optionButtons))))), - const MessageActionSheetCancelButton(), + const ActionSheetCancelButton(), ]))); }); } -abstract class MessageActionSheetMenuItemButton extends StatelessWidget { - MessageActionSheetMenuItemButton({ - super.key, - required this.message, - required this.pageContext, - }) : assert(pageContext.findAncestorWidgetOfExactType() != null); +abstract class ActionSheetMenuItemButton extends StatelessWidget { + const ActionSheetMenuItemButton({super.key, required this.pageContext}); IconData get icon; String label(ZulipLocalizations zulipLocalizations); @@ -111,8 +73,6 @@ abstract class MessageActionSheetMenuItemButton extends StatelessWidget { /// For operations that need a [BuildContext], see [pageContext]. void onPressed(); - final Message message; - /// A context within the [MessageListPage] this action sheet was /// triggered from. final BuildContext pageContext; @@ -157,8 +117,8 @@ abstract class MessageActionSheetMenuItemButton extends StatelessWidget { } } -class MessageActionSheetCancelButton extends StatelessWidget { - const MessageActionSheetCancelButton({super.key}); +class ActionSheetCancelButton extends StatelessWidget { + const ActionSheetCancelButton({super.key}); @override Widget build(BuildContext context) { @@ -183,6 +143,57 @@ class MessageActionSheetCancelButton extends StatelessWidget { } } +/// Show a sheet of actions you can take on a message in the message list. +/// +/// Must have a [MessageListPage] ancestor. +void showMessageActionSheet({required BuildContext context, required Message message}) { + final store = PerAccountStoreWidget.of(context); + + // The UI that's conditioned on this won't live-update during this appearance + // of the action sheet (we avoid calling composeBoxControllerOf in a build + // method; see its doc). + // So we rely on the fact that isComposeBoxOffered for any given message list + // will be constant through the page's life. + final messageListPage = MessageListPage.ancestorOf(context); + final isComposeBoxOffered = messageListPage.composeBoxController != null; + + final isMessageRead = message.flags.contains(MessageFlag.read); + final markAsUnreadSupported = store.connection.zulipFeatureLevel! >= 155; // TODO(server-6) + final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead; + + final hasThumbsUpReactionVote = message.reactions + ?.aggregated.any((reactionWithVotes) => + reactionWithVotes.reactionType == ReactionType.unicodeEmoji + && reactionWithVotes.emojiCode == '1f44d' + && reactionWithVotes.userIds.contains(store.selfUserId)) + ?? false; + + final optionButtons = [ + if (!hasThumbsUpReactionVote) + AddThumbsUpButton(message: message, pageContext: context), + StarButton(message: message, pageContext: context), + if (isComposeBoxOffered) + QuoteAndReplyButton(message: message, pageContext: context), + if (showMarkAsUnreadButton) + MarkAsUnreadButton(message: message, pageContext: context), + CopyMessageTextButton(message: message, pageContext: context), + CopyMessageLinkButton(message: message, pageContext: context), + ShareButton(message: message, pageContext: context), + ]; + + _showActionSheet(context, optionButtons: optionButtons); +} + +abstract class MessageActionSheetMenuItemButton extends ActionSheetMenuItemButton { + MessageActionSheetMenuItemButton({ + super.key, + required this.message, + required super.pageContext, + }) : assert(pageContext.findAncestorWidgetOfExactType() != null); + + final Message message; +} + // This button is very temporary, to complete #125 before we have a way to // choose an arbitrary reaction (#388). So, skipping i18n. class AddThumbsUpButton extends MessageActionSheetMenuItemButton {