Skip to content

Commit 4d106d6

Browse files
committed
compose_box: Disable the compose box in DMs with deactivated users
Fixes: #675
1 parent 0c8a0a8 commit 4d106d6

File tree

4 files changed

+302
-15
lines changed

4 files changed

+302
-15
lines changed

assets/l10n/app_en.arb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@
203203
"user": {"type": "String", "example": "channel name"}
204204
}
205205
},
206+
"composeBoxDeactivatedDmContentHint": "You cannot send messages to {persons, plural, =1{a deactivated user} other{deactivated users}}.",
207+
"@composeBoxDeactivatedDmContentHint": {
208+
"description": "Hint text for content input when sending a message to one or multiple deactivated persons.",
209+
"placeholders": {
210+
"persons": {"type": "int", "example": "1"}
211+
}
212+
},
206213
"composeBoxGroupDmContentHint": "Message group",
207214
"@composeBoxGroupDmContentHint": {
208215
"description": "Hint text for content input when sending a message to a group."

lib/widgets/compose_box.dart

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -271,16 +271,19 @@ class _ContentInput extends StatelessWidget {
271271
required this.controller,
272272
required this.focusNode,
273273
required this.hintText,
274+
this.enabled = true,
274275
});
275276

276277
final Narrow narrow;
277278
final ComposeContentController controller;
278279
final FocusNode focusNode;
279280
final String hintText;
281+
final bool enabled;
280282

281283
@override
282284
Widget build(BuildContext context) {
283285
ColorScheme colorScheme = Theme.of(context).colorScheme;
286+
if (!enabled) controller.clear();
284287

285288
return InputDecorator(
286289
decoration: const InputDecoration(),
@@ -303,6 +306,7 @@ class _ContentInput extends StatelessWidget {
303306
decoration: InputDecoration.collapsed(hintText: hintText),
304307
maxLines: null,
305308
textCapitalization: TextCapitalization.sentences,
309+
enabled: enabled,
306310
);
307311
}),
308312
));
@@ -377,31 +381,36 @@ class _FixedDestinationContentInput extends StatelessWidget {
377381
required this.narrow,
378382
required this.controller,
379383
required this.focusNode,
384+
required this.enabled,
380385
});
381386

382387
final SendableNarrow narrow;
383388
final ComposeContentController controller;
384389
final FocusNode focusNode;
390+
final bool enabled;
385391

386392
String _hintText(BuildContext context) {
387393
final zulipLocalizations = ZulipLocalizations.of(context);
388-
switch (narrow) {
389-
case TopicNarrow(:final streamId, :final topic):
394+
switch ((narrow, enabled)) {
395+
case (TopicNarrow(:final streamId, :final topic), _):
390396
final store = PerAccountStoreWidget.of(context);
391397
final streamName = store.streams[streamId]?.name
392398
?? zulipLocalizations.composeBoxUnknownChannelName;
393399
return zulipLocalizations.composeBoxChannelContentHint(streamName, topic);
394400

395-
case DmNarrow(otherRecipientIds: []): // The self-1:1 thread.
401+
case (DmNarrow(otherRecipientIds: []), _): // The self-1:1 thread.
396402
return zulipLocalizations.composeBoxSelfDmContentHint;
397403

398-
case DmNarrow(otherRecipientIds: [final otherUserId]):
404+
case (DmNarrow(otherRecipientIds: [final otherUserId]), true):
399405
final store = PerAccountStoreWidget.of(context);
400406
final fullName = store.users[otherUserId]?.fullName;
401407
if (fullName == null) return zulipLocalizations.composeBoxGenericContentHint;
402408
return zulipLocalizations.composeBoxDmContentHint(fullName);
403409

404-
case DmNarrow(): // A group DM thread.
410+
case (DmNarrow(:final otherRecipientIds), false):
411+
return zulipLocalizations.composeBoxDeactivatedDmContentHint(otherRecipientIds.length);
412+
413+
case (DmNarrow(), true): // A group DM thread.
405414
return zulipLocalizations.composeBoxGroupDmContentHint;
406415
}
407416
}
@@ -412,7 +421,8 @@ class _FixedDestinationContentInput extends StatelessWidget {
412421
narrow: narrow,
413422
controller: controller,
414423
focusNode: focusNode,
415-
hintText: _hintText(context));
424+
hintText: _hintText(context),
425+
enabled: enabled);
416426
}
417427
}
418428

@@ -492,10 +502,15 @@ Future<void> _uploadFiles({
492502
}
493503

494504
abstract class _AttachUploadsButton extends StatelessWidget {
495-
const _AttachUploadsButton({required this.contentController, required this.contentFocusNode});
505+
const _AttachUploadsButton({
506+
required this.contentController,
507+
required this.contentFocusNode,
508+
required this.enabled,
509+
});
496510

497511
final ComposeContentController contentController;
498512
final FocusNode contentFocusNode;
513+
final bool enabled;
499514

500515
IconData get icon;
501516
String tooltip(ZulipLocalizations zulipLocalizations);
@@ -534,7 +549,7 @@ abstract class _AttachUploadsButton extends StatelessWidget {
534549
return IconButton(
535550
icon: Icon(icon),
536551
tooltip: tooltip(zulipLocalizations),
537-
onPressed: () => _handlePress(context));
552+
onPressed: enabled ? () => _handlePress(context) : null);
538553
}
539554
}
540555

@@ -578,7 +593,11 @@ Future<Iterable<_File>> _getFilePickerFiles(BuildContext context, FileType type)
578593
}
579594

580595
class _AttachFileButton extends _AttachUploadsButton {
581-
const _AttachFileButton({required super.contentController, required super.contentFocusNode});
596+
const _AttachFileButton({
597+
required super.contentController,
598+
required super.contentFocusNode,
599+
required super.enabled,
600+
});
582601

583602
@override
584603
IconData get icon => Icons.attach_file;
@@ -594,7 +613,11 @@ class _AttachFileButton extends _AttachUploadsButton {
594613
}
595614

596615
class _AttachMediaButton extends _AttachUploadsButton {
597-
const _AttachMediaButton({required super.contentController, required super.contentFocusNode});
616+
const _AttachMediaButton({
617+
required super.contentController,
618+
required super.contentFocusNode,
619+
required super.enabled,
620+
});
598621

599622
@override
600623
IconData get icon => Icons.image;
@@ -611,7 +634,11 @@ class _AttachMediaButton extends _AttachUploadsButton {
611634
}
612635

613636
class _AttachFromCameraButton extends _AttachUploadsButton {
614-
const _AttachFromCameraButton({required super.contentController, required super.contentFocusNode});
637+
const _AttachFromCameraButton({
638+
required super.contentController,
639+
required super.contentFocusNode,
640+
required super.enabled,
641+
});
615642

616643
@override
617644
IconData get icon => Icons.camera_alt;
@@ -667,11 +694,13 @@ class _SendButton extends StatefulWidget {
667694
required this.topicController,
668695
required this.contentController,
669696
required this.getDestination,
697+
this.enabled = true,
670698
});
671699

672700
final ComposeTopicController? topicController;
673701
final ComposeContentController contentController;
674702
final MessageDestination Function() getDestination;
703+
final bool enabled;
675704

676705
@override
677706
State<_SendButton> createState() => _SendButtonState();
@@ -776,7 +805,7 @@ class _SendButtonState extends State<_SendButton> {
776805
),
777806
color: foregroundColor,
778807
icon: const Icon(Icons.send),
779-
onPressed: _send));
808+
onPressed: widget.enabled ? _send : null));
780809
}
781810
}
782811

@@ -787,13 +816,15 @@ class _ComposeBoxLayout extends StatelessWidget {
787816
required this.sendButton,
788817
required this.contentController,
789818
required this.contentFocusNode,
819+
this.enabled = true,
790820
});
791821

792822
final Widget? topicInput;
793823
final Widget contentInput;
794824
final Widget sendButton;
795825
final ComposeContentController contentController;
796826
final FocusNode contentFocusNode;
827+
final bool enabled;
797828

798829
@override
799830
Widget build(BuildContext context) {
@@ -837,9 +868,21 @@ class _ComposeBoxLayout extends StatelessWidget {
837868
data: themeData.copyWith(
838869
iconTheme: themeData.iconTheme.copyWith(color: colorScheme.onSurfaceVariant)),
839870
child: Row(children: [
840-
_AttachFileButton(contentController: contentController, contentFocusNode: contentFocusNode),
841-
_AttachMediaButton(contentController: contentController, contentFocusNode: contentFocusNode),
842-
_AttachFromCameraButton(contentController: contentController, contentFocusNode: contentFocusNode),
871+
_AttachFileButton(
872+
contentController: contentController,
873+
contentFocusNode: contentFocusNode,
874+
enabled: enabled,
875+
),
876+
_AttachMediaButton(
877+
contentController: contentController,
878+
contentFocusNode: contentFocusNode,
879+
enabled: enabled,
880+
),
881+
_AttachFromCameraButton(
882+
contentController: contentController,
883+
contentFocusNode: contentFocusNode,
884+
enabled: enabled,
885+
),
843886
])),
844887
])))); }
845888
}
@@ -937,19 +980,28 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
937980

938981
@override
939982
Widget build(BuildContext context) {
983+
final store = PerAccountStoreWidget.of(context);
984+
final bool enabled = switch (widget.narrow) {
985+
DmNarrow(:final otherRecipientIds) => otherRecipientIds.every((id) =>
986+
store.users[id]?.isActive ?? true),
987+
TopicNarrow() => true,
988+
};
940989
return _ComposeBoxLayout(
941990
contentController: _contentController,
942991
contentFocusNode: _contentFocusNode,
943992
topicInput: null,
993+
enabled: enabled,
944994
contentInput: _FixedDestinationContentInput(
945995
narrow: widget.narrow,
946996
controller: _contentController,
947997
focusNode: _contentFocusNode,
998+
enabled: enabled,
948999
),
9491000
sendButton: _SendButton(
9501001
topicController: null,
9511002
contentController: _contentController,
9521003
getDestination: () => widget.narrow.destination,
1004+
enabled: enabled,
9531005
));
9541006
}
9551007
}

0 commit comments

Comments
 (0)