@@ -275,12 +275,14 @@ class _ContentInput extends StatelessWidget {
275
275
required this .controller,
276
276
required this .focusNode,
277
277
required this .hintText,
278
+ required this .readOnly,
278
279
});
279
280
280
281
final Narrow narrow;
281
282
final ComposeContentController controller;
282
283
final FocusNode focusNode;
283
284
final String hintText;
285
+ final bool readOnly;
284
286
285
287
@override
286
288
Widget build (BuildContext context) {
@@ -303,10 +305,11 @@ class _ContentInput extends StatelessWidget {
303
305
return TextField (
304
306
controller: controller,
305
307
focusNode: focusNode,
306
- style: TextStyle (color: colorScheme.onSurface),
308
+ style: TextStyle (color: readOnly ? colorScheme.onSurface. withOpacity ( 0.5 ) : colorScheme.onSurface),
307
309
decoration: InputDecoration .collapsed (hintText: hintText),
308
310
maxLines: null ,
309
311
textCapitalization: TextCapitalization .sentences,
312
+ readOnly: readOnly,
310
313
);
311
314
}),
312
315
));
@@ -320,12 +323,14 @@ class _StreamContentInput extends StatefulWidget {
320
323
required this .controller,
321
324
required this .topicController,
322
325
required this .focusNode,
326
+ required this .readOnly,
323
327
});
324
328
325
329
final ChannelNarrow narrow;
326
330
final ComposeContentController controller;
327
331
final ComposeTopicController topicController;
328
332
final FocusNode focusNode;
333
+ final bool readOnly;
329
334
330
335
@override
331
336
State <_StreamContentInput > createState () => _StreamContentInputState ();
@@ -372,7 +377,8 @@ class _StreamContentInputState extends State<_StreamContentInput> {
372
377
narrow: widget.narrow,
373
378
controller: widget.controller,
374
379
focusNode: widget.focusNode,
375
- hintText: zulipLocalizations.composeBoxChannelContentHint (streamName, _topicTextNormalized));
380
+ hintText: zulipLocalizations.composeBoxChannelContentHint (streamName, _topicTextNormalized),
381
+ readOnly: widget.readOnly);
376
382
}
377
383
}
378
384
@@ -381,12 +387,15 @@ class _TopicInput extends StatelessWidget {
381
387
required this .streamId,
382
388
required this .controller,
383
389
required this .focusNode,
384
- required this .contentFocusNode});
390
+ required this .contentFocusNode,
391
+ required this .readOnly,
392
+ });
385
393
386
394
final int streamId;
387
395
final ComposeTopicController controller;
388
396
final FocusNode focusNode;
389
397
final FocusNode contentFocusNode;
398
+ final bool readOnly;
390
399
391
400
@override
392
401
Widget build (BuildContext context) {
@@ -404,6 +413,7 @@ class _TopicInput extends StatelessWidget {
404
413
textInputAction: TextInputAction .next,
405
414
style: TextStyle (color: colorScheme.onSurface),
406
415
decoration: InputDecoration (hintText: zulipLocalizations.composeBoxTopicHintText),
416
+ readOnly: readOnly,
407
417
));
408
418
}
409
419
}
@@ -413,11 +423,13 @@ class _FixedDestinationContentInput extends StatelessWidget {
413
423
required this .narrow,
414
424
required this .controller,
415
425
required this .focusNode,
426
+ required this .readOnly,
416
427
});
417
428
418
429
final SendableNarrow narrow;
419
430
final ComposeContentController controller;
420
431
final FocusNode focusNode;
432
+ final bool readOnly;
421
433
422
434
String _hintText (BuildContext context) {
423
435
final zulipLocalizations = ZulipLocalizations .of (context);
@@ -448,7 +460,8 @@ class _FixedDestinationContentInput extends StatelessWidget {
448
460
narrow: narrow,
449
461
controller: controller,
450
462
focusNode: focusNode,
451
- hintText: _hintText (context));
463
+ hintText: _hintText (context),
464
+ readOnly: readOnly);
452
465
}
453
466
}
454
467
@@ -538,10 +551,15 @@ Future<void> _uploadFiles({
538
551
}
539
552
540
553
abstract class _AttachUploadsButton extends StatelessWidget {
541
- const _AttachUploadsButton ({required this .contentController, required this .contentFocusNode});
554
+ const _AttachUploadsButton ({
555
+ required this .contentController,
556
+ required this .contentFocusNode,
557
+ required this .disabled,
558
+ });
542
559
543
560
final ComposeContentController contentController;
544
561
final FocusNode contentFocusNode;
562
+ final bool disabled;
545
563
546
564
IconData get icon;
547
565
String tooltip (ZulipLocalizations zulipLocalizations);
@@ -580,7 +598,7 @@ abstract class _AttachUploadsButton extends StatelessWidget {
580
598
return IconButton (
581
599
icon: Icon (icon),
582
600
tooltip: tooltip (zulipLocalizations),
583
- onPressed: () => _handlePress (context));
601
+ onPressed: disabled ? null : () => _handlePress (context));
584
602
}
585
603
}
586
604
@@ -639,7 +657,11 @@ Future<Iterable<_File>> _getFilePickerFiles(BuildContext context, FileType type)
639
657
}
640
658
641
659
class _AttachFileButton extends _AttachUploadsButton {
642
- const _AttachFileButton ({required super .contentController, required super .contentFocusNode});
660
+ const _AttachFileButton ({
661
+ required super .contentController,
662
+ required super .contentFocusNode,
663
+ required super .disabled,
664
+ });
643
665
644
666
@override
645
667
IconData get icon => Icons .attach_file;
@@ -655,7 +677,11 @@ class _AttachFileButton extends _AttachUploadsButton {
655
677
}
656
678
657
679
class _AttachMediaButton extends _AttachUploadsButton {
658
- const _AttachMediaButton ({required super .contentController, required super .contentFocusNode});
680
+ const _AttachMediaButton ({
681
+ required super .contentController,
682
+ required super .contentFocusNode,
683
+ required super .disabled,
684
+ });
659
685
660
686
@override
661
687
IconData get icon => Icons .image;
@@ -672,7 +698,11 @@ class _AttachMediaButton extends _AttachUploadsButton {
672
698
}
673
699
674
700
class _AttachFromCameraButton extends _AttachUploadsButton {
675
- const _AttachFromCameraButton ({required super .contentController, required super .contentFocusNode});
701
+ const _AttachFromCameraButton ({
702
+ required super .contentController,
703
+ required super .contentFocusNode,
704
+ required super .disabled,
705
+ });
676
706
677
707
@override
678
708
IconData get icon => Icons .camera_alt;
@@ -748,11 +778,15 @@ class _SendButton extends StatefulWidget {
748
778
required this .topicController,
749
779
required this .contentController,
750
780
required this .getDestination,
781
+ required this .isSending,
782
+ required this .setIsSending,
751
783
});
752
784
753
785
final ComposeTopicController ? topicController;
754
786
final ComposeContentController contentController;
755
787
final MessageDestination Function () getDestination;
788
+ final bool isSending;
789
+ final void Function (bool value) setIsSending;
756
790
757
791
@override
758
792
State <_SendButton > createState () => _SendButtonState ();
@@ -817,12 +851,8 @@ class _SendButtonState extends State<_SendButton> {
817
851
final store = PerAccountStoreWidget .of (context);
818
852
final content = widget.contentController.textNormalized;
819
853
820
- widget.contentController.clear ();
821
-
854
+ widget.setIsSending (true );
822
855
try {
823
- // TODO(#720) clear content input only on success response;
824
- // while waiting, put input(s) and send button into a disabled
825
- // "working on it" state (letting input text be selected for copying).
826
856
await store.sendMessage (destination: widget.getDestination (), content: content);
827
857
} on ApiRequestException catch (e) {
828
858
if (! mounted) return ;
@@ -835,7 +865,10 @@ class _SendButtonState extends State<_SendButton> {
835
865
title: zulipLocalizations.errorMessageNotSent,
836
866
message: message);
837
867
return ;
868
+ } finally {
869
+ widget.setIsSending (false );
838
870
}
871
+ widget.contentController.clear ();
839
872
}
840
873
841
874
@override
@@ -872,7 +905,7 @@ class _SendButtonState extends State<_SendButton> {
872
905
),
873
906
color: foregroundColor,
874
907
icon: const Icon (Icons .send),
875
- onPressed: _send));
908
+ onPressed: widget.isSending ? null : _send));
876
909
}
877
910
}
878
911
@@ -905,13 +938,15 @@ class _ComposeBoxLayout extends StatelessWidget {
905
938
required this .sendButton,
906
939
required this .contentController,
907
940
required this .contentFocusNode,
941
+ required this .isSending,
908
942
});
909
943
910
944
final Widget ? topicInput;
911
945
final Widget contentInput;
912
946
final Widget sendButton;
913
947
final ComposeContentController contentController;
914
948
final FocusNode contentFocusNode;
949
+ final bool isSending;
915
950
916
951
@override
917
952
Widget build (BuildContext context) {
@@ -933,6 +968,7 @@ class _ComposeBoxLayout extends StatelessWidget {
933
968
);
934
969
935
970
return _ComposeBoxContainer (
971
+ isSending: isSending,
936
972
child: Column (children: [
937
973
Row (crossAxisAlignment: CrossAxisAlignment .end, children: [
938
974
Expanded (
@@ -950,9 +986,9 @@ class _ComposeBoxLayout extends StatelessWidget {
950
986
data: themeData.copyWith (
951
987
iconTheme: themeData.iconTheme.copyWith (color: colorScheme.onSurfaceVariant)),
952
988
child: Row (children: [
953
- _AttachFileButton (contentController: contentController, contentFocusNode: contentFocusNode),
954
- _AttachMediaButton (contentController: contentController, contentFocusNode: contentFocusNode),
955
- _AttachFromCameraButton (contentController: contentController, contentFocusNode: contentFocusNode),
989
+ _AttachFileButton (contentController: contentController, contentFocusNode: contentFocusNode, disabled : isSending ),
990
+ _AttachMediaButton (contentController: contentController, contentFocusNode: contentFocusNode, disabled : isSending ),
991
+ _AttachFromCameraButton (contentController: contentController, contentFocusNode: contentFocusNode, disabled : isSending ),
956
992
])),
957
993
]));
958
994
}
@@ -991,6 +1027,14 @@ class _StreamComposeBoxState extends State<_StreamComposeBox> implements Compose
991
1027
FocusNode get topicFocusNode => _topicFocusNode;
992
1028
final _topicFocusNode = FocusNode ();
993
1029
1030
+ bool get isSending => _isSending;
1031
+ bool _isSending = false ;
1032
+ void setIsSending (bool value) {
1033
+ setState (() {
1034
+ _isSending = value;
1035
+ });
1036
+ }
1037
+
994
1038
@override
995
1039
void dispose () {
996
1040
_topicController.dispose ();
@@ -1004,23 +1048,28 @@ class _StreamComposeBoxState extends State<_StreamComposeBox> implements Compose
1004
1048
return _ComposeBoxLayout (
1005
1049
contentController: _contentController,
1006
1050
contentFocusNode: _contentFocusNode,
1051
+ isSending: _isSending,
1007
1052
topicInput: _TopicInput (
1008
1053
streamId: widget.narrow.streamId,
1009
1054
controller: _topicController,
1010
1055
focusNode: topicFocusNode,
1011
1056
contentFocusNode: _contentFocusNode,
1057
+ readOnly: _isSending,
1012
1058
),
1013
1059
contentInput: _StreamContentInput (
1014
1060
narrow: widget.narrow,
1015
1061
topicController: _topicController,
1016
1062
controller: _contentController,
1017
1063
focusNode: _contentFocusNode,
1064
+ readOnly: _isSending,
1018
1065
),
1019
1066
sendButton: _SendButton (
1020
1067
topicController: _topicController,
1021
1068
contentController: _contentController,
1022
1069
getDestination: () => StreamDestination (
1023
1070
widget.narrow.streamId, _topicController.textNormalized),
1071
+ isSending: _isSending,
1072
+ setIsSending: setIsSending,
1024
1073
));
1025
1074
}
1026
1075
}
@@ -1064,6 +1113,14 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
1064
1113
@override FocusNode get contentFocusNode => _contentFocusNode;
1065
1114
final _contentFocusNode = FocusNode ();
1066
1115
1116
+ bool get isSending => _isSending;
1117
+ bool _isSending = false ;
1118
+ void setIsSending (bool value) {
1119
+ setState (() {
1120
+ _isSending = value;
1121
+ });
1122
+ }
1123
+
1067
1124
@override
1068
1125
void dispose () {
1069
1126
_contentController.dispose ();
@@ -1088,22 +1145,26 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
1088
1145
Widget build (BuildContext context) {
1089
1146
final errorBanner = _errorBanner (context);
1090
1147
if (errorBanner != null ) {
1091
- return _ComposeBoxContainer (child: errorBanner);
1148
+ return _ComposeBoxContainer (isSending : false , child: errorBanner);
1092
1149
}
1093
1150
1094
1151
return _ComposeBoxLayout (
1095
1152
contentController: _contentController,
1096
1153
contentFocusNode: _contentFocusNode,
1097
1154
topicInput: null ,
1155
+ isSending: _isSending,
1098
1156
contentInput: _FixedDestinationContentInput (
1099
1157
narrow: widget.narrow,
1100
1158
controller: _contentController,
1101
1159
focusNode: _contentFocusNode,
1160
+ readOnly: _isSending,
1102
1161
),
1103
1162
sendButton: _SendButton (
1104
1163
topicController: null ,
1105
1164
contentController: _contentController,
1106
1165
getDestination: () => widget.narrow.destination,
1166
+ isSending: _isSending,
1167
+ setIsSending: setIsSending,
1107
1168
));
1108
1169
}
1109
1170
}
0 commit comments