From bb2055402fe00af1b8a38142ae5c7ebbda20b323 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 26 May 2025 19:45:20 -0400 Subject: [PATCH 1/7] msglist: Use store.senderDisplayName for sender row [chris: added tests] Co-authored-by: Chris Bobbe --- lib/widgets/message_list.dart | 2 +- test/widgets/message_list_test.dart | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index 2c24638f86..aaf881905b 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -1446,7 +1446,7 @@ class _SenderRow extends StatelessWidget { userId: message.senderId), const SizedBox(width: 8), Flexible( - child: Text(message.senderFullName, // TODO(#716): use `store.senderDisplayName` + child: Text(store.senderDisplayName(message), style: TextStyle( fontSize: 18, height: (22 / 18), diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart index 69aa693706..81cc384ef8 100644 --- a/test/widgets/message_list_test.dart +++ b/test/widgets/message_list_test.dart @@ -1443,6 +1443,30 @@ void main() { }); group('MessageWithPossibleSender', () { + testWidgets('known user', (tester) async { + final user = eg.user(fullName: 'Old Name'); + await setupMessageListPage(tester, + messages: [eg.streamMessage(sender: user)], + users: [user]); + + check(find.widgetWithText(MessageWithPossibleSender, 'Old Name')).findsOne(); + + // If the user's name changes, the sender row should update. + await store.handleEvent(RealmUserUpdateEvent(id: 1, + userId: user.userId, fullName: 'New Name')); + await tester.pump(); + check(find.widgetWithText(MessageWithPossibleSender, 'New Name')).findsOne(); + }); + + testWidgets('unknown user', (tester) async { + final user = eg.user(fullName: 'Some User'); + await setupMessageListPage(tester, messages: [eg.streamMessage(sender: user)]); + check(store.getUser(user.userId)).isNull(); + + // The sender row should fall back to the name in the message. + check(find.widgetWithText(MessageWithPossibleSender, 'Some User')).findsOne(); + }); + testWidgets('Updates avatar on RealmUserUpdateEvent', (tester) async { addTearDown(testBinding.reset); From a2686955ffa36ea95f423a0fb5cfee2e0ada4f34 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 26 May 2025 19:46:57 -0400 Subject: [PATCH 2/7] msglist [nfc]: Make _SenderRow accept MessageBase [chris: removed unused import] Co-authored-by: Chris Bobbe --- lib/widgets/message_list.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index aaf881905b..e641a2519b 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -1416,7 +1416,7 @@ final _kMessageTimestampFormat = DateFormat('h:mm aa', 'en_US'); class _SenderRow extends StatelessWidget { const _SenderRow({required this.message, required this.showTimestamp}); - final Message message; + final MessageBase message; final bool showTimestamp; @override @@ -1446,7 +1446,9 @@ class _SenderRow extends StatelessWidget { userId: message.senderId), const SizedBox(width: 8), Flexible( - child: Text(store.senderDisplayName(message), + child: Text(message is Message + ? store.senderDisplayName(message as Message) + : store.userDisplayName(message.senderId), style: TextStyle( fontSize: 18, height: (22 / 18), From 33c97790b4276ed75378cd6cd424580ae8e2ddda Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 26 May 2025 17:51:09 -0400 Subject: [PATCH 3/7] compose [nfc]: Make confirmation dialog message flexible [chris: small formatting/naming changes] Co-authored-by: Chris Bobbe --- lib/widgets/compose_box.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/widgets/compose_box.dart b/lib/widgets/compose_box.dart index d629e69029..320be5b155 100644 --- a/lib/widgets/compose_box.dart +++ b/lib/widgets/compose_box.dart @@ -1849,11 +1849,13 @@ class _ComposeBoxState extends State with PerAccountStoreAwareStateM @override void startEditInteraction(int messageId) async { - if (await _abortBecauseContentInputNotEmpty()) return; - if (!mounted) return; + final zulipLocalizations = ZulipLocalizations.of(context); + + final abort = await _abortBecauseContentInputNotEmpty( + dialogMessage: zulipLocalizations.discardDraftConfirmationDialogMessage); + if (abort || !mounted) return; final store = PerAccountStoreWidget.of(context); - final zulipLocalizations = ZulipLocalizations.of(context); switch (store.getEditMessageErrorStatus(messageId)) { case null: @@ -1878,12 +1880,14 @@ class _ComposeBoxState extends State with PerAccountStoreAwareStateM /// If there's text in the compose box, give a confirmation dialog /// asking if it can be discarded and await the result. - Future _abortBecauseContentInputNotEmpty() async { + Future _abortBecauseContentInputNotEmpty({ + required String dialogMessage, + }) async { final zulipLocalizations = ZulipLocalizations.of(context); if (controller.content.textNormalized.isNotEmpty) { final dialog = showSuggestedActionDialog(context: context, title: zulipLocalizations.discardDraftConfirmationDialogTitle, - message: zulipLocalizations.discardDraftConfirmationDialogMessage, + message: dialogMessage, // TODO(#1032) "destructive" style for action button actionButtonText: zulipLocalizations.discardDraftConfirmationDialogConfirmButton); if (await dialog.result != true) return true; From efe86b48d9c64026f7b0dbd27e806d79051f5d5d Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 26 May 2025 18:30:24 -0400 Subject: [PATCH 4/7] compose test [nfc]: Move some edit message helpers out of group Helpers for starting an edit interaction and dealing with the confirmation dialog will be useful as we support retrieving messages not sent. --- test/widgets/compose_box_test.dart | 106 ++++++++++++++--------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/test/widgets/compose_box_test.dart b/test/widgets/compose_box_test.dart index 11467cea7d..b8ad524e85 100644 --- a/test/widgets/compose_box_test.dart +++ b/test/widgets/compose_box_test.dart @@ -1412,6 +1412,51 @@ void main() { }); }); + /// Starts an edit interaction from the action sheet's 'Edit message' button. + /// + /// The fetch-raw-content request is prepared with [delay] (default 1s). + Future startEditInteractionFromActionSheet( + WidgetTester tester, { + required int messageId, + String originalRawContent = 'foo', + Duration delay = const Duration(seconds: 1), + bool fetchShouldSucceed = true, + }) async { + await tester.longPress(find.byWidgetPredicate((widget) => + widget is MessageWithPossibleSender && widget.item.message.id == messageId)); + // sheet appears onscreen; default duration of bottom-sheet enter animation + await tester.pump(const Duration(milliseconds: 250)); + final findEditButton = find.descendant( + of: find.byType(BottomSheet), + matching: find.byIcon(ZulipIcons.edit, skipOffstage: false)); + await tester.ensureVisible(findEditButton); + if (fetchShouldSucceed) { + connection.prepare(delay: delay, + json: GetMessageResult(message: eg.streamMessage(content: originalRawContent)).toJson()); + } else { + connection.prepare(apiException: eg.apiBadRequest(), delay: delay); + } + await tester.tap(findEditButton); + await tester.pump(); + await tester.pump(); + connection.takeRequests(); + } + + Future expectAndHandleDiscardConfirmation( + WidgetTester tester, { + required bool shouldContinue, + }) async { + final (actionButton, cancelButton) = checkSuggestedActionDialog(tester, + expectedTitle: 'Discard the message you’re writing?', + expectedMessage: 'When you edit a message, the content that was previously in the compose box is discarded.', + expectedActionButtonText: 'Discard'); + if (shouldContinue) { + await tester.tap(find.byWidget(actionButton)); + } else { + await tester.tap(find.byWidget(cancelButton)); + } + } + group('edit message', () { final channel = eg.stream(); final topic = 'topic'; @@ -1464,36 +1509,6 @@ void main() { check(connection.lastRequest).equals(lastRequest); } - /// Starts an interaction from the action sheet's 'Edit message' button. - /// - /// The fetch-raw-content request is prepared with [delay] (default 1s). - Future startInteractionFromActionSheet( - WidgetTester tester, { - required int messageId, - String originalRawContent = 'foo', - Duration delay = const Duration(seconds: 1), - bool fetchShouldSucceed = true, - }) async { - await tester.longPress(find.byWidgetPredicate((widget) => - widget is MessageWithPossibleSender && widget.item.message.id == messageId)); - // sheet appears onscreen; default duration of bottom-sheet enter animation - await tester.pump(const Duration(milliseconds: 250)); - final findEditButton = find.descendant( - of: find.byType(BottomSheet), - matching: find.byIcon(ZulipIcons.edit, skipOffstage: false)); - await tester.ensureVisible(findEditButton); - if (fetchShouldSucceed) { - connection.prepare(delay: delay, - json: GetMessageResult(message: eg.streamMessage(content: originalRawContent)).toJson()); - } else { - connection.prepare(apiException: eg.apiBadRequest(), delay: delay); - } - await tester.tap(findEditButton); - await tester.pump(); - await tester.pump(); - connection.takeRequests(); - } - /// Starts an interaction by tapping a failed edit in the message list. Future startInteractionFromRestoreFailedEdit( WidgetTester tester, { @@ -1501,7 +1516,7 @@ void main() { String originalRawContent = 'foo', String newContent = 'bar', }) async { - await startInteractionFromActionSheet(tester, + await startEditInteractionFromActionSheet(tester, messageId: messageId, originalRawContent: originalRawContent); await tester.pump(Duration(seconds: 1)); // raw-content request await enterContent(tester, newContent); @@ -1557,7 +1572,7 @@ void main() { final messageId = msgIdInNarrow(narrow); switch (start) { case _EditInteractionStart.actionSheet: - await startInteractionFromActionSheet(tester, + await startEditInteractionFromActionSheet(tester, messageId: messageId, originalRawContent: 'foo'); await checkAwaitingRawMessageContent(tester); @@ -1608,21 +1623,6 @@ void main() { testSmoke(narrow: topicNarrow, start: _EditInteractionStart.restoreFailedEdit); testSmoke(narrow: dmNarrow, start: _EditInteractionStart.restoreFailedEdit); - Future expectAndHandleDiscardConfirmation( - WidgetTester tester, { - required bool shouldContinue, - }) async { - final (actionButton, cancelButton) = checkSuggestedActionDialog(tester, - expectedTitle: 'Discard the message you’re writing?', - expectedMessage: 'When you edit a message, the content that was previously in the compose box is discarded.', - expectedActionButtonText: 'Discard'); - if (shouldContinue) { - await tester.tap(find.byWidget(actionButton)); - } else { - await tester.tap(find.byWidget(cancelButton)); - } - } - // Test the "Discard…?" confirmation dialog when you tap "Edit message" in // the action sheet but there's text in the compose box for a new message. void testInterruptComposingFromActionSheet({required Narrow narrow}) { @@ -1637,7 +1637,7 @@ void main() { await enterContent(tester, 'composing new message'); // Expect confirmation dialog; tap Cancel - await startInteractionFromActionSheet(tester, messageId: messageId); + await startEditInteractionFromActionSheet(tester, messageId: messageId); await expectAndHandleDiscardConfirmation(tester, shouldContinue: false); check(connection.takeRequests()).isEmpty(); // fetch-raw-content request wasn't actually sent; @@ -1651,7 +1651,7 @@ void main() { checkContentInputValue(tester, 'composing new message…'); // Try again, but this time tap Discard and expect to enter an edit session - await startInteractionFromActionSheet(tester, + await startEditInteractionFromActionSheet(tester, messageId: messageId, originalRawContent: 'foo'); await expectAndHandleDiscardConfirmation(tester, shouldContinue: true); await tester.pump(); @@ -1685,7 +1685,7 @@ void main() { final messageId = msgIdInNarrow(narrow); await prepareEditMessage(tester, narrow: narrow); - await startInteractionFromActionSheet(tester, + await startEditInteractionFromActionSheet(tester, messageId: messageId, originalRawContent: 'foo'); await tester.pump(Duration(seconds: 1)); // raw-content request await enterContent(tester, 'bar'); @@ -1742,7 +1742,7 @@ void main() { checkNotInEditingMode(tester, narrow: narrow); final messageId = msgIdInNarrow(narrow); - await startInteractionFromActionSheet(tester, + await startEditInteractionFromActionSheet(tester, messageId: messageId, originalRawContent: 'foo', fetchShouldSucceed: false); @@ -1790,7 +1790,7 @@ void main() { final messageId = msgIdInNarrow(narrow); switch (start) { case _EditInteractionStart.actionSheet: - await startInteractionFromActionSheet(tester, + await startEditInteractionFromActionSheet(tester, messageId: messageId, delay: Duration(seconds: 5)); await checkAwaitingRawMessageContent(tester); await tester.pump(duringFetchRawContentRequest! @@ -1809,7 +1809,7 @@ void main() { // We've canceled the previous edit session, so we should be able to // do a new edit-message session… - await startInteractionFromActionSheet(tester, + await startEditInteractionFromActionSheet(tester, messageId: messageId, originalRawContent: 'foo'); await checkAwaitingRawMessageContent(tester); await tester.pump(Duration(seconds: 1)); // fetch-raw-content request From 47680e91a507b79666956ec461d87b36a2315988 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 26 May 2025 17:55:54 -0400 Subject: [PATCH 5/7] compose [nfc]: Update string to mention "edit" in name and description This migrates the Polish and Russian translations (the only existing non-en locales to translate this) to use the new string as well. --- assets/l10n/app_en.arb | 6 +++--- assets/l10n/app_pl.arb | 6 +++--- assets/l10n/app_ru.arb | 4 ++-- lib/generated/l10n/zulip_localizations.dart | 4 ++-- .../l10n/zulip_localizations_ar.dart | 2 +- .../l10n/zulip_localizations_de.dart | 2 +- .../l10n/zulip_localizations_en.dart | 2 +- .../l10n/zulip_localizations_ja.dart | 2 +- .../l10n/zulip_localizations_nb.dart | 2 +- .../l10n/zulip_localizations_pl.dart | 2 +- .../l10n/zulip_localizations_ru.dart | 2 +- .../l10n/zulip_localizations_sk.dart | 2 +- .../l10n/zulip_localizations_uk.dart | 2 +- .../l10n/zulip_localizations_zh.dart | 2 +- lib/widgets/compose_box.dart | 2 +- test/widgets/compose_box_test.dart | 19 ++++++++++++++----- 16 files changed, 35 insertions(+), 26 deletions(-) diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 91206fabc6..55542171cf 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -377,9 +377,9 @@ "@discardDraftConfirmationDialogTitle": { "description": "Title for a confirmation dialog for discarding message text that was typed into the compose box." }, - "discardDraftConfirmationDialogMessage": "When you edit a message, the content that was previously in the compose box is discarded.", - "@discardDraftConfirmationDialogMessage": { - "description": "Message for a confirmation dialog for discarding message text that was typed into the compose box." + "discardDraftForEditConfirmationDialogMessage": "When you edit a message, the content that was previously in the compose box is discarded.", + "@discardDraftForEditConfirmationDialogMessage": { + "description": "Message for a confirmation dialog for discarding message text that was typed into the compose box, when editing a message." }, "discardDraftConfirmationDialogConfirmButton": "Discard", "@discardDraftConfirmationDialogConfirmButton": { diff --git a/assets/l10n/app_pl.arb b/assets/l10n/app_pl.arb index 468e2136b2..d02f55a4f5 100644 --- a/assets/l10n/app_pl.arb +++ b/assets/l10n/app_pl.arb @@ -1049,9 +1049,9 @@ "@discardDraftConfirmationDialogTitle": { "description": "Title for a confirmation dialog for discarding message text that was typed into the compose box." }, - "discardDraftConfirmationDialogMessage": "Miej na uwadze, że przechodząc do zmiany wiadomości wyczyścisz okno nowej wiadomości.", - "@discardDraftConfirmationDialogMessage": { - "description": "Message for a confirmation dialog for discarding message text that was typed into the compose box." + "discardDraftForEditConfirmationDialogMessage": "Miej na uwadze, że przechodząc do zmiany wiadomości wyczyścisz okno nowej wiadomości.", + "@discardDraftForEditConfirmationDialogMessage": { + "description": "Message for a confirmation dialog for discarding message text that was typed into the compose box, when editing a message." }, "discardDraftConfirmationDialogConfirmButton": "Odrzuć", "@discardDraftConfirmationDialogConfirmButton": { diff --git a/assets/l10n/app_ru.arb b/assets/l10n/app_ru.arb index bf36f0c900..3e5ac1ec3c 100644 --- a/assets/l10n/app_ru.arb +++ b/assets/l10n/app_ru.arb @@ -1069,8 +1069,8 @@ "@editAlreadyInProgressMessage": { "description": "Error message when a message edit cannot be saved because there is another edit already in progress." }, - "discardDraftConfirmationDialogMessage": "При изменении сообщения текст из поля для редактирования удаляется.", - "@discardDraftConfirmationDialogMessage": { + "discardDraftForEditConfirmationDialogMessage": "При изменении сообщения текст из поля для редактирования удаляется.", + "@discardDraftForEditConfirmationDialogMessage": { "description": "Message for a confirmation dialog for discarding message text that was typed into the compose box." } } diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index de0368bb6d..6f8b6315b4 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -643,11 +643,11 @@ abstract class ZulipLocalizations { /// **'Discard the message you’re writing?'** String get discardDraftConfirmationDialogTitle; - /// Message for a confirmation dialog for discarding message text that was typed into the compose box. + /// Message for a confirmation dialog for discarding message text that was typed into the compose box, when editing a message. /// /// In en, this message translates to: /// **'When you edit a message, the content that was previously in the compose box is discarded.'** - String get discardDraftConfirmationDialogMessage; + String get discardDraftForEditConfirmationDialogMessage; /// Label for the 'Discard' button on a confirmation dialog for discarding message text that was typed into the compose box. /// diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index febbb2c585..57f236bd03 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -318,7 +318,7 @@ class ZulipLocalizationsAr extends ZulipLocalizations { 'Discard the message you’re writing?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'When you edit a message, the content that was previously in the compose box is discarded.'; @override diff --git a/lib/generated/l10n/zulip_localizations_de.dart b/lib/generated/l10n/zulip_localizations_de.dart index a9188773c0..e0ac848b7d 100644 --- a/lib/generated/l10n/zulip_localizations_de.dart +++ b/lib/generated/l10n/zulip_localizations_de.dart @@ -318,7 +318,7 @@ class ZulipLocalizationsDe extends ZulipLocalizations { 'Discard the message you’re writing?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'When you edit a message, the content that was previously in the compose box is discarded.'; @override diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index 73a4212ee3..8a399bdbaf 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -318,7 +318,7 @@ class ZulipLocalizationsEn extends ZulipLocalizations { 'Discard the message you’re writing?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'When you edit a message, the content that was previously in the compose box is discarded.'; @override diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index b614f71cbb..a33b115bb7 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -318,7 +318,7 @@ class ZulipLocalizationsJa extends ZulipLocalizations { 'Discard the message you’re writing?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'When you edit a message, the content that was previously in the compose box is discarded.'; @override diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index 4929eae453..3fabc54006 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -318,7 +318,7 @@ class ZulipLocalizationsNb extends ZulipLocalizations { 'Discard the message you’re writing?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'When you edit a message, the content that was previously in the compose box is discarded.'; @override diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index 3af0e94f46..dae93fa495 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -325,7 +325,7 @@ class ZulipLocalizationsPl extends ZulipLocalizations { 'Czy chcesz przerwać szykowanie wpisu?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'Miej na uwadze, że przechodząc do zmiany wiadomości wyczyścisz okno nowej wiadomości.'; @override diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index 72a87e6c32..9dc9fa3b0b 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -326,7 +326,7 @@ class ZulipLocalizationsRu extends ZulipLocalizations { 'Отказаться от написанного сообщения?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'При изменении сообщения текст из поля для редактирования удаляется.'; @override diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index 30cd4c89f8..5436670934 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -318,7 +318,7 @@ class ZulipLocalizationsSk extends ZulipLocalizations { 'Discard the message you’re writing?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'When you edit a message, the content that was previously in the compose box is discarded.'; @override diff --git a/lib/generated/l10n/zulip_localizations_uk.dart b/lib/generated/l10n/zulip_localizations_uk.dart index a77d28aeff..7deabdf03d 100644 --- a/lib/generated/l10n/zulip_localizations_uk.dart +++ b/lib/generated/l10n/zulip_localizations_uk.dart @@ -327,7 +327,7 @@ class ZulipLocalizationsUk extends ZulipLocalizations { 'Discard the message you’re writing?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'When you edit a message, the content that was previously in the compose box is discarded.'; @override diff --git a/lib/generated/l10n/zulip_localizations_zh.dart b/lib/generated/l10n/zulip_localizations_zh.dart index 59c3a57129..13d65a499b 100644 --- a/lib/generated/l10n/zulip_localizations_zh.dart +++ b/lib/generated/l10n/zulip_localizations_zh.dart @@ -318,7 +318,7 @@ class ZulipLocalizationsZh extends ZulipLocalizations { 'Discard the message you’re writing?'; @override - String get discardDraftConfirmationDialogMessage => + String get discardDraftForEditConfirmationDialogMessage => 'When you edit a message, the content that was previously in the compose box is discarded.'; @override diff --git a/lib/widgets/compose_box.dart b/lib/widgets/compose_box.dart index 320be5b155..2f47f05f44 100644 --- a/lib/widgets/compose_box.dart +++ b/lib/widgets/compose_box.dart @@ -1852,7 +1852,7 @@ class _ComposeBoxState extends State with PerAccountStoreAwareStateM final zulipLocalizations = ZulipLocalizations.of(context); final abort = await _abortBecauseContentInputNotEmpty( - dialogMessage: zulipLocalizations.discardDraftConfirmationDialogMessage); + dialogMessage: zulipLocalizations.discardDraftForEditConfirmationDialogMessage); if (abort || !mounted) return; final store = PerAccountStoreWidget.of(context); diff --git a/test/widgets/compose_box_test.dart b/test/widgets/compose_box_test.dart index b8ad524e85..b4de306aa3 100644 --- a/test/widgets/compose_box_test.dart +++ b/test/widgets/compose_box_test.dart @@ -1444,11 +1444,12 @@ void main() { Future expectAndHandleDiscardConfirmation( WidgetTester tester, { + required String expectedMessage, required bool shouldContinue, }) async { final (actionButton, cancelButton) = checkSuggestedActionDialog(tester, expectedTitle: 'Discard the message you’re writing?', - expectedMessage: 'When you edit a message, the content that was previously in the compose box is discarded.', + expectedMessage: expectedMessage, expectedActionButtonText: 'Discard'); if (shouldContinue) { await tester.tap(find.byWidget(actionButton)); @@ -1623,6 +1624,14 @@ void main() { testSmoke(narrow: topicNarrow, start: _EditInteractionStart.restoreFailedEdit); testSmoke(narrow: dmNarrow, start: _EditInteractionStart.restoreFailedEdit); + Future expectAndHandleDiscardForEditConfirmation(WidgetTester tester, { + required bool shouldContinue, + }) { + return expectAndHandleDiscardConfirmation(tester, + expectedMessage: 'When you edit a message, the content that was previously in the compose box is discarded.', + shouldContinue: shouldContinue); + } + // Test the "Discard…?" confirmation dialog when you tap "Edit message" in // the action sheet but there's text in the compose box for a new message. void testInterruptComposingFromActionSheet({required Narrow narrow}) { @@ -1638,7 +1647,7 @@ void main() { // Expect confirmation dialog; tap Cancel await startEditInteractionFromActionSheet(tester, messageId: messageId); - await expectAndHandleDiscardConfirmation(tester, shouldContinue: false); + await expectAndHandleDiscardForEditConfirmation(tester, shouldContinue: false); check(connection.takeRequests()).isEmpty(); // fetch-raw-content request wasn't actually sent; // take back its prepared response @@ -1653,7 +1662,7 @@ void main() { // Try again, but this time tap Discard and expect to enter an edit session await startEditInteractionFromActionSheet(tester, messageId: messageId, originalRawContent: 'foo'); - await expectAndHandleDiscardConfirmation(tester, shouldContinue: true); + await expectAndHandleDiscardForEditConfirmation(tester, shouldContinue: true); await tester.pump(); await checkAwaitingRawMessageContent(tester); await tester.pump(Duration(seconds: 1)); // fetch-raw-content request @@ -1702,7 +1711,7 @@ void main() { // Expect confirmation dialog; tap Cancel await tester.tap(find.text('EDIT NOT SAVED')); await tester.pump(); - await expectAndHandleDiscardConfirmation(tester, shouldContinue: false); + await expectAndHandleDiscardForEditConfirmation(tester, shouldContinue: false); checkNotInEditingMode(tester, narrow: narrow, expectedContentText: 'composing new message'); @@ -1712,7 +1721,7 @@ void main() { // Try again, but this time tap Discard and expect to enter edit session await tester.tap(find.text('EDIT NOT SAVED')); await tester.pump(); - await expectAndHandleDiscardConfirmation(tester, shouldContinue: true); + await expectAndHandleDiscardForEditConfirmation(tester, shouldContinue: true); await tester.pump(); checkContentInputValue(tester, 'bar'); await enterContent(tester, 'baz'); From 145e99f6a692606817d5a7ef9425d60403c34d3f Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Thu, 29 May 2025 16:40:52 -0700 Subject: [PATCH 6/7] msglist [nfc]: Distribute a 4px bottom padding down to conditional cases To prepare for adjusting one of the cases from 4px to 2px: https://github.com/zulip/zulip-flutter/pull/1535#discussion_r2114820185 --- lib/widgets/message_list.dart | 70 +++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index e641a2519b..b82830e6cc 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -1537,7 +1537,7 @@ class MessageWithPossibleSender extends StatelessWidget { behavior: HitTestBehavior.translucent, onLongPress: () => showMessageActionSheet(context: context, message: message), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4), + padding: const EdgeInsets.only(top: 4), child: Column(children: [ if (item.showSender) _SenderRow(message: message, showTimestamp: true), @@ -1555,14 +1555,18 @@ class MessageWithPossibleSender extends StatelessWidget { if (editMessageErrorStatus != null) _EditMessageStatusRow(messageId: message.id, status: editMessageErrorStatus) else if (editStateText != null) - Text(editStateText, - textAlign: TextAlign.end, - style: TextStyle( - color: designVariables.labelEdited, - fontSize: 12, - height: (12 / 12), - letterSpacing: proportionalLetterSpacing( - context, 0.05, baseFontSize: 12))), + Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Text(editStateText, + textAlign: TextAlign.end, + style: TextStyle( + color: designVariables.labelEdited, + fontSize: 12, + height: (12 / 12), + letterSpacing: proportionalLetterSpacing(context, + 0.05, baseFontSize: 12)))) + else + Padding(padding: const EdgeInsets.only(bottom: 4)) ])), SizedBox(width: 16, child: star), @@ -1593,30 +1597,34 @@ class _EditMessageStatusRow extends StatelessWidget { return switch (status) { // TODO parse markdown and show new content as local echo? - false => Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - spacing: 1.5, - children: [ - Text( + false => Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 1.5, + children: [ + Text( + style: baseTextStyle + .copyWith(color: designVariables.btnLabelAttLowIntInfo), + textAlign: TextAlign.end, + zulipLocalizations.savingMessageEditLabel), + // TODO instead place within bottom outer padding: + // https://github.com/zulip/zulip-flutter/pull/1498#discussion_r2087576108 + LinearProgressIndicator( + minHeight: 2, + color: designVariables.foreground.withValues(alpha: 0.5), + backgroundColor: designVariables.foreground.withValues(alpha: 0.2), + ), + ])), + true => Padding( + padding: const EdgeInsets.only(bottom: 4), + child: _RestoreEditMessageGestureDetector( + messageId: messageId, + child: Text( style: baseTextStyle - .copyWith(color: designVariables.btnLabelAttLowIntInfo), + .copyWith(color: designVariables.btnLabelAttLowIntDanger), textAlign: TextAlign.end, - zulipLocalizations.savingMessageEditLabel), - // TODO instead place within bottom outer padding: - // https://github.com/zulip/zulip-flutter/pull/1498#discussion_r2087576108 - LinearProgressIndicator( - minHeight: 2, - color: designVariables.foreground.withValues(alpha: 0.5), - backgroundColor: designVariables.foreground.withValues(alpha: 0.2), - ), - ]), - true => _RestoreEditMessageGestureDetector( - messageId: messageId, - child: Text( - style: baseTextStyle - .copyWith(color: designVariables.btnLabelAttLowIntDanger), - textAlign: TextAlign.end, - zulipLocalizations.savingMessageEditFailedLabel)), + zulipLocalizations.savingMessageEditFailedLabel))), }; } } From 5636e7242f6e127a861013a78b5ed25816f0d5ed Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 26 May 2025 20:14:45 -0400 Subject: [PATCH 7/7] msglist: Put edit-message progress bar in top half of 4px bottom padding This is where the progress bar for outbox messages will go, so this is for consistency with that. Discussion: https://github.com/zulip/zulip-flutter/pull/1453#discussion_r2107935179 [chris: fixed to maintain 4px bottom padding in the common case where the progress bar is absent] Co-authored-by: Chris Bobbe --- lib/widgets/message_list.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index b82830e6cc..17422f8e95 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -1598,7 +1598,7 @@ class _EditMessageStatusRow extends StatelessWidget { return switch (status) { // TODO parse markdown and show new content as local echo? false => Padding( - padding: const EdgeInsets.only(bottom: 4), + padding: const EdgeInsets.only(bottom: 2), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, spacing: 1.5,