Skip to content

Commit c7a442f

Browse files
compose_box: Disable the compose box in DMs with deactivated users
Fixes: #675 Co-authored-by: Rajesh Malviya <[email protected]>
1 parent 80a8298 commit c7a442f

File tree

4 files changed

+306
-16
lines changed

4 files changed

+306
-16
lines changed

assets/l10n/app_en.arb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@
203203
"user": {"type": "String", "example": "channel name"}
204204
}
205205
},
206+
"composeBoxDeactivatedDmContentHint": "You cannot send messages to deactivated users.",
207+
"@composeBoxDeactivatedDmContentHint": {
208+
"description": "Hint text for content input when sending a message to one or multiple deactivated persons."
209+
},
206210
"composeBoxGroupDmContentHint": "Message group",
207211
"@composeBoxGroupDmContentHint": {
208212
"description": "Hint text for content input when sending a message to a group."

lib/widgets/compose_box.dart

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -272,17 +272,18 @@ class _ContentInput extends StatelessWidget {
272272
required this.controller,
273273
required this.focusNode,
274274
required this.hintText,
275+
this.enabled = true,
275276
});
276277

277278
final Narrow narrow;
278279
final ComposeContentController controller;
279280
final FocusNode focusNode;
280281
final String hintText;
282+
final bool enabled;
281283

282284
@override
283285
Widget build(BuildContext context) {
284286
ColorScheme colorScheme = Theme.of(context).colorScheme;
285-
286287
return InputDecorator(
287288
decoration: const InputDecoration(),
288289
child: ConstrainedBox(
@@ -304,6 +305,7 @@ class _ContentInput extends StatelessWidget {
304305
decoration: InputDecoration.collapsed(hintText: hintText),
305306
maxLines: null,
306307
textCapitalization: TextCapitalization.sentences,
308+
enabled: enabled,
307309
);
308310
}),
309311
));
@@ -378,32 +380,37 @@ class _FixedDestinationContentInput extends StatelessWidget {
378380
required this.narrow,
379381
required this.controller,
380382
required this.focusNode,
383+
required this.enabled,
381384
});
382385

383386
final SendableNarrow narrow;
384387
final ComposeContentController controller;
385388
final FocusNode focusNode;
389+
final bool enabled;
386390

387391
String _hintText(BuildContext context) {
388392
final zulipLocalizations = ZulipLocalizations.of(context);
389-
switch (narrow) {
390-
case TopicNarrow(:final streamId, :final topic):
393+
switch ((narrow, enabled)) {
394+
case (TopicNarrow(:final streamId, :final topic), _):
391395
final store = PerAccountStoreWidget.of(context);
392396
final streamName = store.streams[streamId]?.name
393397
?? zulipLocalizations.composeBoxUnknownChannelName;
394398
return zulipLocalizations.composeBoxChannelContentHint(streamName, topic);
395399

396-
case DmNarrow(otherRecipientIds: []): // The self-1:1 thread.
400+
case (DmNarrow(otherRecipientIds: []), _): // The self-1:1 thread.
397401
return zulipLocalizations.composeBoxSelfDmContentHint;
398402

399-
case DmNarrow(otherRecipientIds: [final otherUserId]):
403+
case (DmNarrow(otherRecipientIds: [final otherUserId]), true):
400404
final store = PerAccountStoreWidget.of(context);
401405
final fullName = store.users[otherUserId]?.fullName;
402406
if (fullName == null) return zulipLocalizations.composeBoxGenericContentHint;
403407
return zulipLocalizations.composeBoxDmContentHint(fullName);
404408

405-
case DmNarrow(): // A group DM thread.
409+
case (DmNarrow(), true): // A group DM thread.
406410
return zulipLocalizations.composeBoxGroupDmContentHint;
411+
412+
case (DmNarrow(), false):
413+
return zulipLocalizations.composeBoxDeactivatedDmContentHint;
407414
}
408415
}
409416

@@ -413,7 +420,8 @@ class _FixedDestinationContentInput extends StatelessWidget {
413420
narrow: narrow,
414421
controller: controller,
415422
focusNode: focusNode,
416-
hintText: _hintText(context));
423+
hintText: _hintText(context),
424+
enabled: enabled);
417425
}
418426
}
419427

@@ -493,10 +501,15 @@ Future<void> _uploadFiles({
493501
}
494502

495503
abstract class _AttachUploadsButton extends StatelessWidget {
496-
const _AttachUploadsButton({required this.contentController, required this.contentFocusNode});
504+
const _AttachUploadsButton({
505+
required this.contentController,
506+
required this.contentFocusNode,
507+
required this.enabled,
508+
});
497509

498510
final ComposeContentController contentController;
499511
final FocusNode contentFocusNode;
512+
final bool enabled;
500513

501514
IconData get icon;
502515
String tooltip(ZulipLocalizations zulipLocalizations);
@@ -535,7 +548,7 @@ abstract class _AttachUploadsButton extends StatelessWidget {
535548
return IconButton(
536549
icon: Icon(icon),
537550
tooltip: tooltip(zulipLocalizations),
538-
onPressed: () => _handlePress(context));
551+
onPressed: enabled ? () => _handlePress(context) : null);
539552
}
540553
}
541554

@@ -579,7 +592,11 @@ Future<Iterable<_File>> _getFilePickerFiles(BuildContext context, FileType type)
579592
}
580593

581594
class _AttachFileButton extends _AttachUploadsButton {
582-
const _AttachFileButton({required super.contentController, required super.contentFocusNode});
595+
const _AttachFileButton({
596+
required super.contentController,
597+
required super.contentFocusNode,
598+
required super.enabled,
599+
});
583600

584601
@override
585602
IconData get icon => Icons.attach_file;
@@ -595,7 +612,11 @@ class _AttachFileButton extends _AttachUploadsButton {
595612
}
596613

597614
class _AttachMediaButton extends _AttachUploadsButton {
598-
const _AttachMediaButton({required super.contentController, required super.contentFocusNode});
615+
const _AttachMediaButton({
616+
required super.contentController,
617+
required super.contentFocusNode,
618+
required super.enabled,
619+
});
599620

600621
@override
601622
IconData get icon => Icons.image;
@@ -612,7 +633,11 @@ class _AttachMediaButton extends _AttachUploadsButton {
612633
}
613634

614635
class _AttachFromCameraButton extends _AttachUploadsButton {
615-
const _AttachFromCameraButton({required super.contentController, required super.contentFocusNode});
636+
const _AttachFromCameraButton({
637+
required super.contentController,
638+
required super.contentFocusNode,
639+
required super.enabled,
640+
});
616641

617642
@override
618643
IconData get icon => Icons.camera_alt;
@@ -668,11 +693,13 @@ class _SendButton extends StatefulWidget {
668693
required this.topicController,
669694
required this.contentController,
670695
required this.getDestination,
696+
this.enabled = true,
671697
});
672698

673699
final ComposeTopicController? topicController;
674700
final ComposeContentController contentController;
675701
final MessageDestination Function() getDestination;
702+
final bool enabled;
676703

677704
@override
678705
State<_SendButton> createState() => _SendButtonState();
@@ -792,7 +819,7 @@ class _SendButtonState extends State<_SendButton> {
792819
),
793820
color: foregroundColor,
794821
icon: const Icon(Icons.send),
795-
onPressed: _send));
822+
onPressed: widget.enabled ? _send : null));
796823
}
797824
}
798825

@@ -803,13 +830,15 @@ class _ComposeBoxLayout extends StatelessWidget {
803830
required this.sendButton,
804831
required this.contentController,
805832
required this.contentFocusNode,
833+
this.enabled = true,
806834
});
807835

808836
final Widget? topicInput;
809837
final Widget contentInput;
810838
final Widget sendButton;
811839
final ComposeContentController contentController;
812840
final FocusNode contentFocusNode;
841+
final bool enabled;
813842

814843
@override
815844
Widget build(BuildContext context) {
@@ -853,9 +882,21 @@ class _ComposeBoxLayout extends StatelessWidget {
853882
data: themeData.copyWith(
854883
iconTheme: themeData.iconTheme.copyWith(color: colorScheme.onSurfaceVariant)),
855884
child: Row(children: [
856-
_AttachFileButton(contentController: contentController, contentFocusNode: contentFocusNode),
857-
_AttachMediaButton(contentController: contentController, contentFocusNode: contentFocusNode),
858-
_AttachFromCameraButton(contentController: contentController, contentFocusNode: contentFocusNode),
885+
_AttachFileButton(
886+
contentController: contentController,
887+
contentFocusNode: contentFocusNode,
888+
enabled: enabled,
889+
),
890+
_AttachMediaButton(
891+
contentController: contentController,
892+
contentFocusNode: contentFocusNode,
893+
enabled: enabled,
894+
),
895+
_AttachFromCameraButton(
896+
contentController: contentController,
897+
contentFocusNode: contentFocusNode,
898+
enabled: enabled,
899+
),
859900
])),
860901
])))); }
861902
}
@@ -943,6 +984,20 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
943984

944985
@override FocusNode get contentFocusNode => _contentFocusNode;
945986
final _contentFocusNode = FocusNode();
987+
late bool enabled;
988+
989+
@override
990+
void didChangeDependencies() {
991+
super.didChangeDependencies();
992+
final store = PerAccountStoreWidget.of(context);
993+
enabled = switch (widget.narrow) {
994+
DmNarrow(:final otherRecipientIds) => otherRecipientIds.every((id) =>
995+
store.users[id]?.isActive ?? true),
996+
TopicNarrow() => true,
997+
};
998+
999+
if (!enabled) _contentController.clear();
1000+
}
9461001

9471002
@override
9481003
void dispose() {
@@ -957,15 +1012,18 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
9571012
contentController: _contentController,
9581013
contentFocusNode: _contentFocusNode,
9591014
topicInput: null,
1015+
enabled: enabled,
9601016
contentInput: _FixedDestinationContentInput(
9611017
narrow: widget.narrow,
9621018
controller: _contentController,
9631019
focusNode: _contentFocusNode,
1020+
enabled: enabled,
9641021
),
9651022
sendButton: _SendButton(
9661023
topicController: null,
9671024
contentController: _contentController,
9681025
getDestination: () => widget.narrow.destination,
1026+
enabled: enabled,
9691027
));
9701028
}
9711029
}

0 commit comments

Comments
 (0)