-
-
Notifications
You must be signed in to change notification settings - Fork 21
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
Chat-NG: Message Actions #2529
Changes from all commits
181bf49
7725ba0
cad88fb
acb5b50
d4d3c49
a8d5df1
b826309
131932e
34bf589
941e997
4ef159f
a6a55c7
0295470
a6bd120
c1f628f
804afd5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,23 @@ | |
String intoMarkdown({AppFlowyEditorMarkdownCodec? codec}) { | ||
return (codec ?? defaultMarkdownCodec).encode(document); | ||
} | ||
|
||
/// clear the editor text with selection | ||
void clear() async { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is utility fn added as extension to |
||
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: '')); | ||
Comment on lines
+55
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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( | ||
selection, | ||
reason: SelectionUpdateReason.transaction, | ||
); | ||
apply(transaction); | ||
} | ||
} | ||
} | ||
|
||
extension ActerDocumentHelpers on Document { | ||
|
@@ -80,7 +97,7 @@ | |
}) { | ||
if (htmlContent != null) { | ||
final document = ActerDocumentHelpers._fromHtml(htmlContent); | ||
if (document != null) { | ||
if (document != null && !document.isEmpty) { | ||
return document; | ||
} | ||
} | ||
|
@@ -206,6 +223,7 @@ | |
void _triggerExport(ExportCallback exportFn) { | ||
final plain = editorState.intoMarkdown(); | ||
final htmlBody = editorState.intoHtml(); | ||
|
||
exportFn(plain, htmlBody != plain ? htmlBody : null); | ||
} | ||
|
||
|
@@ -378,6 +396,7 @@ | |
...standardCharacterShortcutEvents, | ||
if (roomId != null) ...mentionShortcuts(context, roomId), | ||
], | ||
disableAutoScroll: true, | ||
), | ||
), | ||
), | ||
|
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All these message actions along with other are moved entirely to |
||
BuildContext context, | ||
String body, | ||
) async { | ||
String msg = body.trim(); | ||
await Clipboard.setData( | ||
ClipboardData(text: msg), | ||
); | ||
if (context.mounted) { | ||
EasyLoading.showToast(L10n.of(context).messageCopiedToClipboard); | ||
Navigator.pop(context); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import 'package:acter/common/providers/chat_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'); | ||
|
||
Future<void> redactMessageAction( | ||
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(); | ||
// pop message action options | ||
Navigator.pop(context); | ||
await showAdaptiveDialog( | ||
context: context, | ||
builder: (context) => DefaultDialog( | ||
title: Text(lang.areYouSureYouWantToDeleteThisMessage), | ||
actions: <Widget>[ | ||
OutlinedButton( | ||
onPressed: () => Navigator.pop(context), | ||
child: Text(lang.no), | ||
), | ||
ActerPrimaryActionButton( | ||
onPressed: () async { | ||
try { | ||
final convo = await ref.read(chatProvider(roomId).future); | ||
await convo?.redactMessage( | ||
messageId, | ||
senderId, | ||
null, | ||
null, | ||
); | ||
chatEditorNotifier.unsetActions(); | ||
if (context.mounted) { | ||
Navigator.pop(context); | ||
} | ||
} catch (e, s) { | ||
_log.severe('Redacting message failed', e, s); | ||
if (!context.mounted) return; | ||
EasyLoading.showError( | ||
lang.redactionFailed(e), | ||
duration: const Duration(seconds: 3), | ||
); | ||
Navigator.pop(context); | ||
} | ||
}, | ||
child: Text(lang.yes), | ||
), | ||
], | ||
), | ||
); | ||
} |
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( | ||
BuildContext context, | ||
RoomEventItem item, | ||
String messageId, | ||
String roomId, | ||
) async { | ||
final lang = L10n.of(context); | ||
final senderId = item.sender(); | ||
// pop message action options | ||
Navigator.pop(context); | ||
await openReportContentDialog( | ||
context, | ||
title: lang.reportThisMessage, | ||
description: lang.reportMessageContent, | ||
senderId: senderId, | ||
roomId: roomId, | ||
eventId: messageId, | ||
); | ||
} |
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({ | ||
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
||
showGeneralDialog( | ||
context: context, | ||
|
@@ -22,26 +28,38 @@ | |
barrierColor: Colors.transparent, | ||
transitionDuration: const Duration(milliseconds: 200), | ||
pageBuilder: (context, animation, secondaryAnimation) { | ||
return Stack( | ||
return Column( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved from |
||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: [ | ||
_ReactionOverlay( | ||
_BlurOverlay( | ||
animation: animation, | ||
child: const SizedBox.expand(), | ||
), | ||
Positioned( | ||
left: position.dx, | ||
top: position.dy, | ||
width: messageSize.width, | ||
child: messageWidget, | ||
child: const SizedBox.shrink(), | ||
), | ||
Positioned( | ||
left: position.dx, | ||
top: position.dy - 60, | ||
child: _AnimatedReactionSelector( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is evolved to single re-usable |
||
// Reaction Row | ||
Align( | ||
alignment: isMe ? Alignment.centerRight : Alignment.centerLeft, | ||
child: _AnimatedActionsContainer( | ||
animation: animation, | ||
messageId: messageId, | ||
tagId: messageId, | ||
child: ReactionSelector( | ||
isMe: isMe, | ||
messageId: '$messageId-reactions', | ||
roomId: roomId, | ||
), | ||
), | ||
), | ||
// Message | ||
Center(child: messageWidget), | ||
// Message actions | ||
Align( | ||
alignment: isMe ? Alignment.centerRight : Alignment.centerLeft, | ||
child: _AnimatedActionsContainer( | ||
animation: animation, | ||
tagId: '$messageId-actions', | ||
child: MessageActionsWidget( | ||
isMe: isMe, | ||
canRedact: canRedact, | ||
item: item, | ||
messageId: messageId, | ||
roomId: roomId, | ||
), | ||
|
@@ -53,11 +71,11 @@ | |
); | ||
} | ||
|
||
class _ReactionOverlay extends StatelessWidget { | ||
class _BlurOverlay extends StatelessWidget { | ||
final Animation<double> animation; | ||
final Widget child; | ||
|
||
const _ReactionOverlay({ | ||
const _BlurOverlay({ | ||
required this.animation, | ||
required this.child, | ||
}); | ||
|
@@ -86,21 +104,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({ | ||
required this.animation, | ||
required this.child, | ||
required this.messageId, | ||
required this.tagId, | ||
}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Hero( | ||
tag: messageId, | ||
tag: tagId, | ||
child: Material( | ||
color: Colors.transparent, | ||
child: AnimatedBuilder( | ||
|
There was a problem hiding this comment.
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.