Skip to content

Commit 3c48c68

Browse files
committed
compose_box: Disable the compose box in DMs with deactivated users
Fixes: #675
1 parent a038e72 commit 3c48c68

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
@@ -271,17 +271,18 @@ 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;
284-
285286
return InputDecorator(
286287
decoration: const InputDecoration(),
287288
child: ConstrainedBox(
@@ -303,6 +304,7 @@ class _ContentInput extends StatelessWidget {
303304
decoration: InputDecoration.collapsed(hintText: hintText),
304305
maxLines: null,
305306
textCapitalization: TextCapitalization.sentences,
307+
enabled: enabled,
306308
);
307309
}),
308310
));
@@ -377,32 +379,37 @@ class _FixedDestinationContentInput extends StatelessWidget {
377379
required this.narrow,
378380
required this.controller,
379381
required this.focusNode,
382+
required this.enabled,
380383
});
381384

382385
final SendableNarrow narrow;
383386
final ComposeContentController controller;
384387
final FocusNode focusNode;
388+
final bool enabled;
385389

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

395-
case DmNarrow(otherRecipientIds: []): // The self-1:1 thread.
399+
case (DmNarrow(otherRecipientIds: []), _): // The self-1:1 thread.
396400
return zulipLocalizations.composeBoxSelfDmContentHint;
397401

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

404-
case DmNarrow(): // A group DM thread.
408+
case (DmNarrow(), true): // A group DM thread.
405409
return zulipLocalizations.composeBoxGroupDmContentHint;
410+
411+
case (DmNarrow(), false):
412+
return zulipLocalizations.composeBoxDeactivatedDmContentHint;
406413
}
407414
}
408415

@@ -412,7 +419,8 @@ class _FixedDestinationContentInput extends StatelessWidget {
412419
narrow: narrow,
413420
controller: controller,
414421
focusNode: focusNode,
415-
hintText: _hintText(context));
422+
hintText: _hintText(context),
423+
enabled: enabled);
416424
}
417425
}
418426

@@ -492,10 +500,15 @@ Future<void> _uploadFiles({
492500
}
493501

494502
abstract class _AttachUploadsButton extends StatelessWidget {
495-
const _AttachUploadsButton({required this.contentController, required this.contentFocusNode});
503+
const _AttachUploadsButton({
504+
required this.contentController,
505+
required this.contentFocusNode,
506+
required this.enabled,
507+
});
496508

497509
final ComposeContentController contentController;
498510
final FocusNode contentFocusNode;
511+
final bool enabled;
499512

500513
IconData get icon;
501514
String tooltip(ZulipLocalizations zulipLocalizations);
@@ -534,7 +547,7 @@ abstract class _AttachUploadsButton extends StatelessWidget {
534547
return IconButton(
535548
icon: Icon(icon),
536549
tooltip: tooltip(zulipLocalizations),
537-
onPressed: () => _handlePress(context));
550+
onPressed: enabled ? () => _handlePress(context) : null);
538551
}
539552
}
540553

@@ -578,7 +591,11 @@ Future<Iterable<_File>> _getFilePickerFiles(BuildContext context, FileType type)
578591
}
579592

580593
class _AttachFileButton extends _AttachUploadsButton {
581-
const _AttachFileButton({required super.contentController, required super.contentFocusNode});
594+
const _AttachFileButton({
595+
required super.contentController,
596+
required super.contentFocusNode,
597+
required super.enabled,
598+
});
582599

583600
@override
584601
IconData get icon => Icons.attach_file;
@@ -594,7 +611,11 @@ class _AttachFileButton extends _AttachUploadsButton {
594611
}
595612

596613
class _AttachMediaButton extends _AttachUploadsButton {
597-
const _AttachMediaButton({required super.contentController, required super.contentFocusNode});
614+
const _AttachMediaButton({
615+
required super.contentController,
616+
required super.contentFocusNode,
617+
required super.enabled,
618+
});
598619

599620
@override
600621
IconData get icon => Icons.image;
@@ -611,7 +632,11 @@ class _AttachMediaButton extends _AttachUploadsButton {
611632
}
612633

613634
class _AttachFromCameraButton extends _AttachUploadsButton {
614-
const _AttachFromCameraButton({required super.contentController, required super.contentFocusNode});
635+
const _AttachFromCameraButton({
636+
required super.contentController,
637+
required super.contentFocusNode,
638+
required super.enabled,
639+
});
615640

616641
@override
617642
IconData get icon => Icons.camera_alt;
@@ -667,11 +692,13 @@ class _SendButton extends StatefulWidget {
667692
required this.topicController,
668693
required this.contentController,
669694
required this.getDestination,
695+
this.enabled = true,
670696
});
671697

672698
final ComposeTopicController? topicController;
673699
final ComposeContentController contentController;
674700
final MessageDestination Function() getDestination;
701+
final bool enabled;
675702

676703
@override
677704
State<_SendButton> createState() => _SendButtonState();
@@ -774,7 +801,7 @@ class _SendButtonState extends State<_SendButton> {
774801
),
775802
color: foregroundColor,
776803
icon: const Icon(Icons.send),
777-
onPressed: _send));
804+
onPressed: widget.enabled ? _send : null));
778805
}
779806
}
780807

@@ -785,13 +812,15 @@ class _ComposeBoxLayout extends StatelessWidget {
785812
required this.sendButton,
786813
required this.contentController,
787814
required this.contentFocusNode,
815+
this.enabled = true,
788816
});
789817

790818
final Widget? topicInput;
791819
final Widget contentInput;
792820
final Widget sendButton;
793821
final ComposeContentController contentController;
794822
final FocusNode contentFocusNode;
823+
final bool enabled;
795824

796825
@override
797826
Widget build(BuildContext context) {
@@ -835,9 +864,21 @@ class _ComposeBoxLayout extends StatelessWidget {
835864
data: themeData.copyWith(
836865
iconTheme: themeData.iconTheme.copyWith(color: colorScheme.onSurfaceVariant)),
837866
child: Row(children: [
838-
_AttachFileButton(contentController: contentController, contentFocusNode: contentFocusNode),
839-
_AttachMediaButton(contentController: contentController, contentFocusNode: contentFocusNode),
840-
_AttachFromCameraButton(contentController: contentController, contentFocusNode: contentFocusNode),
867+
_AttachFileButton(
868+
contentController: contentController,
869+
contentFocusNode: contentFocusNode,
870+
enabled: enabled,
871+
),
872+
_AttachMediaButton(
873+
contentController: contentController,
874+
contentFocusNode: contentFocusNode,
875+
enabled: enabled,
876+
),
877+
_AttachFromCameraButton(
878+
contentController: contentController,
879+
contentFocusNode: contentFocusNode,
880+
enabled: enabled,
881+
),
841882
])),
842883
])))); }
843884
}
@@ -925,6 +966,20 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
925966

926967
@override FocusNode get contentFocusNode => _contentFocusNode;
927968
final _contentFocusNode = FocusNode();
969+
late bool enabled;
970+
971+
@override
972+
void didChangeDependencies() {
973+
super.didChangeDependencies();
974+
final store = PerAccountStoreWidget.of(context);
975+
enabled = switch (widget.narrow) {
976+
DmNarrow(:final otherRecipientIds) => otherRecipientIds.every((id) =>
977+
store.users[id]?.isActive ?? true),
978+
TopicNarrow() => true,
979+
};
980+
981+
if (!enabled) _contentController.clear();
982+
}
928983

929984
@override
930985
void dispose() {
@@ -939,15 +994,18 @@ class _FixedDestinationComposeBoxState extends State<_FixedDestinationComposeBox
939994
contentController: _contentController,
940995
contentFocusNode: _contentFocusNode,
941996
topicInput: null,
997+
enabled: enabled,
942998
contentInput: _FixedDestinationContentInput(
943999
narrow: widget.narrow,
9441000
controller: _contentController,
9451001
focusNode: _contentFocusNode,
1002+
enabled: enabled,
9461003
),
9471004
sendButton: _SendButton(
9481005
topicController: null,
9491006
contentController: _contentController,
9501007
getDestination: () => widget.narrow.destination,
1008+
enabled: enabled,
9511009
));
9521010
}
9531011
}

0 commit comments

Comments
 (0)