Skip to content

Commit 060208f

Browse files
committed
action_sheet: Fix rare null-check error on quote-and-reply
In 052f203 it became possible for the compose box to disappear, which would null out MessageListPageState._composeBoxKey.currentState. Anyway, a small and rare bug, but easy to fix.
1 parent ba6424f commit 060208f

File tree

2 files changed

+52
-6
lines changed

2 files changed

+52
-6
lines changed

lib/widgets/action_sheet.dart

+8-5
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,10 @@ class QuoteAndReplyButton extends MessageActionSheetMenuItemButton {
327327

328328
// This will be null only if the compose box disappeared after the
329329
// message action sheet opened, and before "Quote and reply" was pressed.
330-
// Currently a compose box can't ever disappear, so this is impossible.
331-
var composeBoxController = findMessageListPage().composeBoxController!;
330+
// This is rare: it happens when the self-user loses posting permission
331+
// or when a DM recipient becomes deactivated.
332+
var composeBoxController = findMessageListPage().composeBoxController;
333+
if (composeBoxController == null) return;
332334
final topicController = composeBoxController.topicController;
333335
if (
334336
topicController != null
@@ -354,9 +356,10 @@ class QuoteAndReplyButton extends MessageActionSheetMenuItemButton {
354356
if (!pageContext.mounted) return;
355357

356358
// This will be null only if the compose box disappeared during the
357-
// quotation request. Currently a compose box can't ever disappear,
358-
// so this is impossible.
359-
composeBoxController = findMessageListPage().composeBoxController!;
359+
// quotation request. This is rare: it happens when the self-user loses
360+
// posting permission or when a DM recipient becomes deactivated.
361+
composeBoxController = findMessageListPage().composeBoxController;
362+
if (composeBoxController == null) return;
360363
composeBoxController.contentController
361364
.registerQuoteAndReplyEnd(PerAccountStoreWidget.of(pageContext), tag,
362365
message: message,

test/widgets/action_sheet_test.dart

+44-1
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,16 @@ Future<void> setupToMessageActionSheet(WidgetTester tester, {
4343
required Narrow narrow,
4444
}) async {
4545
addTearDown(testBinding.reset);
46+
assert(narrow.containsMessage(message));
4647

4748
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
4849
store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
49-
await store.addUsers([eg.selfUser, eg.user(userId: message.senderId)]);
50+
await store.addUsers([
51+
eg.selfUser,
52+
eg.user(userId: message.senderId),
53+
if (narrow is DmNarrow)
54+
...narrow.otherRecipientIds.map((id) => eg.user(userId: id)),
55+
]);
5056
if (message is StreamMessage) {
5157
final stream = eg.stream(streamId: message.streamId);
5258
await store.addStream(stream);
@@ -313,6 +319,22 @@ void main() {
313319
checkSuccessState(store, contentController,
314320
valueBefore: valueBefore, message: message, rawContent: 'Hello world');
315321
});
322+
323+
testWidgets('no error if user lost posting permission after action sheet opened', (tester) async {
324+
final stream = eg.stream();
325+
final message = eg.streamMessage(stream: stream);
326+
await setupToMessageActionSheet(tester, message: message, narrow: TopicNarrow.ofMessage(message));
327+
328+
await store.handleEvent(RealmUserUpdateEvent(id: 1, userId: eg.selfUser.userId,
329+
role: UserRole.guest));
330+
await store.handleEvent(eg.channelUpdateEvent(stream,
331+
property: ChannelPropertyName.channelPostPolicy,
332+
value: ChannelPostPolicy.administrators));
333+
await tester.pump();
334+
335+
await tapQuoteAndReplyButton(tester);
336+
// no error
337+
});
316338
});
317339

318340
group('in DM narrow', () {
@@ -333,6 +355,27 @@ void main() {
333355
checkSuccessState(store, contentController,
334356
valueBefore: valueBefore, message: message, rawContent: 'Hello world');
335357
});
358+
359+
testWidgets('no error if recipient was deactivated while raw-content request in progress', (tester) async {
360+
final message = eg.dmMessage(from: eg.selfUser, to: [eg.otherUser]);
361+
await setupToMessageActionSheet(tester,
362+
message: message,
363+
narrow: DmNarrow.ofMessage(message, selfUserId: eg.selfUser.userId));
364+
365+
prepareRawContentResponseSuccess(
366+
message: message,
367+
rawContent: 'Hello world',
368+
delay: const Duration(seconds: 5),
369+
);
370+
await tapQuoteAndReplyButton(tester);
371+
await tester.pump(const Duration(seconds: 1)); // message not yet fetched
372+
373+
await store.handleEvent(RealmUserUpdateEvent(id: 1, userId: eg.otherUser.userId,
374+
isActive: false));
375+
await tester.pump();
376+
// no error
377+
await tester.pump(const Duration(seconds: 4));
378+
});
336379
});
337380

338381
testWidgets('request has an error', (tester) async {

0 commit comments

Comments
 (0)