diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index 6a90b61e649..e697d20029b 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -163,11 +163,16 @@ class ActionSheetCancelButton extends StatelessWidget { } /// Show a sheet of actions you can take on a topic. -void showTopicActionSheet(BuildContext context, { +/// +/// [pageContext] should be the context of the whole page in the nav, +/// not of the specific element that was long-pressed. +/// The long-pressed element live-updates on server events, +/// so it might unmount before the action-sheet buttons have finished using it. +void showTopicActionSheet(BuildContext pageContext, { required int channelId, required TopicName topic, }) { - final store = PerAccountStoreWidget.of(context); + final store = PerAccountStoreWidget.of(pageContext); final subscription = store.subscriptions[channelId]; final optionButtons = []; @@ -237,7 +242,7 @@ void showTopicActionSheet(BuildContext context, { currentVisibilityPolicy: visibilityPolicy, newVisibilityPolicy: to, narrow: TopicNarrow(channelId, topic), - pageContext: context); + pageContext: pageContext); })); if (optionButtons.isEmpty) { @@ -250,7 +255,7 @@ void showTopicActionSheet(BuildContext context, { return; } - _showActionSheet(context, optionButtons: optionButtons); + _showActionSheet(pageContext, optionButtons: optionButtons); } class UserTopicUpdateButton extends ActionSheetMenuItemButton { @@ -390,6 +395,9 @@ void showMessageActionSheet({required BuildContext context, required Message mes final markAsUnreadSupported = store.connection.zulipFeatureLevel! >= 155; // TODO(server-6) final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead; + // TODO like in showTopicActionSheet, don't pass buttons a BuildContext + // for live-updating UI that could unmount before it's finished being used. + final optionButtons = [ ReactionButtons(message: message, pageContext: context), StarButton(message: message, pageContext: context), diff --git a/lib/widgets/inbox.dart b/lib/widgets/inbox.dart index 598b99beacb..06ea3ea22fa 100644 --- a/lib/widgets/inbox.dart +++ b/lib/widgets/inbox.dart @@ -13,14 +13,34 @@ import 'text.dart'; import 'theme.dart'; import 'unread_count_badge.dart'; + +/// The interface for the state of an [InboxPageBody]. +/// +/// To obtain one of these, see [InboxPageBody.ancestorOf]. +abstract class InboxPageState { + BuildContext get context; +} + class InboxPageBody extends StatefulWidget { const InboxPageBody({super.key}); + /// The [InboxPageState] above this context in the tree. + /// + /// Uses the inefficient [BuildContext.findAncestorStateOfType]; + /// don't call this in a build method. + // If we do find ourselves wanting this in a build method, it won't be hard + // to enable that: we'd just need to add an [InheritedWidget] here. + static InboxPageState ancestorOf(BuildContext context) { + final state = context.findAncestorStateOfType<_InboxPageState>(); + assert(state != null, 'No InboxPageBody ancestor'); + return state!; + } + @override State createState() => _InboxPageState(); } -class _InboxPageState extends State with PerAccountStoreAwareStateMixin { +class _InboxPageState extends State with PerAccountStoreAwareStateMixin implements InboxPageState { Unreads? unreadsModel; RecentDmConversationsView? recentDmConversationsModel; @@ -516,7 +536,8 @@ class _TopicItem extends StatelessWidget { Navigator.push(context, MessageListPage.buildRoute(context: context, narrow: narrow)); }, - onLongPress: () => showTopicActionSheet(context, + onLongPress: () => showTopicActionSheet( + InboxPageBody.ancestorOf(context).context, channelId: streamId, topic: topic), child: ConstrainedBox(constraints: const BoxConstraints(minHeight: 34), child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index 8bfacf9a3dd..fc55fcc2863 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -183,6 +183,8 @@ abstract class MessageListPageState { /// /// This is null if [MessageList] has not mounted yet. MessageListView? get model; + + BuildContext get context; } class MessageListPage extends StatefulWidget { @@ -400,7 +402,8 @@ class MessageListAppBarTitle extends StatelessWidget { width: double.infinity, child: GestureDetector( behavior: HitTestBehavior.translucent, - onLongPress: () => showTopicActionSheet(context, + onLongPress: () => showTopicActionSheet( + MessageListPage.ancestorOf(context).context, channelId: streamId, topic: topic), child: Column( crossAxisAlignment: willCenterTitle ? CrossAxisAlignment.center @@ -1112,7 +1115,8 @@ class StreamMessageRecipientHeader extends StatelessWidget { onTap: () => Navigator.push(context, MessageListPage.buildRoute(context: context, narrow: TopicNarrow.ofMessage(message))), - onLongPress: () => showTopicActionSheet(context, + onLongPress: () => showTopicActionSheet( + MessageListPage.ancestorOf(context).context, channelId: message.streamId, topic: topic), child: ColoredBox( color: backgroundColor,