Skip to content

Commit

Permalink
remove message string state from chat input provider and use controll…
Browse files Browse the repository at this point in the history
…er text to determine state
  • Loading branch information
gtalha07 committed Aug 15, 2024
1 parent 9b0d54c commit 8192c82
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -174,18 +161,14 @@ 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,
final Map<String, String> mentions = const {},
this.editBtnVisible = false})
: _mentions = mentions;

@override
@JsonKey()
final String message;
@override
@JsonKey()
final SelectedMessageState selectedMessageState;
Expand Down Expand Up @@ -213,15 +196,14 @@ 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
bool operator ==(Object other) {
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) ||
Expand All @@ -238,7 +220,6 @@ class _$ChatInputStateImpl implements _ChatInputState {
@override
int get hashCode => Object.hash(
runtimeType,
message,
selectedMessageState,
sendingState,
emojiPickerVisible,
Expand All @@ -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<String, String> mentions,
final bool editBtnVisible}) = _$ChatInputStateImpl;

@override
String get message;
@override
SelectedMessageState get selectedMessageState;
@override
Expand Down
39 changes: 0 additions & 39 deletions app/lib/features/chat/providers/notifiers/chat_input_notifier.dart
Original file line number Diff line number Diff line change
@@ -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<ChatInputState> {
ChatInputNotifier() : super(const ChatInputState());
Expand All @@ -23,45 +21,10 @@ class ChatInputNotifier extends StateNotifier<ChatInputState> {
);
}

void updateMessage(String value) {
state = state.copyWith(message: value);
}

void setEditMessage(Message message) {
final Map<String, String> 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,
);
}

Expand All @@ -83,7 +46,6 @@ class ChatInputNotifier extends StateNotifier<ChatInputState> {

void unsetSelectedMessage() {
state = state.copyWith(
message: '',
selectedMessage: null,
selectedMessageState: SelectedMessageState.none,
);
Expand All @@ -101,7 +63,6 @@ class ChatInputNotifier extends StateNotifier<ChatInputState> {
void messageSent() {
// reset the state;
state = state.copyWith(
message: '',
sendingState: SendingState.preparing,
selectedMessage: null,
selectedMessageState: SelectedMessageState.none,
Expand Down
31 changes: 31 additions & 0 deletions app/lib/features/chat/utils.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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(
Expand Down Expand Up @@ -244,3 +246,32 @@ String prepareMsg(MsgContent? content) {
(match) => '<a href="${match.group(0)}">${match.group(0)}</a>',
);
}

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 '';
}
51 changes: 19 additions & 32 deletions app/lib/features/chat/widgets/custom_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -31,12 +32,6 @@ import 'package:skeletonizer/skeletonizer.dart';

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

final _sendButtonVisible = StateProvider.family<bool, String>(
(ref, roomId) => ref.watch(
chatInputProvider.select((value) => value.message.isNotEmpty),
),
);

final _allowEdit = StateProvider.family<bool, String>(
(ref, roomId) => ref.watch(
chatInputProvider
Expand Down Expand Up @@ -333,7 +328,7 @@ class __ChatInputState extends ConsumerState<_ChatInput> {
),
),
),
if (ref.watch(_sendButtonVisible(roomId)))
if (textController.text.trim().isNotEmpty)
renderSendButton(context, roomId),
],
),
Expand Down Expand Up @@ -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();
});
}
});
}

Expand Down Expand Up @@ -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: <ShortcutActivator, VoidCallback>{
const SingleActivator(LogicalKeyboardKey.enter): () {
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 8192c82

Please sign in to comment.