Skip to content

Commit b6023ad

Browse files
committed
compose_box: Disable the compose box in DMs with deactivated users
Fixes: #675
1 parent 590fb69 commit b6023ad

File tree

4 files changed

+304
-15
lines changed

4 files changed

+304
-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: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -271,16 +271,21 @@ 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) {
287+
controller.clear();
288+
}
284289

285290
return InputDecorator(
286291
decoration: const InputDecoration(),
@@ -303,6 +308,7 @@ class _ContentInput extends StatelessWidget {
303308
decoration: InputDecoration.collapsed(hintText: hintText),
304309
maxLines: null,
305310
textCapitalization: TextCapitalization.sentences,
311+
enabled: enabled,
306312
);
307313
}),
308314
));
@@ -377,31 +383,36 @@ class _FixedDestinationContentInput extends StatelessWidget {
377383
required this.narrow,
378384
required this.controller,
379385
required this.focusNode,
386+
required this.enabled,
380387
});
381388

382389
final SendableNarrow narrow;
383390
final ComposeContentController controller;
384391
final FocusNode focusNode;
392+
final bool enabled;
385393

386394
String _hintText(BuildContext context) {
387395
final zulipLocalizations = ZulipLocalizations.of(context);
388-
switch (narrow) {
389-
case TopicNarrow(:final streamId, :final topic):
396+
switch ((narrow, enabled)) {
397+
case (TopicNarrow(:final streamId, :final topic), _):
390398
final store = PerAccountStoreWidget.of(context);
391399
final streamName = store.streams[streamId]?.name
392400
?? zulipLocalizations.composeBoxUnknownChannelName;
393401
return zulipLocalizations.composeBoxChannelContentHint(streamName, topic);
394402

395-
case DmNarrow(otherRecipientIds: []): // The self-1:1 thread.
403+
case (DmNarrow(otherRecipientIds: []), _): // The self-1:1 thread.
396404
return zulipLocalizations.composeBoxSelfDmContentHint;
397405

398-
case DmNarrow(otherRecipientIds: [final otherUserId]):
406+
case (DmNarrow(otherRecipientIds: [final otherUserId]), true):
399407
final store = PerAccountStoreWidget.of(context);
400408
final fullName = store.users[otherUserId]?.fullName;
401409
if (fullName == null) return zulipLocalizations.composeBoxGenericContentHint;
402410
return zulipLocalizations.composeBoxDmContentHint(fullName);
403411

404-
case DmNarrow(): // A group DM thread.
412+
case (DmNarrow(:final otherRecipientIds), false):
413+
return zulipLocalizations.composeBoxDeactivatedDmContentHint(otherRecipientIds.length);
414+
415+
case (DmNarrow(), true): // A group DM thread.
405416
return zulipLocalizations.composeBoxGroupDmContentHint;
406417
}
407418
}
@@ -412,7 +423,8 @@ class _FixedDestinationContentInput extends StatelessWidget {
412423
narrow: narrow,
413424
controller: controller,
414425
focusNode: focusNode,
415-
hintText: _hintText(context));
426+
hintText: _hintText(context),
427+
enabled: enabled);
416428
}
417429
}
418430

@@ -492,10 +504,15 @@ Future<void> _uploadFiles({
492504
}
493505

494506
abstract class _AttachUploadsButton extends StatelessWidget {
495-
const _AttachUploadsButton({required this.contentController, required this.contentFocusNode});
507+
const _AttachUploadsButton({
508+
required this.contentController,
509+
required this.contentFocusNode,
510+
required this.enabled,
511+
});
496512

497513
final ComposeContentController contentController;
498514
final FocusNode contentFocusNode;
515+
final bool enabled;
499516

500517
IconData get icon;
501518
String tooltip(ZulipLocalizations zulipLocalizations);
@@ -534,7 +551,7 @@ abstract class _AttachUploadsButton extends StatelessWidget {
534551
return IconButton(
535552
icon: Icon(icon),
536553
tooltip: tooltip(zulipLocalizations),
537-
onPressed: () => _handlePress(context));
554+
onPressed: enabled ? () => _handlePress(context) : null);
538555
}
539556
}
540557

@@ -578,7 +595,11 @@ Future<Iterable<_File>> _getFilePickerFiles(BuildContext context, FileType type)
578595
}
579596

580597
class _AttachFileButton extends _AttachUploadsButton {
581-
const _AttachFileButton({required super.contentController, required super.contentFocusNode});
598+
const _AttachFileButton({
599+
required super.contentController,
600+
required super.contentFocusNode,
601+
required super.enabled,
602+
});
582603

583604
@override
584605
IconData get icon => Icons.attach_file;
@@ -594,7 +615,11 @@ class _AttachFileButton extends _AttachUploadsButton {
594615
}
595616

596617
class _AttachMediaButton extends _AttachUploadsButton {
597-
const _AttachMediaButton({required super.contentController, required super.contentFocusNode});
618+
const _AttachMediaButton({
619+
required super.contentController,
620+
required super.contentFocusNode,
621+
required super.enabled,
622+
});
598623

599624
@override
600625
IconData get icon => Icons.image;
@@ -611,7 +636,11 @@ class _AttachMediaButton extends _AttachUploadsButton {
611636
}
612637

613638
class _AttachFromCameraButton extends _AttachUploadsButton {
614-
const _AttachFromCameraButton({required super.contentController, required super.contentFocusNode});
639+
const _AttachFromCameraButton({
640+
required super.contentController,
641+
required super.contentFocusNode,
642+
required super.enabled,
643+
});
615644

616645
@override
617646
IconData get icon => Icons.camera_alt;
@@ -667,11 +696,13 @@ class _SendButton extends StatefulWidget {
667696
required this.topicController,
668697
required this.contentController,
669698
required this.getDestination,
699+
this.enabled = true,
670700
});
671701

672702
final ComposeTopicController? topicController;
673703
final ComposeContentController contentController;
674704
final MessageDestination Function() getDestination;
705+
final bool enabled;
675706

676707
@override
677708
State<_SendButton> createState() => _SendButtonState();
@@ -776,7 +807,7 @@ class _SendButtonState extends State<_SendButton> {
776807
),
777808
color: foregroundColor,
778809
icon: const Icon(Icons.send),
779-
onPressed: _send));
810+
onPressed: widget.enabled ? _send : null));
780811
}
781812
}
782813

@@ -787,13 +818,15 @@ class _ComposeBoxLayout extends StatelessWidget {
787818
required this.sendButton,
788819
required this.contentController,
789820
required this.contentFocusNode,
821+
this.enabled = true,
790822
});
791823

792824
final Widget? topicInput;
793825
final Widget contentInput;
794826
final Widget sendButton;
795827
final ComposeContentController contentController;
796828
final FocusNode contentFocusNode;
829+
final bool enabled;
797830

798831
@override
799832
Widget build(BuildContext context) {
@@ -837,9 +870,21 @@ class _ComposeBoxLayout extends StatelessWidget {
837870
data: themeData.copyWith(
838871
iconTheme: themeData.iconTheme.copyWith(color: colorScheme.onSurfaceVariant)),
839872
child: Row(children: [
840-
_AttachFileButton(contentController: contentController, contentFocusNode: contentFocusNode),
841-
_AttachMediaButton(contentController: contentController, contentFocusNode: contentFocusNode),
842-
_AttachFromCameraButton(contentController: contentController, contentFocusNode: contentFocusNode),
873+
_AttachFileButton(
874+
contentController: contentController,
875+
contentFocusNode: contentFocusNode,
876+
enabled: enabled,
877+
),
878+
_AttachMediaButton(
879+
contentController: contentController,
880+
contentFocusNode: contentFocusNode,
881+
enabled: enabled,
882+
),
883+
_AttachFromCameraButton(
884+
contentController: contentController,
885+
contentFocusNode: contentFocusNode,
886+
enabled: enabled,
887+
),
843888
])),
844889
])))); }
845890
}
@@ -937,19 +982,28 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
937982

938983
@override
939984
Widget build(BuildContext context) {
985+
final store = PerAccountStoreWidget.of(context);
986+
final bool enabled = switch (widget.narrow) {
987+
DmNarrow(:final otherRecipientIds) => otherRecipientIds.every((id) =>
988+
store.users[id]?.isActive ?? true),
989+
TopicNarrow() => true,
990+
};
940991
return _ComposeBoxLayout(
941992
contentController: _contentController,
942993
contentFocusNode: _contentFocusNode,
943994
topicInput: null,
995+
enabled: enabled,
944996
contentInput: _FixedDestinationContentInput(
945997
narrow: widget.narrow,
946998
controller: _contentController,
947999
focusNode: _contentFocusNode,
1000+
enabled: enabled,
9481001
),
9491002
sendButton: _SendButton(
9501003
topicController: null,
9511004
contentController: _contentController,
9521005
getDestination: () => widget.narrow.destination,
1006+
enabled: enabled,
9531007
));
9541008
}
9551009
}

0 commit comments

Comments
 (0)