@@ -275,17 +275,65 @@ class ComposeContentController extends ComposeController<ContentValidationError>
275275 }
276276}
277277
278- class _TopBar extends StatelessWidget {
279- const _TopBar ({required this .showProgressIndicator});
278+ class SendErrorController extends ChangeNotifier {
279+ SendErrorController ();
280+
281+ Iterable <String > get errors => _errors;
282+ Set <String > _errors = {};
283+ set errors (Iterable <String > value) {
284+ _errors = Set .from (value);
285+ assert (_errors.length == value.length, 'There should not be duplicate errors' );
286+ notifyListeners ();
287+ }
288+
289+ void remove (String value) {
290+ final removed = _errors.remove (value);
291+ assert (removed);
292+ notifyListeners ();
293+ }
294+ }
295+
296+ class _TopBar extends StatefulWidget {
297+ const _TopBar ({required this .showProgressIndicator, required this .sendErrorController});
280298
281299 final bool showProgressIndicator;
300+ final SendErrorController sendErrorController;
301+
302+ @override
303+ State <_TopBar > createState () => _TopBarState ();
304+ }
305+
306+ class _TopBarState extends State <_TopBar > {
307+ @override
308+ void initState () {
309+ super .initState ();
310+ widget.sendErrorController.addListener (_sendErrorChanged);
311+ }
312+
313+ @override
314+ void dispose () {
315+ widget.sendErrorController.removeListener (_sendErrorChanged);
316+ super .dispose ();
317+ }
318+
319+ void _sendErrorChanged () {
320+ setState (() {
321+ // The actual state lives in `widget.sendErrorController`.
322+ });
323+ }
282324
283325 @override
284326 Widget build (BuildContext context) {
327+ final designVariables = DesignVariables .of (context);
285328 // TODO: Figure out a way so that this does not shift the message list
286329 // when it gains more height.
287330 return Column (children: [
288- if (showProgressIndicator) _progressIndicator (context),
331+ if (widget.showProgressIndicator) _progressIndicator (context),
332+ for (final value in widget.sendErrorController.errors)
333+ _ErrorBanner (label: value,
334+ action: IconButton (icon: Icon (
335+ ZulipIcons .remove, color: designVariables.btnLabelAttLowIntDanger),
336+ onPressed: () => widget.sendErrorController.remove (value))),
289337 ]);
290338 }
291339}
@@ -968,12 +1016,14 @@ class _SendButton extends StatefulWidget {
9681016 required this .enabled,
9691017 required this .topicController,
9701018 required this .contentController,
1019+ required this .sendErrorController,
9711020 required this .getDestination,
9721021 });
9731022
9741023 final ValueNotifier <bool > enabled;
9751024 final ComposeTopicController ? topicController;
9761025 final ComposeContentController contentController;
1026+ final SendErrorController sendErrorController;
9771027 final MessageDestination Function () getDestination;
9781028
9791029 @override
@@ -1029,10 +1079,7 @@ class _SendButtonState extends State<_SendButton> {
10291079 for (final error in widget.contentController.validationErrors)
10301080 error.message (zulipLocalizations),
10311081 ];
1032- showErrorDialog (
1033- context: context,
1034- title: zulipLocalizations.errorMessageNotSent,
1035- message: validationErrorMessages.join ('\n\n ' ));
1082+ widget.sendErrorController.errors = validationErrorMessages;
10361083 return ;
10371084 }
10381085 if (! widget.enabled.value) {
@@ -1053,6 +1100,7 @@ class _SendButtonState extends State<_SendButton> {
10531100 .sendMessage (destination: widget.getDestination (), content: content)
10541101 .timeout (kSendMessageTimeout);
10551102 widget.contentController.clear ();
1103+ widget.sendErrorController.errors = [];
10561104 } catch (e) {
10571105 if (! mounted) return ;
10581106
@@ -1064,9 +1112,7 @@ class _SendButtonState extends State<_SendButton> {
10641112 case ApiRequestException (): message = e.message;
10651113 default : rethrow ;
10661114 }
1067- showErrorDialog (context: context,
1068- title: zulipLocalizations.errorMessageNotSent,
1069- message: message);
1115+ widget.sendErrorController.errors = [message];
10701116 return ;
10711117 } finally {
10721118 widget.enabled.value = true ;
@@ -1196,6 +1242,7 @@ abstract class ComposeBoxController<T extends StatefulWidget> extends State<T> {
11961242 bool get enabled;
11971243 ComposeTopicController ? get topicController;
11981244 ComposeContentController get contentController;
1245+ SendErrorController get sendErrorController;
11991246 FocusNode get contentFocusNode;
12001247}
12011248
@@ -1229,6 +1276,9 @@ class _StreamComposeBoxState extends State<_StreamComposeBox> implements Compose
12291276 FocusNode get topicFocusNode => _topicFocusNode;
12301277 final _topicFocusNode = FocusNode ();
12311278
1279+ @override SendErrorController get sendErrorController => _sendErrorController;
1280+ final _sendErrorController = SendErrorController ();
1281+
12321282 @override
12331283 void initState () {
12341284 super .initState ();
@@ -1241,6 +1291,7 @@ class _StreamComposeBoxState extends State<_StreamComposeBox> implements Compose
12411291 _topicController.dispose ();
12421292 _contentController.dispose ();
12431293 _contentFocusNode.dispose ();
1294+ _sendErrorController.dispose ();
12441295 super .dispose ();
12451296 }
12461297
@@ -1253,7 +1304,10 @@ class _StreamComposeBoxState extends State<_StreamComposeBox> implements Compose
12531304 @override
12541305 Widget build (BuildContext context) {
12551306 return _ComposeBoxLayout (
1256- topBar: _TopBar (showProgressIndicator: ! enabled),
1307+ topBar: _TopBar (
1308+ showProgressIndicator: ! enabled,
1309+ sendErrorController: sendErrorController,
1310+ ),
12571311 topicInput: _TopicInput (
12581312 enabled: enabled,
12591313 streamId: widget.narrow.streamId,
@@ -1277,16 +1331,18 @@ class _StreamComposeBoxState extends State<_StreamComposeBox> implements Compose
12771331 enabled: _enabled,
12781332 topicController: _topicController,
12791333 contentController: _contentController,
1334+ sendErrorController: _sendErrorController,
12801335 getDestination: () => StreamDestination (
12811336 widget.narrow.streamId, _topicController.textNormalized),
12821337 ));
12831338 }
12841339}
12851340
12861341class _ErrorBanner extends StatelessWidget {
1287- const _ErrorBanner ({required this .label});
1342+ const _ErrorBanner ({required this .label, this .action });
12881343
12891344 final String label;
1345+ final Widget ? action;
12901346
12911347 @override
12921348 Widget build (BuildContext context) {
@@ -1298,6 +1354,11 @@ class _ErrorBanner extends StatelessWidget {
12981354 // which is a variable equivalent to this value.
12991355 wght: 600 ));
13001356
1357+ final padding = (action == null )
1358+ // Ensure that the text is centered when it is the only element.
1359+ ? const EdgeInsets .symmetric (horizontal: 16 , vertical: 5 )
1360+ : const EdgeInsetsDirectional .fromSTEB (16 , 5 , 8 , 5 );
1361+
13011362 return Container (
13021363 constraints: const BoxConstraints (minHeight: 40 ),
13031364 decoration: BoxDecoration (
@@ -1307,8 +1368,9 @@ class _ErrorBanner extends StatelessWidget {
13071368 children: [
13081369 Expanded (
13091370 child: Padding (
1310- padding: const EdgeInsets . symmetric (horizontal : 16 , vertical : 5 ) ,
1371+ padding: padding ,
13111372 child: Text (label, style: labelTextStyle))),
1373+ if (action != null ) action! ,
13121374 ]));
13131375 }
13141376}
@@ -1334,6 +1396,9 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
13341396 @override FocusNode get contentFocusNode => _contentFocusNode;
13351397 final _contentFocusNode = FocusNode ();
13361398
1399+ @override SendErrorController get sendErrorController => _sendErrorController;
1400+ final _sendErrorController = SendErrorController ();
1401+
13371402 @override
13381403 void initState () {
13391404 super .initState ();
@@ -1345,6 +1410,7 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
13451410 _enabled.dispose ();
13461411 _contentController.dispose ();
13471412 _contentFocusNode.dispose ();
1413+ _sendErrorController.dispose ();
13481414 super .dispose ();
13491415 }
13501416
@@ -1357,7 +1423,10 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
13571423 @override
13581424 Widget build (BuildContext context) {
13591425 return _ComposeBoxLayout (
1360- topBar: _TopBar (showProgressIndicator: ! enabled),
1426+ topBar: _TopBar (
1427+ showProgressIndicator: ! enabled,
1428+ sendErrorController: sendErrorController,
1429+ ),
13611430 topicInput: null ,
13621431 contentInput: _FixedDestinationContentInput (
13631432 enabled: enabled,
@@ -1374,6 +1443,7 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
13741443 enabled: _enabled,
13751444 topicController: null ,
13761445 contentController: _contentController,
1446+ sendErrorController: _sendErrorController,
13771447 getDestination: () => widget.narrow.destination,
13781448 ));
13791449 }
0 commit comments