Skip to content

Commit d0c8988

Browse files
PIG208chrisbobbe
andcommitted
msglist: Support retrieving failed outbox message content
Different from the Figma design, the bottom padding below the progress bar is changed from 0.5px to 2px, as discussed here: zulip#1453 (comment) Fixes: zulip#1441 Co-authored-by: Chris Bobbe <[email protected]>
1 parent 8ff7b79 commit d0c8988

19 files changed

+442
-46
lines changed

assets/l10n/app_en.arb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,9 @@
385385
"@discardDraftForEditConfirmationDialogMessage": {
386386
"description": "Message for a confirmation dialog for discarding message text that was typed into the compose box, when editing a message."
387387
},
388-
"discardDraftForMessageNotSentConfirmationDialogMessage": "When you restore a message not sent, the content that was previously in the compose box is discarded.",
389-
"@discardDraftForMessageNotSentConfirmationDialogMessage": {
390-
"description": "Message for a confirmation dialog when restoring a message not sent, for discarding message text that was typed into the compose box."
388+
"discardDraftForOutboxConfirmationDialogMessage": "When you restore an unsent message, the content that was previously in the compose box is discarded.",
389+
"@discardDraftForOutboxConfirmationDialogMessage": {
390+
"description": "Message for a confirmation dialog when restoring an outbox message, for discarding message text that was typed into the compose box."
391391
},
392392
"discardDraftConfirmationDialogConfirmButton": "Discard",
393393
"@discardDraftConfirmationDialogConfirmButton": {

assets/l10n/app_pl.arb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,10 +1113,6 @@
11131113
"@messageNotSentLabel": {
11141114
"description": "Text on a message in the message list saying that a send message request failed. (Use ALL CAPS for cased alphabets: Latin, Greek, Cyrillic, etc.)"
11151115
},
1116-
"discardDraftForMessageNotSentConfirmationDialogMessage": "Odzyskanie wiadomości, która nie została wysłana, skutkuje wyczyszczeniem zawartości pola dodania wpisu.",
1117-
"@discardDraftForMessageNotSentConfirmationDialogMessage": {
1118-
"description": "Message for a confirmation dialog when restoring a message not sent, for discarding message text that was typed into the compose box."
1119-
},
11201116
"errorNotificationOpenAccountNotFound": "Nie odnaleziono konta powiązanego z tym powiadomieniem.",
11211117
"@errorNotificationOpenAccountNotFound": {
11221118
"description": "Error message when the account associated with the notification could not be found"

assets/l10n/app_ru.arb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,10 +1105,6 @@
11051105
"@newDmFabButtonLabel": {
11061106
"description": "Label for the floating action button (FAB) that opens the new DM sheet."
11071107
},
1108-
"discardDraftForMessageNotSentConfirmationDialogMessage": "При восстановлении неотправленного сообщения текст в поле ввода текста будет утрачен.",
1109-
"@discardDraftForMessageNotSentConfirmationDialogMessage": {
1110-
"description": "Message for a confirmation dialog when restoring a message not sent, for discarding message text that was typed into the compose box."
1111-
},
11121108
"newDmSheetScreenTitle": "Новое ЛС",
11131109
"@newDmSheetScreenTitle": {
11141110
"description": "Title displayed at the top of the new DM screen."

lib/generated/l10n/zulip_localizations.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -655,11 +655,11 @@ abstract class ZulipLocalizations {
655655
/// **'When you edit a message, the content that was previously in the compose box is discarded.'**
656656
String get discardDraftForEditConfirmationDialogMessage;
657657

658-
/// Message for a confirmation dialog when restoring a message not sent, for discarding message text that was typed into the compose box.
658+
/// Message for a confirmation dialog when restoring an outbox message, for discarding message text that was typed into the compose box.
659659
///
660660
/// In en, this message translates to:
661-
/// **'When you restore a message not sent, the content that was previously in the compose box is discarded.'**
662-
String get discardDraftForMessageNotSentConfirmationDialogMessage;
661+
/// **'When you restore an unsent message, the content that was previously in the compose box is discarded.'**
662+
String get discardDraftForOutboxConfirmationDialogMessage;
663663

664664
/// Label for the 'Discard' button on a confirmation dialog for discarding message text that was typed into the compose box.
665665
///

lib/generated/l10n/zulip_localizations_ar.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
325325
'When you edit a message, the content that was previously in the compose box is discarded.';
326326

327327
@override
328-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
329-
'When you restore a message not sent, the content that was previously in the compose box is discarded.';
328+
String get discardDraftForOutboxConfirmationDialogMessage =>
329+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
330330

331331
@override
332332
String get discardDraftConfirmationDialogConfirmButton => 'Discard';

lib/generated/l10n/zulip_localizations_de.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ class ZulipLocalizationsDe extends ZulipLocalizations {
325325
'When you edit a message, the content that was previously in the compose box is discarded.';
326326

327327
@override
328-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
329-
'When you restore a message not sent, the content that was previously in the compose box is discarded.';
328+
String get discardDraftForOutboxConfirmationDialogMessage =>
329+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
330330

331331
@override
332332
String get discardDraftConfirmationDialogConfirmButton => 'Discard';

lib/generated/l10n/zulip_localizations_en.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
325325
'When you edit a message, the content that was previously in the compose box is discarded.';
326326

327327
@override
328-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
329-
'When you restore a message not sent, the content that was previously in the compose box is discarded.';
328+
String get discardDraftForOutboxConfirmationDialogMessage =>
329+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
330330

331331
@override
332332
String get discardDraftConfirmationDialogConfirmButton => 'Discard';

lib/generated/l10n/zulip_localizations_ja.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
325325
'When you edit a message, the content that was previously in the compose box is discarded.';
326326

327327
@override
328-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
329-
'When you restore a message not sent, the content that was previously in the compose box is discarded.';
328+
String get discardDraftForOutboxConfirmationDialogMessage =>
329+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
330330

331331
@override
332332
String get discardDraftConfirmationDialogConfirmButton => 'Discard';

lib/generated/l10n/zulip_localizations_nb.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
325325
'When you edit a message, the content that was previously in the compose box is discarded.';
326326

327327
@override
328-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
329-
'When you restore a message not sent, the content that was previously in the compose box is discarded.';
328+
String get discardDraftForOutboxConfirmationDialogMessage =>
329+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
330330

331331
@override
332332
String get discardDraftConfirmationDialogConfirmButton => 'Discard';

lib/generated/l10n/zulip_localizations_pl.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,8 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
333333
'Miej na uwadze, że przechodząc do zmiany wiadomości wyczyścisz okno nowej wiadomości.';
334334

335335
@override
336-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
337-
'Odzyskanie wiadomości, która nie została wysłana, skutkuje wyczyszczeniem zawartości pola dodania wpisu.';
336+
String get discardDraftForOutboxConfirmationDialogMessage =>
337+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
338338

339339
@override
340340
String get discardDraftConfirmationDialogConfirmButton => 'Odrzuć';

lib/generated/l10n/zulip_localizations_ru.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,8 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
334334
'При изменении сообщения текст из поля для редактирования удаляется.';
335335

336336
@override
337-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
338-
'При восстановлении неотправленного сообщения текст в поле ввода текста будет утрачен.';
337+
String get discardDraftForOutboxConfirmationDialogMessage =>
338+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
339339

340340
@override
341341
String get discardDraftConfirmationDialogConfirmButton => 'Сбросить';

lib/generated/l10n/zulip_localizations_sk.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
325325
'When you edit a message, the content that was previously in the compose box is discarded.';
326326

327327
@override
328-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
329-
'When you restore a message not sent, the content that was previously in the compose box is discarded.';
328+
String get discardDraftForOutboxConfirmationDialogMessage =>
329+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
330330

331331
@override
332332
String get discardDraftConfirmationDialogConfirmButton => 'Discard';

lib/generated/l10n/zulip_localizations_uk.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,8 @@ class ZulipLocalizationsUk extends ZulipLocalizations {
334334
'When you edit a message, the content that was previously in the compose box is discarded.';
335335

336336
@override
337-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
338-
'When you restore a message not sent, the content that was previously in the compose box is discarded.';
337+
String get discardDraftForOutboxConfirmationDialogMessage =>
338+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
339339

340340
@override
341341
String get discardDraftConfirmationDialogConfirmButton => 'Discard';

lib/generated/l10n/zulip_localizations_zh.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ class ZulipLocalizationsZh extends ZulipLocalizations {
325325
'When you edit a message, the content that was previously in the compose box is discarded.';
326326

327327
@override
328-
String get discardDraftForMessageNotSentConfirmationDialogMessage =>
329-
'When you restore a message not sent, the content that was previously in the compose box is discarded.';
328+
String get discardDraftForOutboxConfirmationDialogMessage =>
329+
'When you restore an unsent message, the content that was previously in the compose box is discarded.';
330330

331331
@override
332332
String get discardDraftConfirmationDialogConfirmButton => 'Discard';

lib/model/message.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -881,9 +881,8 @@ mixin _OutboxMessageStore on PerAccountStoreBase {
881881
void _handleMessageEventOutbox(MessageEvent event) {
882882
if (event.localMessageId != null) {
883883
final localMessageId = int.parse(event.localMessageId!, radix: 10);
884-
// The outbox message can be missing if the user removes it (to be
885-
// implemented in #1441) before the event arrives.
886-
// Nothing to do in that case.
884+
// The outbox message can be missing if the user removes it before the
885+
// event arrives. Nothing to do in that case.
887886
_outboxMessages.remove(localMessageId);
888887
_outboxMessageDebounceTimers.remove(localMessageId)?.cancel();
889888
_outboxMessageWaitPeriodTimers.remove(localMessageId)?.cancel();

lib/widgets/compose_box.dart

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import '../api/route/messages.dart';
1313
import '../generated/l10n/zulip_localizations.dart';
1414
import '../model/binding.dart';
1515
import '../model/compose.dart';
16+
import '../model/message.dart';
1617
import '../model/narrow.dart';
1718
import '../model/store.dart';
1819
import 'actions.dart';
@@ -1840,6 +1841,16 @@ class ComposeBox extends StatefulWidget {
18401841
abstract class ComposeBoxState extends State<ComposeBox> {
18411842
ComposeBoxController get controller;
18421843

1844+
/// Fills the compose box with the content of an [OutboxMessage]
1845+
/// for a failed [sendMessage] request.
1846+
///
1847+
/// If there is already text in the compose box, gives a confirmation dialog
1848+
/// to confirm that it is OK to discard that text.
1849+
///
1850+
/// [localMessageId], as in [OutboxMessage.localMessageId], must be present
1851+
/// in the message store.
1852+
void restoreMessageNotSent(int localMessageId);
1853+
18431854
/// Switch the compose box to editing mode.
18441855
///
18451856
/// If there is already text in the compose box, gives a confirmation dialog
@@ -1861,6 +1872,29 @@ class _ComposeBoxState extends State<ComposeBox> with PerAccountStoreAwareStateM
18611872
@override ComposeBoxController get controller => _controller!;
18621873
ComposeBoxController? _controller;
18631874

1875+
@override
1876+
void restoreMessageNotSent(int localMessageId) async {
1877+
final zulipLocalizations = ZulipLocalizations.of(context);
1878+
1879+
final abort = await _abortBecauseContentInputNotEmpty(
1880+
dialogMessage: zulipLocalizations.discardDraftForOutboxConfirmationDialogMessage);
1881+
if (abort || !mounted) return;
1882+
1883+
final store = PerAccountStoreWidget.of(context);
1884+
final outboxMessage = store.takeOutboxMessage(localMessageId);
1885+
setState(() {
1886+
_setNewController(store);
1887+
final controller = this.controller;
1888+
controller
1889+
..content.value = TextEditingValue(text: outboxMessage.contentMarkdown)
1890+
..contentFocusNode.requestFocus();
1891+
if (controller is StreamComposeBoxController) {
1892+
controller.topic.setTopic(
1893+
(outboxMessage.conversation as StreamConversation).topic);
1894+
}
1895+
});
1896+
}
1897+
18641898
@override
18651899
void startEditInteraction(int messageId) async {
18661900
final zulipLocalizations = ZulipLocalizations.of(context);
@@ -1942,7 +1976,7 @@ class _ComposeBoxState extends State<ComposeBox> with PerAccountStoreAwareStateM
19421976
if (!mounted) return;
19431977
if (!identical(controller, emptyEditController)) {
19441978
// During the fetch-raw-content request, the user tapped Cancel
1945-
// or tapped a failed message edit to restore.
1979+
// or tapped a failed message edit or failed outbox message to restore.
19461980
// TODO in this case we don't want the error dialog caused by
19471981
// ZulipAction.fetchRawContentWithFeedback; suppress that
19481982
return;

lib/widgets/message_list.dart

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:intl/intl.dart' hide TextDirection;
55

66
import '../api/model/model.dart';
77
import '../generated/l10n/zulip_localizations.dart';
8+
import '../model/message.dart';
89
import '../model/message_list.dart';
910
import '../model/narrow.dart';
1011
import '../model/store.dart';
@@ -1686,19 +1687,113 @@ class OutboxMessageWithPossibleSender extends StatelessWidget {
16861687
@override
16871688
Widget build(BuildContext context) {
16881689
final message = item.message;
1690+
final localMessageId = message.localMessageId;
1691+
1692+
// This is adapted from [MessageContent].
1693+
// TODO(#576): Offer InheritedMessage ancestor once we are ready
1694+
// to support local echoing images and lightbox.
1695+
Widget content = DefaultTextStyle(
1696+
style: ContentTheme.of(context).textStylePlainParagraph,
1697+
child: BlockContentList(nodes: item.content.nodes));
1698+
1699+
switch (message.state) {
1700+
case OutboxMessageState.hidden:
1701+
throw StateError('Hidden OutboxMessage messages should not appear in message lists');
1702+
case OutboxMessageState.waiting:
1703+
break;
1704+
case OutboxMessageState.failed:
1705+
case OutboxMessageState.waitPeriodExpired:
1706+
// TODO(#576): When we support rendered-content local echo,
1707+
// use IgnorePointer along with this faded appearance,
1708+
// like we do for the failed-message-edit state
1709+
content = _RestoreOutboxMessageGestureDetector(
1710+
localMessageId: localMessageId,
1711+
child: Opacity(opacity: 0.6, child: content));
1712+
}
1713+
16891714
return Padding(
1690-
padding: const EdgeInsets.symmetric(vertical: 4),
1715+
padding: const EdgeInsets.only(top: 4),
16911716
child: Column(children: [
16921717
if (item.showSender)
16931718
_SenderRow(message: message, showTimestamp: false),
16941719
Padding(
16951720
padding: const EdgeInsets.symmetric(horizontal: 16),
1696-
// This is adapted from [MessageContent].
1697-
// TODO(#576): Offer InheritedMessage ancestor once we are ready
1698-
// to support local echoing images and lightbox.
1699-
child: DefaultTextStyle(
1700-
style: ContentTheme.of(context).textStylePlainParagraph,
1701-
child: BlockContentList(nodes: item.content.nodes))),
1721+
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch,
1722+
children: [
1723+
content,
1724+
_OutboxMessageStatusRow(
1725+
localMessageId: localMessageId, outboxMessageState: message.state),
1726+
])),
17021727
]));
17031728
}
17041729
}
1730+
1731+
class _OutboxMessageStatusRow extends StatelessWidget {
1732+
const _OutboxMessageStatusRow({
1733+
required this.localMessageId,
1734+
required this.outboxMessageState,
1735+
});
1736+
1737+
final int localMessageId;
1738+
final OutboxMessageState outboxMessageState;
1739+
1740+
@override
1741+
Widget build(BuildContext context) {
1742+
switch (outboxMessageState) {
1743+
case OutboxMessageState.hidden:
1744+
assert(false,
1745+
'Hidden OutboxMessage messages should not appear in message lists');
1746+
return SizedBox.shrink();
1747+
1748+
case OutboxMessageState.waiting:
1749+
final designVariables = DesignVariables.of(context);
1750+
return Padding(
1751+
padding: const EdgeInsetsGeometry.only(bottom: 2),
1752+
child: LinearProgressIndicator(
1753+
minHeight: 2,
1754+
color: designVariables.foreground.withFadedAlpha(0.5),
1755+
backgroundColor: designVariables.foreground.withFadedAlpha(0.2)));
1756+
1757+
case OutboxMessageState.failed:
1758+
case OutboxMessageState.waitPeriodExpired:
1759+
final designVariables = DesignVariables.of(context);
1760+
final zulipLocalizations = ZulipLocalizations.of(context);
1761+
return Padding(
1762+
padding: const EdgeInsets.only(bottom: 4),
1763+
child: _RestoreOutboxMessageGestureDetector(
1764+
localMessageId: localMessageId,
1765+
child: Text(
1766+
zulipLocalizations.messageNotSentLabel,
1767+
textAlign: TextAlign.end,
1768+
style: TextStyle(
1769+
color: designVariables.btnLabelAttLowIntDanger,
1770+
fontSize: 12,
1771+
height: 12 / 12,
1772+
letterSpacing: proportionalLetterSpacing(
1773+
context, 0.05, baseFontSize: 12)))));
1774+
}
1775+
}
1776+
}
1777+
1778+
class _RestoreOutboxMessageGestureDetector extends StatelessWidget {
1779+
const _RestoreOutboxMessageGestureDetector({
1780+
required this.localMessageId,
1781+
required this.child,
1782+
});
1783+
1784+
final int localMessageId;
1785+
final Widget child;
1786+
1787+
@override
1788+
Widget build(BuildContext context) {
1789+
return GestureDetector(
1790+
behavior: HitTestBehavior.opaque,
1791+
onTap: () {
1792+
final composeBoxState = MessageListPage.ancestorOf(context).composeBoxState;
1793+
// TODO(#1518) allow restore-outbox-message from any message-list page
1794+
if (composeBoxState == null) return;
1795+
composeBoxState.restoreMessageNotSent(localMessageId);
1796+
},
1797+
child: child);
1798+
}
1799+
}

0 commit comments

Comments
 (0)