Skip to content

Commit 7a2c9d2

Browse files
gnpricechrisbobbe
authored andcommitted
msglist [nfc]: Generalize ancestorOf from composeBoxControllerOf
1 parent c874531 commit 7a2c9d2

File tree

3 files changed

+47
-23
lines changed

3 files changed

+47
-23
lines changed

lib/widgets/action_sheet.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ void showMessageActionSheet({required BuildContext context, required Message mes
2626
// of the action sheet (we avoid calling composeBoxControllerOf in a build
2727
// method; see its doc). But currently it will be constant through the life of
2828
// any message list, so that's fine.
29-
final isComposeBoxOffered = MessageListPage.composeBoxControllerOf(context) != null;
29+
final messageListPage = MessageListPage.ancestorOf(context);
30+
final isComposeBoxOffered = messageListPage.composeBoxController != null;
3031

3132
final hasThumbsUpReactionVote = message.reactions
3233
?.aggregated.any((reactionWithVotes) =>
@@ -239,7 +240,7 @@ class QuoteAndReplyButton extends MessageActionSheetMenuItemButton {
239240
// message action sheet opened, and before "Quote and reply" was pressed.
240241
// Currently a compose box can't ever disappear, so this is impossible.
241242
ComposeBoxController composeBoxController =
242-
MessageListPage.composeBoxControllerOf(messageListContext)!;
243+
MessageListPage.ancestorOf(messageListContext).composeBoxController!;
243244
final topicController = composeBoxController.topicController;
244245
if (
245246
topicController != null
@@ -264,7 +265,8 @@ class QuoteAndReplyButton extends MessageActionSheetMenuItemButton {
264265
// This will be null only if the compose box disappeared during the
265266
// quotation request. Currently a compose box can't ever disappear,
266267
// so this is impossible.
267-
composeBoxController = MessageListPage.composeBoxControllerOf(messageListContext)!;
268+
composeBoxController =
269+
MessageListPage.ancestorOf(messageListContext).composeBoxController!;
268270
composeBoxController.contentController
269271
.registerQuoteAndReplyEnd(PerAccountStoreWidget.of(messageListContext), tag,
270272
message: message,

lib/widgets/message_list.dart

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ import 'edit_state_marker.dart';
2525
import 'text.dart';
2626
import 'theme.dart';
2727

28+
/// The interface for the state of a [MessageListPage].
29+
///
30+
/// To obtain one of these, see [MessageListPage.ancestorOf].
31+
abstract class MessageListPageState {
32+
/// The controller for this [MessageListPage]'s compose box,
33+
/// if this [MessageListPage] offers a compose box.
34+
ComposeBoxController? get composeBoxController;
35+
}
36+
2837
class MessageListPage extends StatefulWidget {
2938
const MessageListPage({super.key, required this.narrow});
3039

@@ -34,14 +43,16 @@ class MessageListPage extends StatefulWidget {
3443
page: MessageListPage(narrow: narrow));
3544
}
3645

37-
/// A [ComposeBoxController], if this [MessageListPage] offers a compose box.
46+
/// The [MessageListPageState] above this context in the tree.
3847
///
3948
/// Uses the inefficient [BuildContext.findAncestorStateOfType];
4049
/// don't call this in a build method.
41-
static ComposeBoxController? composeBoxControllerOf(BuildContext context) {
42-
final messageListPageState = context.findAncestorStateOfType<_MessageListPageState>();
43-
assert(messageListPageState != null, 'No MessageListPage ancestor');
44-
return messageListPageState!._composeBoxKey.currentState;
50+
// If we do find ourselves wanting this in a build method, it won't be hard
51+
// to enable that: we'd just need to add an [InheritedWidget] here.
52+
static MessageListPageState ancestorOf(BuildContext context) {
53+
final state = context.findAncestorStateOfType<_MessageListPageState>();
54+
assert(state != null, 'No MessageListPage ancestor');
55+
return state!;
4556
}
4657

4758
final Narrow narrow;
@@ -53,7 +64,10 @@ class MessageListPage extends StatefulWidget {
5364
// TODO(design) this seems ad-hoc; is there a better color?
5465
const _kUnsubscribedStreamRecipientHeaderColor = Color(0xfff5f5f5);
5566

56-
class _MessageListPageState extends State<MessageListPage> {
67+
class _MessageListPageState extends State<MessageListPage> implements MessageListPageState {
68+
@override
69+
ComposeBoxController? get composeBoxController => _composeBoxKey.currentState;
70+
5771
final GlobalKey<ComposeBoxController> _composeBoxKey = GlobalKey();
5872

5973
@override

test/widgets/message_list_test.dart

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,28 +89,36 @@ void main() {
8989
}
9090

9191
group('MessageListPage', () {
92-
testWidgets('composeBoxControllerOf finds compose box', (tester) async {
93-
final stream = eg.stream();
94-
await setupMessageListPage(tester, narrow: StreamNarrow(stream.streamId),
95-
messages: [eg.streamMessage(stream: stream, content: "<p>a message</p>")]);
96-
final context = tester.element(find.text("a message"));
97-
check(MessageListPage.composeBoxControllerOf(context)).isNotNull();
98-
});
99-
100-
testWidgets('composeBoxControllerOf null when no compose box', (tester) async {
101-
await setupMessageListPage(tester, narrow: const CombinedFeedNarrow(),
92+
testWidgets('ancestorOf finds page state from message', (tester) async {
93+
await setupMessageListPage(tester,
10294
messages: [eg.streamMessage(content: "<p>a message</p>")]);
103-
final context = tester.element(find.text("a message"));
104-
check(MessageListPage.composeBoxControllerOf(context)).isNull();
95+
final expectedState = tester.state<State>(find.byType(MessageListPage));
96+
check(MessageListPage.ancestorOf(tester.element(find.text("a message"))))
97+
.identicalTo(expectedState as MessageListPageState);
10598
});
10699

107-
testWidgets('composeBoxControllerOf throws when not a descendant of MessageListPage', (tester) async {
100+
testWidgets('ancestorOf throws when not a descendant of MessageListPage', (tester) async {
108101
await setupMessageListPage(tester,
109102
messages: [eg.streamMessage(content: "<p>a message</p>")]);
110103
final element = tester.element(find.byType(PerAccountStoreWidget));
111-
check(() => MessageListPage.composeBoxControllerOf(element))
104+
check(() => MessageListPage.ancestorOf(element))
112105
.throws<void>();
113106
});
107+
108+
testWidgets('composeBoxController finds compose box', (tester) async {
109+
final stream = eg.stream();
110+
await setupMessageListPage(tester, narrow: StreamNarrow(stream.streamId),
111+
messages: [eg.streamMessage(stream: stream, content: "<p>a message</p>")]);
112+
final state = MessageListPage.ancestorOf(tester.element(find.text("a message")));
113+
check(state.composeBoxController).isNotNull();
114+
});
115+
116+
testWidgets('composeBoxController null when no compose box', (tester) async {
117+
await setupMessageListPage(tester, narrow: const CombinedFeedNarrow(),
118+
messages: [eg.streamMessage(content: "<p>a message</p>")]);
119+
final state = MessageListPage.ancestorOf(tester.element(find.text("a message")));
120+
check(state.composeBoxController).isNull();
121+
});
114122
});
115123

116124
group('presents message content appropriately', () {

0 commit comments

Comments
 (0)