diff --git a/app/lib/features/chat/models/chat_input_state/chat_input_state.dart b/app/lib/features/chat/models/chat_input_state/chat_input_state.dart index 5ca30fdb074d..f237869841c0 100644 --- a/app/lib/features/chat/models/chat_input_state/chat_input_state.dart +++ b/app/lib/features/chat/models/chat_input_state/chat_input_state.dart @@ -18,7 +18,6 @@ enum SendingState { @freezed class ChatInputState with _$ChatInputState { const factory ChatInputState({ - @Default('') String message, @Default(SelectedMessageState.none) SelectedMessageState selectedMessageState, @Default(SendingState.preparing) SendingState sendingState, diff --git a/app/lib/features/chat/models/chat_input_state/chat_input_state.freezed.dart b/app/lib/features/chat/models/chat_input_state/chat_input_state.freezed.dart index 91d2c3f9239b..3e462a1fad6e 100644 --- a/app/lib/features/chat/models/chat_input_state/chat_input_state.freezed.dart +++ b/app/lib/features/chat/models/chat_input_state/chat_input_state.freezed.dart @@ -16,7 +16,6 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$ChatInputState { - String get message => throw _privateConstructorUsedError; SelectedMessageState get selectedMessageState => throw _privateConstructorUsedError; SendingState get sendingState => throw _privateConstructorUsedError; @@ -37,8 +36,7 @@ abstract class $ChatInputStateCopyWith<$Res> { _$ChatInputStateCopyWithImpl<$Res, ChatInputState>; @useResult $Res call( - {String message, - SelectedMessageState selectedMessageState, + {SelectedMessageState selectedMessageState, SendingState sendingState, bool emojiPickerVisible, types.Message? selectedMessage, @@ -59,7 +57,6 @@ class _$ChatInputStateCopyWithImpl<$Res, $Val extends ChatInputState> @pragma('vm:prefer-inline') @override $Res call({ - Object? message = null, Object? selectedMessageState = null, Object? sendingState = null, Object? emojiPickerVisible = null, @@ -68,10 +65,6 @@ class _$ChatInputStateCopyWithImpl<$Res, $Val extends ChatInputState> Object? editBtnVisible = null, }) { return _then(_value.copyWith( - message: null == message - ? _value.message - : message // ignore: cast_nullable_to_non_nullable - as String, selectedMessageState: null == selectedMessageState ? _value.selectedMessageState : selectedMessageState // ignore: cast_nullable_to_non_nullable @@ -109,8 +102,7 @@ abstract class _$$ChatInputStateImplCopyWith<$Res> @override @useResult $Res call( - {String message, - SelectedMessageState selectedMessageState, + {SelectedMessageState selectedMessageState, SendingState sendingState, bool emojiPickerVisible, types.Message? selectedMessage, @@ -129,7 +121,6 @@ class __$$ChatInputStateImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? message = null, Object? selectedMessageState = null, Object? sendingState = null, Object? emojiPickerVisible = null, @@ -138,10 +129,6 @@ class __$$ChatInputStateImplCopyWithImpl<$Res> Object? editBtnVisible = null, }) { return _then(_$ChatInputStateImpl( - message: null == message - ? _value.message - : message // ignore: cast_nullable_to_non_nullable - as String, selectedMessageState: null == selectedMessageState ? _value.selectedMessageState : selectedMessageState // ignore: cast_nullable_to_non_nullable @@ -174,8 +161,7 @@ class __$$ChatInputStateImplCopyWithImpl<$Res> class _$ChatInputStateImpl implements _ChatInputState { const _$ChatInputStateImpl( - {this.message = '', - this.selectedMessageState = SelectedMessageState.none, + {this.selectedMessageState = SelectedMessageState.none, this.sendingState = SendingState.preparing, this.emojiPickerVisible = false, this.selectedMessage = null, @@ -183,9 +169,6 @@ class _$ChatInputStateImpl implements _ChatInputState { this.editBtnVisible = false}) : _mentions = mentions; - @override - @JsonKey() - final String message; @override @JsonKey() final SelectedMessageState selectedMessageState; @@ -213,7 +196,7 @@ class _$ChatInputStateImpl implements _ChatInputState { @override String toString() { - return 'ChatInputState(message: $message, selectedMessageState: $selectedMessageState, sendingState: $sendingState, emojiPickerVisible: $emojiPickerVisible, selectedMessage: $selectedMessage, mentions: $mentions, editBtnVisible: $editBtnVisible)'; + return 'ChatInputState(selectedMessageState: $selectedMessageState, sendingState: $sendingState, emojiPickerVisible: $emojiPickerVisible, selectedMessage: $selectedMessage, mentions: $mentions, editBtnVisible: $editBtnVisible)'; } @override @@ -221,7 +204,6 @@ class _$ChatInputStateImpl implements _ChatInputState { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ChatInputStateImpl && - (identical(other.message, message) || other.message == message) && (identical(other.selectedMessageState, selectedMessageState) || other.selectedMessageState == selectedMessageState) && (identical(other.sendingState, sendingState) || @@ -238,7 +220,6 @@ class _$ChatInputStateImpl implements _ChatInputState { @override int get hashCode => Object.hash( runtimeType, - message, selectedMessageState, sendingState, emojiPickerVisible, @@ -256,16 +237,13 @@ class _$ChatInputStateImpl implements _ChatInputState { abstract class _ChatInputState implements ChatInputState { const factory _ChatInputState( - {final String message, - final SelectedMessageState selectedMessageState, + {final SelectedMessageState selectedMessageState, final SendingState sendingState, final bool emojiPickerVisible, final types.Message? selectedMessage, final Map mentions, final bool editBtnVisible}) = _$ChatInputStateImpl; - @override - String get message; @override SelectedMessageState get selectedMessageState; @override diff --git a/app/lib/features/chat/providers/notifiers/chat_input_notifier.dart b/app/lib/features/chat/providers/notifiers/chat_input_notifier.dart index 38c6adc8923a..54a34ed10dc4 100644 --- a/app/lib/features/chat/providers/notifiers/chat_input_notifier.dart +++ b/app/lib/features/chat/providers/notifiers/chat_input_notifier.dart @@ -1,8 +1,6 @@ -import 'package:acter/features/chat/utils.dart'; import 'package:acter/features/chat/models/chat_input_state/chat_input_state.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:html/parser.dart'; class ChatInputNotifier extends StateNotifier { ChatInputNotifier() : super(const ChatInputState()); @@ -23,45 +21,10 @@ class ChatInputNotifier extends StateNotifier { ); } - void updateMessage(String value) { - state = state.copyWith(message: value); - } - void setEditMessage(Message message) { - final Map mentions = {}; - String messageBodyText = ''; - - if (message is TextMessage) { - // Parse String Data to HTML document - final document = parse(message.text); - - if (document.body != null) { - // Get message data - String msg = message.text.trim(); - - // Get list of 'A Tags' values - final aTagElementList = document.getElementsByTagName('a'); - - for (final aTagElement in aTagElementList) { - final userMentionMessageData = - parseUserMentionMessage(msg, aTagElement); - msg = userMentionMessageData.parsedMessage; - - // Update mentions data - mentions[userMentionMessageData.displayName] = - userMentionMessageData.userName; - } - - // Parse data - final messageDocument = parse(msg); - messageBodyText = messageDocument.body?.text ?? ''; - } - } state = state.copyWith( selectedMessage: message, selectedMessageState: SelectedMessageState.edit, - mentions: mentions, - message: messageBodyText, ); } @@ -83,7 +46,6 @@ class ChatInputNotifier extends StateNotifier { void unsetSelectedMessage() { state = state.copyWith( - message: '', selectedMessage: null, selectedMessageState: SelectedMessageState.none, ); @@ -101,7 +63,6 @@ class ChatInputNotifier extends StateNotifier { void messageSent() { // reset the state; state = state.copyWith( - message: '', sendingState: SendingState.preparing, selectedMessage: null, selectedMessageState: SelectedMessageState.none, diff --git a/app/lib/features/chat/utils.dart b/app/lib/features/chat/utils.dart index 5ea32d042131..d20535c3d817 100644 --- a/app/lib/features/chat/utils.dart +++ b/app/lib/features/chat/utils.dart @@ -1,5 +1,6 @@ import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; +import 'package:acter/features/chat/providers/chat_providers.dart'; import 'package:acter/features/room/actions/join_room.dart'; import 'package:acter/router/utils.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; @@ -8,6 +9,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:html/dom.dart' as html; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart' as types; +import 'package:html/parser.dart'; //Check for mentioned user link final mentionedUserLinkRegex = RegExp( @@ -244,3 +246,32 @@ String prepareMsg(MsgContent? content) { (match) => '${match.group(0)}', ); } + +String parseEditMsg(WidgetRef ref) { + // shouldn't ever happen to be null if state is edit or reply + final message = ref.read(chatInputProvider).selectedMessage!; + + if (message is types.TextMessage) { + // Parse String Data to HTML document + final document = parse(message.text); + + if (document.body != null) { + // Get message data + String msg = message.text.trim(); + + // Get list of 'A Tags' values + final aTagElementList = document.getElementsByTagName('a'); + + for (final aTagElement in aTagElementList) { + final userMentionMessageData = + parseUserMentionMessage(msg, aTagElement); + msg = userMentionMessageData.parsedMessage; + } + + // Parse data + final messageDocument = parse(msg); + return messageDocument.body?.text ?? ''; + } + } + return ''; +} diff --git a/app/lib/features/chat/widgets/custom_input.dart b/app/lib/features/chat/widgets/custom_input.dart index 3f0cb2fae919..75bf77ddc068 100644 --- a/app/lib/features/chat/widgets/custom_input.dart +++ b/app/lib/features/chat/widgets/custom_input.dart @@ -8,6 +8,7 @@ import 'package:acter/common/widgets/frost_effect.dart'; import 'package:acter/features/attachments/actions/select_attachment.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/utils.dart'; import 'package:acter/features/chat/widgets/custom_message_builder.dart'; import 'package:acter/features/chat/widgets/image_message_builder.dart'; import 'package:acter/features/chat/widgets/mention_profile_builder.dart'; @@ -31,12 +32,6 @@ import 'package:skeletonizer/skeletonizer.dart'; final _log = Logger('a3::chat::custom_input'); -final _sendButtonVisible = StateProvider.family( - (ref, roomId) => ref.watch( - chatInputProvider.select((value) => value.message.isNotEmpty), - ), -); - final _allowEdit = StateProvider.family( (ref, roomId) => ref.watch( chatInputProvider @@ -333,7 +328,7 @@ class __ChatInputState extends ConsumerState<_ChatInput> { ), ), ), - if (ref.watch(_sendButtonVisible(roomId))) + if (textController.text.trim().isNotEmpty) renderSendButton(context, roomId), ], ), @@ -692,10 +687,23 @@ class _TextInputWidgetConsumerState extends ConsumerState<_TextInputWidget> { @override void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.controller.text = ref.read( - chatInputProvider.select((value) => value.message), - ); + ref.listenManual( + chatInputProvider.select((state) => state.selectedMessageState), + (prev, next) { + if (next == SelectedMessageState.edit) { + // a new message has been selected to be edited or switched from reply + // to edit, force refresh the inner text controller to reflect that + widget.controller.text = parseEditMsg(ref); + // frame delay to keep focus connected with keyboard. + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.chatFocus.requestFocus(); + }); + } else if (next == SelectedMessageState.replyTo) { + // frame delay to keep focus connected with keyboard.. + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.chatFocus.requestFocus(); + }); + } }); } @@ -740,26 +748,6 @@ class _TextInputWidgetConsumerState extends ConsumerState<_TextInputWidget> { @override Widget build(BuildContext context) { - ref.listen(chatInputProvider, (prev, next) { - if (next.selectedMessageState == SelectedMessageState.edit && - (prev?.selectedMessageState != next.selectedMessageState || - next.message != prev?.message)) { - // a new message has been selected to be edited or switched from reply - // to edit, force refresh the inner text controller to reflect that - widget.controller.text = next.message; - // frame delay to keep focus connected with keyboard. - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.chatFocus.requestFocus(); - }); - } else if (next.selectedMessageState == SelectedMessageState.replyTo && - (next.selectedMessage != prev?.selectedMessage || - prev?.selectedMessageState != next.selectedMessageState)) { - // frame delay to keep focus connected with keyboard.. - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.chatFocus.requestFocus(); - }); - } - }); return CallbackShortcuts( bindings: { const SingleActivator(LogicalKeyboardKey.enter): () { @@ -813,7 +801,6 @@ class _TextInputWidgetConsumerState extends ConsumerState<_TextInputWidget> { focusNode: chatFocus, enabled: ref.watch(_allowEdit(widget.roomId)), onChanged: (val) { - ref.read(chatInputProvider.notifier).updateMessage(val); if (widget.onTyping != null) { widget.onTyping!(val.isNotEmpty); }