diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 1dc215b62..c234a102f 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -1061,6 +1061,8 @@ "type": "text", "placeholders": {} }, + "redactMessageDescription": "The message will be redacted for all participants in this conversation. This cannot be undone.", + "optionalRedactReason": "(Optional) Reason for redacting this message...", "invitedUser": "📩 {username} invited {targetName}", "@invitedUser": { "type": "text", @@ -1568,6 +1570,21 @@ "type": "text", "placeholders": {} }, + "redactedBy": "Redacted by {username}", + "@redactedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "redactedByBecause": "Redacted by {username} because: \"{reason}\"", + "@redactedByBecause": { + "type": "text", + "placeholders": { + "username": {}, + "reason": {} + } + }, "redactedAnEvent": "{username} redacted an event", "@redactedAnEvent": { "type": "text", diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 053dffda5..9ab086fc9 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -734,22 +734,28 @@ class ChatController extends State { } void redactEventsAction() async { - final confirmed = await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context)!.messageWillBeRemovedWarning, - okLabel: L10n.of(context)!.remove, - cancelLabel: L10n.of(context)!.cancel, - ) == - OkCancelResult.ok; - if (!confirmed) return; + final reasonInput = await showTextInputDialog( + context: context, + title: L10n.of(context)!.redactMessage, + message: L10n.of(context)!.redactMessageDescription, + isDestructiveAction: true, + textFields: [ + DialogTextField( + hintText: L10n.of(context)!.optionalRedactReason, + ), + ], + okLabel: L10n.of(context)!.remove, + cancelLabel: L10n.of(context)!.cancel, + ); + if (reasonInput == null) return; + final reason = reasonInput.single.isEmpty ? null : reasonInput.single; for (final event in selectedEvents) { await showFutureLoadingDialog( context: context, future: () async { if (event.status.isSent) { if (event.canRedact) { - await event.redactEvent(); + await event.redactEvent(reason: reason); } else { final client = currentRoomBundle.firstWhere( (cl) => selectedEvents.first.senderId == cl!.userID, @@ -759,7 +765,9 @@ class ChatController extends State { return; } final room = client.getRoomById(roomId)!; - await Event.fromJson(event.toJson(), room).redactEvent(); + await Event.fromJson(event.toJson(), room).redactEvent( + reason: reason, + ); } } else { await event.remove(); diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 5b3de582c..c576c26a1 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -385,8 +385,8 @@ class Message extends StatelessWidget { container = row; } - if (event.messageType == MessageTypes.BadEncrypted || event.redacted) { - container = Opacity(opacity: 0.33, child: container); + if (event.messageType == MessageTypes.BadEncrypted) { + container = Opacity(opacity: 0.4, child: container); } return Swipeable( diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 662e34397..eba77d5c0 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -162,7 +162,7 @@ class MessageContent extends StatelessWidget { return _ButtonContent( textColor: buttonTextColor, onPressed: () => _verifyOrRequestKey(context), - icon: const Icon(Icons.lock_outline), + icon: '🔒', label: L10n.of(context)!.encrypted, fontSize: fontSize, ); @@ -208,12 +208,19 @@ class MessageContent extends StatelessWidget { return FutureBuilder( future: event.redactedBecause?.fetchSenderUser(), builder: (context, snapshot) { + final reason = + event.redactedBecause?.content.tryGet('reason'); + final redactedBy = snapshot.data?.calcDisplayname() ?? + event.redactedBecause?.senderId.localpart ?? + L10n.of(context)!.user; return _ButtonContent( - label: L10n.of(context)!.redactedAnEvent( - snapshot.data?.calcDisplayname() ?? - event.senderFromMemoryOrFallback.calcDisplayname(), - ), - icon: const Icon(Icons.delete_outlined), + label: reason == null + ? L10n.of(context)!.redactedBy(redactedBy) + : L10n.of(context)!.redactedByBecause( + redactedBy, + reason, + ), + icon: '🗑ī¸', textColor: buttonTextColor, onPressed: () => onInfoTab!(event), fontSize: fontSize, @@ -263,7 +270,7 @@ class MessageContent extends StatelessWidget { snapshot.data?.calcDisplayname() ?? event.senderFromMemoryOrFallback.calcDisplayname(), ), - icon: const Icon(Icons.phone_outlined), + icon: '📞', textColor: buttonTextColor, onPressed: () => onInfoTab!(event), fontSize: fontSize, @@ -280,7 +287,7 @@ class MessageContent extends StatelessWidget { event.senderFromMemoryOrFallback.calcDisplayname(), event.type, ), - icon: const Icon(Icons.info_outlined), + icon: 'ℹī¸', textColor: buttonTextColor, onPressed: () => onInfoTab!(event), fontSize: fontSize, @@ -294,7 +301,7 @@ class MessageContent extends StatelessWidget { class _ButtonContent extends StatelessWidget { final void Function() onPressed; final String label; - final Icon icon; + final String icon; final Color? textColor; final double fontSize; @@ -311,20 +318,12 @@ class _ButtonContent extends StatelessWidget { Widget build(BuildContext context) { return InkWell( onTap: onPressed, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - icon, - const SizedBox(width: 8), - Text( - label, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: textColor, - fontSize: fontSize, - ), - ), - ], + child: Text( + '$icon $label', + style: TextStyle( + color: textColor, + fontSize: fontSize, + ), ), ); }