Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chat-NG: Message Actions #2529

Merged
merged 16 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changes/2529-chat_ng-message-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[Labs] Chat-NG:

- [Feature] Now you can do message actions on message .i.e. edit/reply/copy/delete/report.
9 changes: 9 additions & 0 deletions app/lib/common/providers/chat_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,12 @@
NotifierProvider<SelectedChatIdNotifier, String?>(
() => SelectedChatIdNotifier(),
);

final chatComposerDraftProvider = FutureProvider.autoDispose
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just moved from chat module to much more common providers section because it makes sense but also to avoid import conflicts.

.family<ComposeDraft?, String>((ref, roomId) async {
final chat = await ref.watch(chatProvider(roomId).future);

Check warning on line 40 in app/lib/common/providers/chat_providers.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/common/providers/chat_providers.dart#L40

Added line #L40 was not covered by tests
if (chat == null) {
return null;
}
return (await chat.msgDraft().then((val) => val.draft()));

Check warning on line 44 in app/lib/common/providers/chat_providers.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/common/providers/chat_providers.dart#L44

Added line #L44 was not covered by tests
});
21 changes: 20 additions & 1 deletion app/lib/common/widgets/html_editor/html_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@
String intoMarkdown({AppFlowyEditorMarkdownCodec? codec}) {
return (codec ?? defaultMarkdownCodec).encode(document);
}

/// clear the editor text with selection
void clear() async {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is utility fn added as extension to textEditorState to clear document content. Because it's usage is widely needed so having it neatly separated is so nice!!

if (!document.isEmpty) {
gtalha07 marked this conversation as resolved.
Show resolved Hide resolved
final transaction = this.transaction;
final selection = this.selection;
final node = transaction.document.root.children.last;
transaction.deleteNode(node);
transaction.insertNode([0], paragraphNode(text: ''));

Check warning on line 59 in app/lib/common/widgets/html_editor/html_editor.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/common/widgets/html_editor/html_editor.dart#L55-L59

Added lines #L55 - L59 were not covered by tests
Comment on lines +55 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping review of this logic as I don't have much insight knowledge of AppFlowy Editor.


updateSelectionWithReason(

Check warning on line 61 in app/lib/common/widgets/html_editor/html_editor.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/common/widgets/html_editor/html_editor.dart#L61

Added line #L61 was not covered by tests
selection,
reason: SelectionUpdateReason.transaction,
);
apply(transaction);

Check warning on line 65 in app/lib/common/widgets/html_editor/html_editor.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/common/widgets/html_editor/html_editor.dart#L65

Added line #L65 was not covered by tests
}
}
}

extension ActerDocumentHelpers on Document {
Expand Down Expand Up @@ -80,7 +97,7 @@
}) {
if (htmlContent != null) {
final document = ActerDocumentHelpers._fromHtml(htmlContent);
if (document != null) {
if (document != null && !document.isEmpty) {

Check warning on line 100 in app/lib/common/widgets/html_editor/html_editor.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/common/widgets/html_editor/html_editor.dart#L100

Added line #L100 was not covered by tests
return document;
}
}
Expand Down Expand Up @@ -206,6 +223,7 @@
void _triggerExport(ExportCallback exportFn) {
final plain = editorState.intoMarkdown();
final htmlBody = editorState.intoHtml();

exportFn(plain, htmlBody != plain ? htmlBody : null);
}

Expand Down Expand Up @@ -378,6 +396,7 @@
...standardCharacterShortcutEvents,
if (roomId != null) ...mentionShortcuts(context, roomId),
],
disableAutoScroll: true,
),
),
),
Expand Down
9 changes: 0 additions & 9 deletions app/lib/features/chat/providers/chat_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,6 @@ final chatStateProvider =
(ref, roomId) => ChatRoomNotifier(ref: ref, roomId: roomId),
);

final chatComposerDraftProvider = FutureProvider.autoDispose
.family<ComposeDraft?, String>((ref, roomId) async {
final chat = await ref.watch(chatProvider(roomId).future);
if (chat == null) {
return null;
}
return (await chat.msgDraft().then((val) => val.draft()));
});

final chatTopic =
FutureProvider.autoDispose.family<String?, String>((ref, roomId) async {
final c = await ref.watch(chatProvider(roomId).future);
Expand Down
18 changes: 18 additions & 0 deletions app/lib/features/chat_ng/actions/copy_message_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';

Future<void> copyMessageAction(

Check warning on line 6 in app/lib/features/chat_ng/actions/copy_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/copy_message_action.dart#L6

Added line #L6 was not covered by tests
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these message actions along with other are moved entirely to actions for legibility.

BuildContext context,
String body,
) async {
String msg = body.trim();
await Clipboard.setData(
ClipboardData(text: msg),

Check warning on line 12 in app/lib/features/chat_ng/actions/copy_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/copy_message_action.dart#L10-L12

Added lines #L10 - L12 were not covered by tests
);
if (context.mounted) {
EasyLoading.showToast(L10n.of(context).messageCopiedToClipboard);
Navigator.pop(context);

Check warning on line 16 in app/lib/features/chat_ng/actions/copy_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/copy_message_action.dart#L14-L16

Added lines #L14 - L16 were not covered by tests
}
}
67 changes: 67 additions & 0 deletions app/lib/features/chat_ng/actions/redact_message_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:acter/common/providers/chat_providers.dart';
import 'package:acter/common/providers/room_providers.dart';
import 'package:acter/common/toolkit/buttons/primary_action_button.dart';
import 'package:acter/common/widgets/default_dialog.dart';
import 'package:acter/features/chat_ng/providers/chat_room_messages_provider.dart';
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'
show RoomEventItem;
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:logging/logging.dart';

final _log = Logger('a3::chat::message_actions_redact');

Check warning on line 14 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L14

Added line #L14 was not covered by tests

Future<void> redactMessageAction(

Check warning on line 16 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L16

Added line #L16 was not covered by tests
BuildContext context,
WidgetRef ref,
RoomEventItem item,
String messageId,
String roomId,
) async {
final chatEditorNotifier = ref.watch(chatEditorStateProvider.notifier);
final lang = L10n.of(context);
final senderId = item.sender();

Check warning on line 25 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L23-L25

Added lines #L23 - L25 were not covered by tests
// pop message action options
Navigator.pop(context);
await showAdaptiveDialog(

Check warning on line 28 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L27-L28

Added lines #L27 - L28 were not covered by tests
context: context,
builder: (context) => DefaultDialog(
title: Text(lang.areYouSureYouWantToDeleteThisMessage),
actions: <Widget>[
OutlinedButton(
onPressed: () => Navigator.pop(context),
child: Text(lang.no),

Check warning on line 35 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L30-L35

Added lines #L30 - L35 were not covered by tests
),
ActerPrimaryActionButton(
onPressed: () async {

Check warning on line 38 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L37-L38

Added lines #L37 - L38 were not covered by tests
try {
final convo = await ref.read(chatProvider(roomId).future);
if (convo == null) throw RoomNotFound();
gtalha07 marked this conversation as resolved.
Show resolved Hide resolved
await convo.redactMessage(

Check warning on line 42 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L40-L42

Added lines #L40 - L42 were not covered by tests
messageId,
senderId,
null,
null,
);
chatEditorNotifier.unsetActions();
if (context.mounted) {
Navigator.pop(context);

Check warning on line 50 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L48-L50

Added lines #L48 - L50 were not covered by tests
}
} catch (e, s) {
_log.severe('Redacting message failed', e, s);
if (!context.mounted) return;
EasyLoading.showError(
lang.redactionFailed(e),

Check warning on line 56 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L53-L56

Added lines #L53 - L56 were not covered by tests
duration: const Duration(seconds: 3),
);
Navigator.pop(context);

Check warning on line 59 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L59

Added line #L59 was not covered by tests
}
},
child: Text(lang.yes),

Check warning on line 62 in app/lib/features/chat_ng/actions/redact_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/redact_message_action.dart#L62

Added line #L62 was not covered by tests
),
],
),
);
}
25 changes: 25 additions & 0 deletions app/lib/features/chat_ng/actions/report_message_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:acter/common/actions/report_content.dart';
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'
show RoomEventItem;
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';

Future<void> reportMessageAction(

Check warning on line 7 in app/lib/features/chat_ng/actions/report_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/report_message_action.dart#L7

Added line #L7 was not covered by tests
BuildContext context,
RoomEventItem item,
String messageId,
String roomId,
) async {
final lang = L10n.of(context);
final senderId = item.sender();

Check warning on line 14 in app/lib/features/chat_ng/actions/report_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/report_message_action.dart#L13-L14

Added lines #L13 - L14 were not covered by tests
// pop message action options
Navigator.pop(context);
await openReportContentDialog(

Check warning on line 17 in app/lib/features/chat_ng/actions/report_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/report_message_action.dart#L16-L17

Added lines #L16 - L17 were not covered by tests
context,
title: lang.reportThisMessage,
description: lang.reportMessageContent,

Check warning on line 20 in app/lib/features/chat_ng/actions/report_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/report_message_action.dart#L19-L20

Added lines #L19 - L20 were not covered by tests
senderId: senderId,
roomId: roomId,
eventId: messageId,
);
}
25 changes: 9 additions & 16 deletions app/lib/features/chat_ng/actions/send_message_action.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:acter/common/providers/chat_providers.dart';
import 'package:acter/common/widgets/html_editor/html_editor.dart';
import 'package:acter/features/chat/models/chat_input_state/chat_input_state.dart';
import 'package:acter/features/chat/providers/chat_providers.dart';
import 'package:acter/features/chat_ng/providers/chat_room_messages_provider.dart';
import 'package:acter/features/home/providers/client_providers.dart';
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart' show MsgDraft;
import 'package:appflowy_editor/appflowy_editor.dart';
Expand Down Expand Up @@ -40,35 +40,28 @@
}

// actually send it out
final inputState = ref.read(chatInputProvider);
final chatEditorState = ref.read(chatEditorStateProvider);

Check warning on line 43 in app/lib/features/chat_ng/actions/send_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/send_message_action.dart#L43

Added line #L43 was not covered by tests
final stream = await ref.read(timelineStreamProvider(roomId).future);

if (inputState.selectedMessageState == SelectedMessageState.replyTo) {
final remoteId = inputState.selectedMessage?.remoteId;
if (chatEditorState.isReplying) {
final remoteId = chatEditorState.selectedMsgItem?.eventId();

Check warning on line 47 in app/lib/features/chat_ng/actions/send_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/send_message_action.dart#L46-L47

Added lines #L46 - L47 were not covered by tests
if (remoteId == null) throw 'remote id of sel msg not available';
await stream.replyMessage(remoteId, draft);
} else if (inputState.selectedMessageState == SelectedMessageState.edit) {
final remoteId = inputState.selectedMessage?.remoteId;
} else if (chatEditorState.isEditing) {
final remoteId = chatEditorState.selectedMsgItem?.eventId();

Check warning on line 51 in app/lib/features/chat_ng/actions/send_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/send_message_action.dart#L50-L51

Added lines #L50 - L51 were not covered by tests
if (remoteId == null) throw 'remote id of sel msg not available';
await stream.editMessage(remoteId, draft);
} else {
await stream.sendMessage(draft);
}

ref.read(chatInputProvider.notifier).messageSent();
final transaction = textEditorState.transaction;
final nodes = transaction.document.root.children;
// delete all nodes of document (reset)
transaction.document.delete([0], nodes.length);
final delta = Delta()..insert('');
// insert empty text node
transaction.document.insert([0], [paragraphNode(delta: delta)]);
await textEditorState.apply(transaction, withUpdateSelection: false);
// FIXME: works for single line text, but doesn't get focus on multi-line (iOS)
textEditorState.moveCursorForward(SelectionMoveRange.line);
textEditorState.clear();

Check warning on line 59 in app/lib/features/chat_ng/actions/send_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/send_message_action.dart#L59

Added line #L59 was not covered by tests

// also clear composed state
final convo = await ref.read(chatProvider(roomId).future);
final notifier = ref.read(chatEditorStateProvider.notifier);
notifier.unsetActions();

Check warning on line 64 in app/lib/features/chat_ng/actions/send_message_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/actions/send_message_action.dart#L63-L64

Added lines #L63 - L64 were not covered by tests
if (convo != null) {
await convo.saveMsgDraft(
textEditorState.intoMarkdown(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import 'dart:ui';

import 'package:acter/features/chat_ng/widgets/message_actions_widget.dart';
import 'package:acter/features/chat_ng/widgets/reactions/reaction_selector.dart';
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'
show RoomEventItem;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

// reaction selector action on chat message
void reactionSelectionAction({
// message actions on chat message
void messageActions({

Check warning on line 11 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L11

Added line #L11 was not covered by tests
required BuildContext context,
required Widget messageWidget,
required bool isMe,
required String roomId,
required bool canRedact,
required RoomEventItem item,
required String messageId,
}) {
final RenderBox box = context.findRenderObject() as RenderBox;
final Offset position = box.localToGlobal(Offset.zero);
final messageSize = box.size;
required String roomId,
}) async {
// trigger vibration haptic
await HapticFeedback.heavyImpact();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a vibration haptic which is often commonly seen on opening message actions .i.e. WhatsApp, Signal etc.

if (!context.mounted) return;

Check warning on line 22 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L21-L22

Added lines #L21 - L22 were not covered by tests

showGeneralDialog(
context: context,
Expand All @@ -22,29 +28,36 @@
barrierColor: Colors.transparent,
transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (context, animation, secondaryAnimation) {
return Stack(
return Column(

Check warning on line 31 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L31

Added line #L31 was not covered by tests
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from Stack to Column layout. Because it allows better positioning for message actions + reaction selector and makes the selected message appeared centered regardless of its actual position in message list.

mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_ReactionOverlay(
_BlurOverlay(

Check warning on line 35 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L35

Added line #L35 was not covered by tests
animation: animation,
child: const SizedBox.expand(),
child: const SizedBox.shrink(),
),
Positioned(
left: position.dx,
top: position.dy,
width: messageSize.width,
child: messageWidget,
// Reaction Row
_AnimatedActionsContainer(

Check warning on line 40 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L40

Added line #L40 was not covered by tests
animation: animation,
tagId: messageId,
child: ReactionSelector(

Check warning on line 43 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L43

Added line #L43 was not covered by tests
isMe: isMe,
messageId: '$messageId-reactions',

Check warning on line 45 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L45

Added line #L45 was not covered by tests
roomId: roomId,
),
),
Positioned(
left: position.dx,
top: position.dy - 60,
child: _AnimatedReactionSelector(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is evolved to single re-usable AnimatedActionsContainer because we need similar effect for message actions :)

animation: animation,
// Message
messageWidget,
// Message actions
_AnimatedActionsContainer(

Check warning on line 52 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L52

Added line #L52 was not covered by tests
animation: animation,
tagId: '$messageId-actions',
child: MessageActionsWidget(

Check warning on line 55 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L54-L55

Added lines #L54 - L55 were not covered by tests
isMe: isMe,
canRedact: canRedact,
item: item,
messageId: messageId,
child: ReactionSelector(
isMe: isMe,
messageId: messageId,
roomId: roomId,
),
roomId: roomId,
),
),
],
Expand All @@ -53,11 +66,11 @@
);
}

class _ReactionOverlay extends StatelessWidget {
class _BlurOverlay extends StatelessWidget {
final Animation<double> animation;
final Widget child;

const _ReactionOverlay({
const _BlurOverlay({

Check warning on line 73 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L73

Added line #L73 was not covered by tests
required this.animation,
required this.child,
});
Expand Down Expand Up @@ -86,21 +99,21 @@
}
}

class _AnimatedReactionSelector extends StatelessWidget {
class _AnimatedActionsContainer extends StatelessWidget {
final Animation<double> animation;
final Widget child;
final String messageId;
final String tagId;

const _AnimatedReactionSelector({
const _AnimatedActionsContainer({

Check warning on line 107 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L107

Added line #L107 was not covered by tests
required this.animation,
required this.child,
required this.messageId,
required this.tagId,
});

@override
Widget build(BuildContext context) {
return Hero(
tag: messageId,
tag: tagId,

Check warning on line 116 in app/lib/features/chat_ng/dialogs/message_actions.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/chat_ng/dialogs/message_actions.dart#L116

Added line #L116 was not covered by tests
child: Material(
color: Colors.transparent,
child: AnimatedBuilder(
Expand Down
Loading
Loading