-
-
Notifications
You must be signed in to change notification settings - Fork 22
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
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2529 +/- ##
==========================================
+ Coverage 31.33% 31.88% +0.54%
==========================================
Files 716 724 +8
Lines 47369 47609 +240
==========================================
+ Hits 14843 15180 +337
+ Misses 32526 32429 -97
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
@@ -34,3 +34,12 @@ final selectedChatIdProvider = | |||
NotifierProvider<SelectedChatIdNotifier, String?>( | |||
() => SelectedChatIdNotifier(), | |||
); | |||
|
|||
final chatComposerDraftProvider = FutureProvider.autoDispose |
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.
@@ -48,6 +48,23 @@ extension ActerEditorStateHelpers on EditorState { | |||
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 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!!
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 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.
required String roomId, | ||
}) async { | ||
// trigger vibration haptic | ||
await HapticFeedback.heavyImpact(); |
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.
just a vibration haptic which is often commonly seen on opening message actions .i.e. WhatsApp, Signal etc.
@@ -22,29 +28,36 @@ void reactionSelectionAction({ | |||
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 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.
Positioned( | ||
left: position.dx, | ||
top: position.dy - 60, | ||
child: _AnimatedReactionSelector( |
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 evolved to single re-usable AnimatedActionsContainer
because we need similar effect for message actions :)
|
||
enum MessageAction { none, reply, edit } | ||
|
||
class ChatEditorState { |
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.
A new ChatEditorState
as opposed to other chat version ChatInputState
, but much more simplified and refined.
@@ -24,28 +26,29 @@ typedef RoomMsgId = ({String roomId, String uniqueId}); | |||
typedef MentionQuery = (String, MentionType); | |||
typedef ReactionItem = (String, List<ReactionRecord>); | |||
|
|||
final chatStateProvider = StateNotifierProvider.family<ChatRoomMessagesNotifier, | |||
ChatRoomState, String>( | |||
final chatMessagesStateProvider = StateNotifierProvider.family< |
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.
To avoid conflicting namings of similar providers from other version chat.
show RoomEventItem; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
||
class ChatEditorNotifier extends AutoDisposeNotifier<ChatEditorState> { |
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.
A new notifier for ChatEditorState
as opposed to ChatInputState
. But the benefit here is this new notifier uses Notifier
(newer API) from StateNotifier
migration.
@@ -55,6 +60,14 @@ class _ChatEditorState extends ConsumerState<ChatEditor> { | |||
// have it call the first time to adjust height | |||
_updateHeight(); | |||
WidgetsBinding.instance.addPostFrameCallback((_) => _loadDraft()); | |||
|
|||
ref.listenManual(chatEditorStateProvider, (prev, next) async { | |||
if (next.isEditing && |
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.
we only need Edit
case here to update EditorState
.
@@ -72,6 +85,26 @@ class _ChatEditorState extends ConsumerState<ChatEditor> { | |||
} | |||
} | |||
|
|||
void _handleEditing(RoomEventItem? item) { |
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 part handles editorState field update when action is Editing
.
if (chatEditorState.isReplying || chatEditorState.isEditing) { | ||
final msgItem = chatEditorState.selectedMsgItem; | ||
previewWidget = msgItem.map( | ||
(item) => ChatEditorActionsPreview( |
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 actual preview widget of reply/edit actions which appears on top of ChatEditor
.
final chatEditorState = ref.watch(chatEditorStateProvider); | ||
final isPreviewOpen = | ||
chatEditorState.isReplying || chatEditorState.isEditing; | ||
final radiusVal = isPreviewOpen ? 2.0 : 15.0; |
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.
To keep UI not ugly when preview widget is opened!
import 'package:flutter_gen/gen_l10n/l10n.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
||
class MessageActionsWidget extends ConsumerWidget { |
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 widget is responsible for rendering message actions options .i.e. copy/delete/reply/edit etc.
} | ||
|
||
void main() { | ||
group('Chat editor reply/edit preview tests', () { |
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.
And finally some widgets tests to ensure that actions state logics are working as intended!
}, | ||
); | ||
|
||
// testWidgets('closing edit preview resets chat editor state', |
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.
I'd probably need some help here. This test-case is fine logic wise but somehow tester
can't find close icon of preview widget. Would like to have thoughtful solutions here :)
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.
final transaction = this.transaction; | ||
final selection = this.selection; | ||
final node = transaction.document.root.children.last; | ||
transaction.deleteNode(node); | ||
transaction.insertNode([0], paragraphNode(text: '')); |
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.
Skipping review of this logic as I don't have much insight knowledge of AppFlowy Editor.
It fine to not write complicated calculation but just love to see alignment fixes which make it better UI feel. (Like MenuAction and Reaction can be in Right side for |
Hmm, I guess that's a fine suggestion to try. Will check and update if it fixes :) |
PR Description:
Adds the implementation of message actions UI .i.e. reply/edit/copy/delete. The PR grew to large change-set, particularly because of how these features are aligned within chat editor and have to make adjustments across entire module to have this in effect.
I have pointed out explanation, by detail where a reviewer should probably look at, explaining why certain things are intended the way it is needed to work.
PREVIEW:
(Desktop):
Screen.Recording.2025-02-04.at.22.57.00.mov
(Mobile):
Screen.Recording.2025-02-04.at.23.14.34.mov