Skip to content

Commit dc305b2

Browse files
committed
compose: Show error dialog on failure of edit-message request
I'm not finding the discussion where we said we wanted this (for consistency with the send-message UX), but I think we did. Error-feedback logic copied from the tap-"Send" button (_SendButtonState._send) modulo the error dialog's title, "Message not saved" vs. "Message not sent".
1 parent 85ebff1 commit dc305b2

File tree

6 files changed

+52
-17
lines changed

6 files changed

+52
-17
lines changed

lib/model/message.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ class MessageStoreImpl extends HasRealmStore with MessageStore, _OutboxMessageSt
352352
}
353353
status.hasError = true;
354354
_notifyMessageListViewsForOneMessage(messageId);
355+
rethrow;
355356
}
356357
}
357358

lib/widgets/compose_box.dart

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,7 +1774,7 @@ class _EditMessageBanner extends _Banner {
17741774
Color getBackgroundColor(DesignVariables designVariables) =>
17751775
designVariables.bannerBgIntInfo;
17761776

1777-
void _handleTapSave (BuildContext pageContext) {
1777+
void _handleTapSave (BuildContext pageContext) async {
17781778
final store = PerAccountStoreWidget.of(pageContext);
17791779
final controller = composeBoxState.controller;
17801780
if (controller is! EditMessageComposeBoxController) return; // TODO(log)
@@ -1800,10 +1800,24 @@ class _EditMessageBanner extends _Banner {
18001800
final messageId = controller.messageId;
18011801
final newContent = controller.content.textNormalized;
18021802
composeBoxState.endEditInteraction();
1803-
store.editMessage(
1804-
messageId: messageId,
1805-
originalRawContent: originalRawContent,
1806-
newContent: newContent);
1803+
1804+
try {
1805+
await store.editMessage(
1806+
messageId: messageId,
1807+
originalRawContent: originalRawContent,
1808+
newContent: newContent);
1809+
} on ApiRequestException catch (e) {
1810+
if (!pageContext.mounted) return;
1811+
final zulipLocalizations = ZulipLocalizations.of(pageContext);
1812+
final message = switch (e) {
1813+
ZulipApiException() => zulipLocalizations.errorServerMessage(e.message),
1814+
_ => e.message,
1815+
};
1816+
showErrorDialog(context: pageContext,
1817+
title: zulipLocalizations.errorMessageEditNotSaved,
1818+
message: message);
1819+
return;
1820+
}
18071821
}
18081822

18091823
@override

test/model/message_test.dart

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:crypto/crypto.dart';
77
import 'package:fake_async/fake_async.dart';
88
import 'package:http/http.dart' as http;
99
import 'package:test/scaffolding.dart';
10+
import 'package:zulip/api/exception.dart';
1011
import 'package:zulip/api/model/events.dart';
1112
import 'package:zulip/api/model/model.dart';
1213
import 'package:zulip/api/model/submessage.dart';
@@ -682,8 +683,8 @@ void main() {
682683
check(store.getEditMessageErrorStatus(message.id)).isNull();
683684

684685
connection.prepare(apiException: eg.apiBadRequest(), delay: Duration(seconds: 1));
685-
unawaited(store.editMessage(messageId: message.id,
686-
originalRawContent: 'old content', newContent: 'new content'));
686+
unawaited(check(store.editMessage(messageId: message.id,
687+
originalRawContent: 'old content', newContent: 'new content')).throws<ZulipApiException>());
687688
checkNotifiedOnce();
688689
async.elapse(Duration(seconds: 1));
689690
check(store.getEditMessageErrorStatus(message.id)).isNotNull().isTrue();
@@ -695,8 +696,8 @@ void main() {
695696
check(store.getEditMessageErrorStatus(message.id)).isNull();
696697

697698
connection.prepare(apiException: eg.apiBadRequest(), delay: Duration(seconds: 1));
698-
unawaited(store.editMessage(messageId: message.id,
699-
originalRawContent: 'old content', newContent: 'new content'));
699+
unawaited(check(store.editMessage(messageId: message.id,
700+
originalRawContent: 'old content', newContent: 'new content')).throws<ZulipApiException>());
700701
checkNotifiedOnce();
701702
async.elapse(Duration(seconds: 1));
702703
check(store.getEditMessageErrorStatus(message.id)).isNotNull().isTrue();
@@ -760,8 +761,8 @@ void main() {
760761

761762
connection.prepare(
762763
httpException: const SocketException('failed'), delay: Duration(seconds: 1));
763-
unawaited(store.editMessage(messageId: message.id,
764-
originalRawContent: 'old content', newContent: 'new content'));
764+
unawaited(check(store.editMessage(messageId: message.id,
765+
originalRawContent: 'old content', newContent: 'new content')).throws<NetworkException>());
765766
checkNotifiedOnce();
766767

767768
async.elapse(Duration(seconds: 1));
@@ -781,8 +782,8 @@ void main() {
781782

782783
connection.prepare(
783784
httpException: const SocketException('failed'), delay: Duration(seconds: 1));
784-
unawaited(store.editMessage(messageId: message.id,
785-
originalRawContent: 'old content', newContent: 'new content'));
785+
unawaited(check(store.editMessage(messageId: message.id,
786+
originalRawContent: 'old content', newContent: 'new content')).throws<NetworkException>());
786787
checkNotifiedOnce();
787788

788789
async.elapse(Duration(seconds: 1));
@@ -801,8 +802,8 @@ void main() {
801802
check(store.getEditMessageErrorStatus(message.id)).isNull();
802803

803804
connection.prepare(apiException: eg.apiBadRequest(), delay: Duration(seconds: 1));
804-
unawaited(store.editMessage(messageId: message.id,
805-
originalRawContent: 'old content', newContent: 'new content'));
805+
unawaited(check(store.editMessage(messageId: message.id,
806+
originalRawContent: 'old content', newContent: 'new content')).throws<ZulipApiException>());
806807
checkNotifiedOnce();
807808
async.elapse(Duration(seconds: 1));
808809
check(store.getEditMessageErrorStatus(message.id)).isNotNull().isTrue();

test/widgets/action_sheet_test.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,12 @@ void main() {
17801780
await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e
17811781
}
17821782

1783+
Future<void> takeErrorDialogAndPump(WidgetTester tester) async {
1784+
final errorDialog = checkErrorDialog(tester, expectedTitle: 'Message not saved');
1785+
await tester.tap(find.byWidget(errorDialog));
1786+
await tester.pump();
1787+
}
1788+
17831789
group('present/absent appropriately', () {
17841790
/// Test whether the edit-message button is visible, given params.
17851791
///
@@ -1870,6 +1876,7 @@ void main() {
18701876
connection.prepare(apiException: eg.apiBadRequest());
18711877
await tester.tap(find.widgetWithText(ZulipWebUiKitButton, 'Save'));
18721878
await tester.pump(Duration.zero);
1879+
await takeErrorDialogAndPump(tester);
18731880
} else if (errorStatus == false) {
18741881
// We're testing the request-in-progress state. Prepare a delay,
18751882
// tap Save, and wait through only part of the delay.

test/widgets/compose_box_test.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1748,6 +1748,12 @@ void main() {
17481748
await tester.pump(); // message list updates
17491749
}
17501750

1751+
Future<void> takeErrorDialogAndPump(WidgetTester tester) async {
1752+
final errorDialog = checkErrorDialog(tester, expectedTitle: 'Message not saved');
1753+
await tester.tap(find.byWidget(errorDialog));
1754+
await tester.pump();
1755+
}
1756+
17511757
/// Check that the compose box is in the "Preparing…" state,
17521758
/// awaiting the fetch-raw-content request.
17531759
Future<void> checkAwaitingRawMessageContent(WidgetTester tester) async {
@@ -1770,6 +1776,7 @@ void main() {
17701776
await tester.tap(
17711777
find.widgetWithText(ZulipWebUiKitButton, 'Save'), warnIfMissed: false);
17721778
await tester.pump(Duration.zero);
1779+
checkNoDialog(tester);
17731780
check(connection.lastRequest).equals(lastRequest);
17741781
}
17751782

@@ -1788,6 +1795,7 @@ void main() {
17881795
connection.prepare(apiException: eg.apiBadRequest());
17891796
await tester.tap(find.widgetWithText(ZulipWebUiKitButton, 'Save'));
17901797
await tester.pump(Duration.zero);
1798+
await takeErrorDialogAndPump(tester);
17911799
await tester.tap(find.text('EDIT NOT SAVED'));
17921800
await tester.pump();
17931801
connection.takeRequests();
@@ -1966,6 +1974,7 @@ void main() {
19661974
await tester.tap(find.widgetWithText(ZulipWebUiKitButton, 'Save'));
19671975
connection.takeRequests();
19681976
await tester.pump(Duration.zero);
1977+
await takeErrorDialogAndPump(tester);
19691978
checkNotInEditingMode(tester, narrow: narrow);
19701979
check(find.text('EDIT NOT SAVED')).findsOne();
19711980

test/widgets/message_list_test.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart';
1010
import 'package:flutter_checks/flutter_checks.dart';
1111
import 'package:flutter_test/flutter_test.dart';
1212
import 'package:http/http.dart' as http;
13+
import 'package:zulip/api/exception.dart';
1314
import 'package:zulip/api/model/events.dart';
1415
import 'package:zulip/api/model/initial_snapshot.dart';
1516
import 'package:zulip/api/model/model.dart';
@@ -2292,12 +2293,14 @@ void main() {
22922293
messages: [message]);
22932294

22942295
connection.prepare(apiException: eg.apiBadRequest(), delay: Duration(seconds: 1));
2295-
unawaited(store.editMessage(messageId: message.id,
2296+
unawaited(check(store.editMessage(messageId: message.id,
22962297
originalRawContent: 'foo',
2297-
newContent: 'bar'));
2298+
newContent: 'bar')).throws<ZulipApiException>());
22982299
await tester.pump(Duration.zero);
22992300
checkEditInProgress(tester);
23002301
await tester.pump(Duration(seconds: 1));
2302+
// (the error dialog is tested elsewhere;
2303+
// it's triggered in the "Save" tap handler, not store.editMessage)
23012304
checkEditFailed(tester);
23022305

23032306
connection.prepare(json: GetMessageResult(

0 commit comments

Comments
 (0)