diff --git a/app/containers/MessageComposer/MessageComposer.tsx b/app/containers/MessageComposer/MessageComposer.tsx
index 6805a659507..0e881a59276 100644
--- a/app/containers/MessageComposer/MessageComposer.tsx
+++ b/app/containers/MessageComposer/MessageComposer.tsx
@@ -85,7 +85,7 @@ export const MessageComposer = ({
const closeEmojiKeyboardAndAction = (onClosed?: Function, params?: any) => {
resetKeyboard();
- onClosed && onClosed(params);
+ onClosed?.(params);
};
useImperativeHandle(forwardedRef, () => ({
diff --git a/app/containers/MessageComposer/MessageComposerContainer.tsx b/app/containers/MessageComposer/MessageComposerContainer.tsx
index b24608a61b8..eb4645d384e 100644
--- a/app/containers/MessageComposer/MessageComposerContainer.tsx
+++ b/app/containers/MessageComposer/MessageComposerContainer.tsx
@@ -6,8 +6,10 @@ import { MessageComposer } from './MessageComposer';
import { EmojiKeyboardProvider } from './hooks/useEmojiKeyboard';
import { ComposerAttachments } from './components/Attachments/ComposerAttachments';
+const defaultChildren = ;
+
export const MessageComposerContainer = forwardRef(
- ({ children = }, ref): ReactElement => {
+ ({ children = defaultChildren }, ref): ReactElement => {
return (
diff --git a/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.ts b/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.ts
index 30dadfa9d1f..ee547191239 100644
--- a/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.ts
+++ b/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.ts
@@ -4,6 +4,8 @@ import { AccessibilityInfo } from 'react-native';
import I18n from '../../../../i18n';
import { useIsAutocompleteVisible } from '../../../../views/RoomView/stores/ComposerStore';
+const DELAY_TO_AVOID_KEYBOARD_ANNOUNCEMENT_CONFLICT = 800;
+
export const useAutocompleteA11yAnnounce = (): void => {
const isAutocompleteVisible = useIsAutocompleteVisible();
@@ -12,10 +14,9 @@ export const useAutocompleteA11yAnnounce = (): void => {
return;
}
- // timeout to prevent conflict with default keyboard announcement.
const timeout = setTimeout(() => {
AccessibilityInfo.announceForAccessibility(I18n.t('The_autocomplete_options_are_available_above_the_input_composer'));
- }, 800);
+ }, DELAY_TO_AVOID_KEYBOARD_ANNOUNCEMENT_CONFLICT);
return () => clearTimeout(timeout);
}, [isAutocompleteVisible]);
diff --git a/app/containers/MessageComposer/components/CancelEdit.tsx b/app/containers/MessageComposer/components/CancelEdit.tsx
index f99ce062124..a065bfbe1fe 100644
--- a/app/containers/MessageComposer/components/CancelEdit.tsx
+++ b/app/containers/MessageComposer/components/CancelEdit.tsx
@@ -1,13 +1,13 @@
import { BaseButton } from './Buttons';
import { useEditCancel } from '../../../views/RoomView/stores/ComposerStore';
-import { useMessageAction } from '../../message/stores/MessageActionStore';
+import { useMessageActionKind } from '../../message/stores/MessageActionStore';
import { Gap } from './Gap';
export const CancelEdit = () => {
const editCancel = useEditCancel();
- const action = useMessageAction();
+ const actionKind = useMessageActionKind();
- if (action?.kind !== 'edit') {
+ if (actionKind !== 'edit') {
return null;
}
return (
diff --git a/app/containers/MessageComposer/components/ComposerInput.tsx b/app/containers/MessageComposer/components/ComposerInput.tsx
index 4df191d42e6..7fa78293e9b 100644
--- a/app/containers/MessageComposer/components/ComposerInput.tsx
+++ b/app/containers/MessageComposer/components/ComposerInput.tsx
@@ -1,4 +1,4 @@
-import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
+import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef } from 'react';
import { TextInput, StyleSheet, type TextInputProps, InteractionManager } from 'react-native';
import { useDebouncedCallback } from 'use-debounce';
import { useDispatch } from 'react-redux';
@@ -54,352 +54,356 @@ import { isExternalKeyboardConnected } from '../../../lib/methods/helpers/extern
const defaultSelection: IInputSelection = { start: 0, end: 0 };
-export const ComposerInput = forwardRef(({ inputRef }, ref) => {
- const { colors, theme } = useTheme();
- const rid = useComposerRid();
- const tmid = useComposerTmid();
- const sharing = useComposerSharing();
- const setQuotesAndText = useSetQuotesAndText();
- const room = useComposerRoom();
- const action = useMessageAction();
- const focused = useFocused();
- const { setFocused, setMicOrSend, setAutocompleteParams } = useMessageComposerApi();
- const autocompleteType = useAutocompleteParams()?.type;
- const textRef = useRef('');
- const firstRender = useRef(true);
- const selectionRef = useRef(defaultSelection);
- const dispatch = useDispatch();
- const isMasterDetail = useMasterDetail();
- const altTextSupported = useAltTextSupported();
- let placeholder = tmid ? I18n.t('Add_thread_reply') : '';
- if (room && !tmid) {
- placeholder = I18n.t('Message_roomname', { roomName: (room.t === 'd' ? '@' : '#') + getRoomTitle(room) });
- if (!isTablet && placeholder.length > COMPOSER_INPUT_PLACEHOLDER_MAX_LENGTH) {
- placeholder = `${placeholder.slice(0, COMPOSER_INPUT_PLACEHOLDER_MAX_LENGTH)}...`;
+export const ComposerInput = memo(
+ forwardRef(({ inputRef }, ref) => {
+ const { colors, theme } = useTheme();
+ const rid = useComposerRid();
+ const tmid = useComposerTmid();
+ const sharing = useComposerSharing();
+ const setQuotesAndText = useSetQuotesAndText();
+ const room = useComposerRoom();
+ const action = useMessageAction();
+ const focused = useFocused();
+ const { setFocused, setMicOrSend, setAutocompleteParams } = useMessageComposerApi();
+ const autocompleteType = useAutocompleteParams()?.type;
+ const textRef = useRef('');
+ const firstRender = useRef(true);
+ const selectionRef = useRef(defaultSelection);
+ const dispatch = useDispatch();
+ const isMasterDetail = useMasterDetail();
+ const altTextSupported = useAltTextSupported();
+ let placeholder = tmid ? I18n.t('Add_thread_reply') : '';
+ if (room && !tmid) {
+ placeholder = I18n.t('Message_roomname', { roomName: (room.t === 'd' ? '@' : '#') + getRoomTitle(room) });
+ if (!isTablet && placeholder.length > COMPOSER_INPUT_PLACEHOLDER_MAX_LENGTH) {
+ placeholder = `${placeholder.slice(0, COMPOSER_INPUT_PLACEHOLDER_MAX_LENGTH)}...`;
+ }
}
- }
- const route = useRoute>();
- const usedCannedResponse = route.params?.usedCannedResponse;
- const prevAction = usePrevious(action);
-
- // subscribe to changes on mic state to update draft after a message is sent
- useMicOrSend();
- const { saveMessageDraft } = useAutoSaveDraft(textRef.current);
-
- // workaround to handle issues with iOS back swipe navigation
- const { iOSBackSwipe } = useIOSBackSwipeHandler();
-
- // Draft/Canned Responses
- useEffect(() => {
- const setDraftMessage = async () => {
- const draftMessage = await loadDraftMessage({ rid, tmid });
- if (draftMessage) {
- const parsedDraft = parseJson(draftMessage);
- if (parsedDraft?.msg || parsedDraft?.quotes) {
- setQuotesAndText?.(parsedDraft.msg, parsedDraft.quotes);
- } else {
- setInput(draftMessage);
+ const route = useRoute>();
+ const usedCannedResponse = route.params?.usedCannedResponse;
+ const prevAction = usePrevious(action);
+
+ // subscribe to changes on mic state to update draft after a message is sent
+ useMicOrSend();
+ const { saveMessageDraft } = useAutoSaveDraft(textRef.current);
+
+ // workaround to handle issues with iOS back swipe navigation
+ const { iOSBackSwipe } = useIOSBackSwipeHandler();
+
+ // Draft/Canned Responses
+ useEffect(() => {
+ const setDraftMessage = async () => {
+ const draftMessage = await loadDraftMessage({ rid, tmid });
+ if (draftMessage) {
+ const parsedDraft = parseJson(draftMessage);
+ if (parsedDraft?.msg || parsedDraft?.quotes) {
+ setQuotesAndText?.(parsedDraft.msg, parsedDraft.quotes);
+ } else {
+ setInput(draftMessage);
+ }
}
+ };
+
+ if (action?.kind !== 'edit' && firstRender.current) {
+ firstRender.current = false;
+ setDraftMessage();
}
- };
+ if (sharing) return;
+ if (usedCannedResponse) setInput(usedCannedResponse);
+ }, [action?.kind, rid, tmid, usedCannedResponse]);
+
+ // Edit/quote
+ useEffect(() => {
+ const fetchMessageAndSetInput = async (messageId: string) => {
+ const message = await getMessageById(messageId);
+ if (message) {
+ setInput(message?.msg || (altTextSupported ? '' : message?.attachments?.[0]?.description || ''));
+ }
+ };
- if (action?.kind !== 'edit' && firstRender.current) {
- firstRender.current = false;
- setDraftMessage();
- }
- if (sharing) return;
- if (usedCannedResponse) setInput(usedCannedResponse);
- }, [action?.kind, rid, tmid, usedCannedResponse]);
-
- // Edit/quote
- useEffect(() => {
- const fetchMessageAndSetInput = async (messageId: string) => {
- const message = await getMessageById(messageId);
- if (message) {
- setInput(message?.msg || (altTextSupported ? '' : message?.attachments?.[0]?.description || ''));
+ if (sharing) return;
+
+ if (prevAction?.kind === 'edit' && action?.kind !== 'edit') {
+ setInput('');
+ return;
+ }
+ if (action?.kind === 'edit') {
+ focus();
+ fetchMessageAndSetInput(action.messageId);
+ return;
+ }
+ if (action?.kind === 'quote' && action.messageIds.length) {
+ focus();
+ }
+ }, [action]);
+
+ useFocusEffect(
+ useCallback(() => {
+ const task = InteractionManager.runAfterInteractions(() => {
+ emitter.on('addMarkdown', ({ style }) => {
+ const { start, end } = selectionRef.current;
+ const text = textRef.current;
+ const markdown = MARKDOWN_STYLES[style];
+ const newText = `${text.substr(0, start)}${markdown}${text.substr(start, end - start)}${markdown}${text.substr(end)}`;
+ setInput(newText, {
+ start: start + markdown.length,
+ end: start === end ? start + markdown.length : end + markdown.length
+ });
+ });
+ emitter.on('toolbarMention', () => {
+ if (autocompleteType) {
+ return;
+ }
+ const { start, end } = selectionRef.current;
+ const text = textRef.current;
+ const newText = `${text.substr(0, start)}@${text.substr(start, end - start)}${text.substr(end)}`;
+ setInput(newText, { start: start + 1, end: start === end ? start + 1 : end + 1 });
+ setAutocompleteParams({ text: '', type: '@' });
+ });
+ });
+ return () => {
+ emitter.off('addMarkdown');
+ emitter.off('toolbarMention');
+ task?.cancel();
+ };
+ }, [rid, tmid, autocompleteType])
+ );
+
+ useImperativeHandle(ref, () => ({
+ getTextAndClear: () => {
+ const text = textRef.current;
+ setInput('', undefined, true);
+ return text;
+ },
+ getText: () => textRef.current,
+ getSelection: () => selectionRef.current,
+ setInput,
+ onAutocompleteItemSelected,
+ focus
+ }));
+
+ const setInput: TSetInput = (text, selection, forceUpdateDraftMessage) => {
+ const message = text.trim();
+ textRef.current = message;
+
+ if (forceUpdateDraftMessage) {
+ saveMessageDraft('');
}
- };
- if (sharing) return;
+ inputRef.current?.setNativeProps?.({ text });
- if (prevAction?.kind === 'edit' && action?.kind !== 'edit') {
- setInput('');
- return;
- }
- if (action?.kind === 'edit') {
- focus();
- fetchMessageAndSetInput(action.messageId);
- return;
- }
- if (action?.kind === 'quote' && action.messageIds.length) {
- focus();
- }
- }, [action]);
+ if (selection) {
+ // setSelection won't trigger onSelectionChange, so we need it to be ran after new text is set
+ setTimeout(() => {
+ inputRef.current?.setSelection?.(selection.start, selection.end);
+ selectionRef.current = selection;
+ }, 50);
+ }
+ setMicOrSend(message.length === 0 ? 'mic' : 'send');
+ };
- useFocusEffect(() => {
- const task = InteractionManager.runAfterInteractions(() => {
- emitter.on('addMarkdown', ({ style }) => {
- const { start, end } = selectionRef.current;
- const text = textRef.current;
- const markdown = MARKDOWN_STYLES[style];
- const newText = `${text.substr(0, start)}${markdown}${text.substr(start, end - start)}${markdown}${text.substr(end)}`;
- setInput(newText, {
- start: start + markdown.length,
- end: start === end ? start + markdown.length : end + markdown.length
- });
- });
- emitter.on('toolbarMention', () => {
- if (autocompleteType) {
- return;
+ const focus = () => {
+ setTimeout(() => {
+ if (inputRef.current) {
+ inputRef.current.focus();
}
- const { start, end } = selectionRef.current;
- const text = textRef.current;
- const newText = `${text.substr(0, start)}@${text.substr(start, end - start)}${text.substr(end)}`;
- setInput(newText, { start: start + 1, end: start === end ? start + 1 : end + 1 });
- setAutocompleteParams({ text: '', type: '@' });
- });
- });
- return () => {
- emitter.off('addMarkdown');
- emitter.off('toolbarMention');
- task?.cancel();
+ }, 300);
};
- });
- useImperativeHandle(ref, () => ({
- getTextAndClear: () => {
- const text = textRef.current;
- setInput('', undefined, true);
- return text;
- },
- getText: () => textRef.current,
- getSelection: () => selectionRef.current,
- setInput,
- onAutocompleteItemSelected,
- focus
- }));
-
- const setInput: TSetInput = (text, selection, forceUpdateDraftMessage) => {
- const message = text.trim();
- textRef.current = message;
-
- if (forceUpdateDraftMessage) {
- saveMessageDraft('');
- }
+ const onChangeText: TextInputProps['onChangeText'] = text => {
+ textRef.current = text;
+ debouncedOnChangeText(text);
+ setInput(text);
+ };
- inputRef.current?.setNativeProps?.({ text });
+ const onSelectionChange: TextInputProps['onSelectionChange'] = e => {
+ selectionRef.current = e.nativeEvent.selection;
+ };
- if (selection) {
- // setSelection won't trigger onSelectionChange, so we need it to be ran after new text is set
- setTimeout(() => {
- inputRef.current?.setSelection?.(selection.start, selection.end);
- selectionRef.current = selection;
- }, 50);
- }
- setMicOrSend(message.length === 0 ? 'mic' : 'send');
- };
+ const onFocus: TextInputProps['onFocus'] = () => {
+ setFocused(true);
+ };
- const focus = () => {
- setTimeout(() => {
- if (inputRef.current) {
- inputRef.current.focus();
+ const onTouchStart: TextInputProps['onTouchStart'] = () => {
+ setFocused(true);
+ };
+
+ const onBlur: TextInputProps['onBlur'] = () => {
+ if (!iOSBackSwipe.current && !isExternalKeyboardConnected()) {
+ setFocused(false);
+ stopAutocomplete();
}
- }, 300);
- };
-
- const onChangeText: TextInputProps['onChangeText'] = text => {
- textRef.current = text;
- debouncedOnChangeText(text);
- setInput(text);
- };
-
- const onSelectionChange: TextInputProps['onSelectionChange'] = e => {
- selectionRef.current = e.nativeEvent.selection;
- };
-
- const onFocus: TextInputProps['onFocus'] = () => {
- setFocused(true);
- };
-
- const onTouchStart: TextInputProps['onTouchStart'] = () => {
- setFocused(true);
- };
-
- const onBlur: TextInputProps['onBlur'] = () => {
- if (!iOSBackSwipe.current && !isExternalKeyboardConnected()) {
- setFocused(false);
- stopAutocomplete();
- }
- };
+ };
- const onAutocompleteItemSelected: IAutocompleteItemProps['onPress'] = async item => {
- if (item.type === 'loading') {
- return null;
- }
+ const onAutocompleteItemSelected: IAutocompleteItemProps['onPress'] = async item => {
+ if (item.type === 'loading') {
+ return null;
+ }
- // If it's slash command preview, we need to execute the command
- if (item.type === '/preview') {
- try {
- if (!rid) return;
- const db = database.active;
- const commandsCollection = db.get('slash_commands');
- const commandRecord = await commandsCollection.find(item.text);
- const { appId } = commandRecord;
- const triggerId = generateTriggerId(appId);
- executeCommandPreview(item.text, item.params, rid, item.preview, triggerId, tmid);
- } catch (e) {
- log(e);
+ // If it's slash command preview, we need to execute the command
+ if (item.type === '/preview') {
+ try {
+ if (!rid) return;
+ const db = database.active;
+ const commandsCollection = db.get('slash_commands');
+ const commandRecord = await commandsCollection.find(item.text);
+ const { appId } = commandRecord;
+ const triggerId = generateTriggerId(appId);
+ executeCommandPreview(item.text, item.params, rid, item.preview, triggerId, tmid);
+ } catch (e) {
+ log(e);
+ }
+ requestAnimationFrame(() => {
+ stopAutocomplete();
+ setInput('', { start: 0, end: 0 });
+ });
+ return;
}
- requestAnimationFrame(() => {
- stopAutocomplete();
- setInput('', { start: 0, end: 0 });
- });
- return;
- }
- // If it's canned response, but there's no canned responses, we open the canned responses view
- if (item.type === '!' && item.id === NO_CANNED_RESPONSES) {
- const params = { rid };
- if (isMasterDetail) {
- Navigation.navigate('ModalStackNavigator', { screen: 'CannedResponsesListView', params });
- } else {
- Navigation.navigate('CannedResponsesListView', params);
+ // If it's canned response, but there's no canned responses, we open the canned responses view
+ if (item.type === '!' && item.id === NO_CANNED_RESPONSES) {
+ const params = { rid };
+ if (isMasterDetail) {
+ Navigation.navigate('ModalStackNavigator', { screen: 'CannedResponsesListView', params });
+ } else {
+ Navigation.navigate('CannedResponsesListView', params);
+ }
+ stopAutocomplete();
+ return;
}
- stopAutocomplete();
- return;
- }
- const text = textRef.current;
- const { start, end } = selectionRef.current;
- const cursor = Math.max(start, end);
- const regexp = getMentionRegexp();
- let textBeforeMention = text.substr(0, cursor).replace(regexp, '');
- // Remove the ! after select the canned response
- if (item.type === '!') {
- const lastIndexOfExclamation = text.lastIndexOf('!', cursor);
- textBeforeMention = text.substr(0, lastIndexOfExclamation).replace(regexp, '');
- }
- let mention = '';
- switch (item.type) {
- case '@':
- mention = fetchIsAllOrHere(item) ? item.title : item.subtitle || item.title;
- break;
- case '#':
- mention = item.subtitle ? item.subtitle : '';
- break;
- case ':':
- mention = `${typeof item.emoji === 'string' ? item.emoji : item.emoji.name}:`;
- break;
- case '/':
- mention = item.title;
- break;
- case '!':
- mention = item.subtitle ? item.subtitle : '';
- break;
- default:
- mention = '';
- }
- const newText = `${textBeforeMention}${mention} ${text.slice(cursor)}`;
+ const text = textRef.current;
+ const { start, end } = selectionRef.current;
+ const cursor = Math.max(start, end);
+ const regexp = getMentionRegexp();
+ let textBeforeMention = text.substr(0, cursor).replace(regexp, '');
+ // Remove the ! after select the canned response
+ if (item.type === '!') {
+ const lastIndexOfExclamation = text.lastIndexOf('!', cursor);
+ textBeforeMention = text.substr(0, lastIndexOfExclamation).replace(regexp, '');
+ }
+ let mention = '';
+ switch (item.type) {
+ case '@':
+ mention = fetchIsAllOrHere(item) ? item.title : item.subtitle || item.title;
+ break;
+ case '#':
+ mention = item.subtitle ? item.subtitle : '';
+ break;
+ case ':':
+ mention = `${typeof item.emoji === 'string' ? item.emoji : item.emoji.name}:`;
+ break;
+ case '/':
+ mention = item.title;
+ break;
+ case '!':
+ mention = item.subtitle ? item.subtitle : '';
+ break;
+ default:
+ mention = '';
+ }
+ const newText = `${textBeforeMention}${mention} ${text.slice(cursor)}`;
- const newCursor = textBeforeMention.length + mention.length + 1;
- setInput(newText, { start: newCursor, end: newCursor });
- focus();
- requestAnimationFrame(() => {
- stopAutocomplete();
- });
- };
+ const newCursor = textBeforeMention.length + mention.length + 1;
+ setInput(newText, { start: newCursor, end: newCursor });
+ focus();
+ requestAnimationFrame(() => {
+ stopAutocomplete();
+ });
+ };
- const stopAutocomplete = () => {
- setAutocompleteParams({ text: '', type: null, params: '' });
- };
+ const stopAutocomplete = () => {
+ setAutocompleteParams({ text: '', type: null, params: '' });
+ };
- const debouncedOnChangeText = useDebouncedCallback(async (text: string) => {
- const isTextEmpty = text.length === 0;
- handleTyping(!isTextEmpty);
- if (isTextEmpty || !focused) {
- stopAutocomplete();
- return;
- }
- const { start, end } = selectionRef.current;
- const cursor = Math.max(start, end);
- const whiteSpaceOrBreakLineRegex = /[\s\n]+/;
- const txt =
- cursor < text.length ? text.substr(0, cursor).split(whiteSpaceOrBreakLineRegex) : text.split(whiteSpaceOrBreakLineRegex);
- const lastWord = txt[txt.length - 1];
- const autocompleteText = lastWord.substring(1);
-
- if (!lastWord) {
- stopAutocomplete();
- return;
- }
- if (!sharing && text.match(/^\//)) {
- const commandParameter = text.match(/^\/([a-z0-9._-]+) (.+)/im);
- if (commandParameter) {
- const db = database.active;
- const [, command, params] = commandParameter;
- const commandsCollection = db.get('slash_commands');
- try {
- const commandRecord = await commandsCollection.find(command);
- if (commandRecord.providesPreview) {
- setAutocompleteParams({ params, text: command, type: '/preview' });
+ const debouncedOnChangeText = useDebouncedCallback(async (text: string) => {
+ const isTextEmpty = text.length === 0;
+ handleTyping(!isTextEmpty);
+ if (isTextEmpty || !focused) {
+ stopAutocomplete();
+ return;
+ }
+ const { start, end } = selectionRef.current;
+ const cursor = Math.max(start, end);
+ const whiteSpaceOrBreakLineRegex = /[\s\n]+/;
+ const txt =
+ cursor < text.length ? text.substr(0, cursor).split(whiteSpaceOrBreakLineRegex) : text.split(whiteSpaceOrBreakLineRegex);
+ const lastWord = txt[txt.length - 1];
+ const autocompleteText = lastWord.substring(1);
+
+ if (!lastWord) {
+ stopAutocomplete();
+ return;
+ }
+ if (!sharing && text.match(/^\//)) {
+ const commandParameter = text.match(/^\/([a-z0-9._-]+) (.+)/im);
+ if (commandParameter) {
+ const db = database.active;
+ const [, command, params] = commandParameter;
+ const commandsCollection = db.get('slash_commands');
+ try {
+ const commandRecord = await commandsCollection.find(command);
+ if (commandRecord.providesPreview) {
+ setAutocompleteParams({ params, text: command, type: '/preview' });
+ }
+ return;
+ } catch (e) {
+ // do nothing
}
- return;
- } catch (e) {
- // do nothing
}
+ setAutocompleteParams({ text: autocompleteText, type: '/' });
+ return;
+ }
+ if (lastWord.match(/^#/)) {
+ setAutocompleteParams({ text: autocompleteText, type: '#' });
+ return;
+ }
+ if (lastWord.match(/^@/)) {
+ setAutocompleteParams({ text: autocompleteText, type: '@' });
+ return;
+ }
+ if (lastWord.match(/^:/)) {
+ setAutocompleteParams({ text: autocompleteText, type: ':' });
+ return;
+ }
+ if (lastWord.match(/^!/) && room?.t === 'l') {
+ setAutocompleteParams({ text: autocompleteText, type: '!' });
+ return;
}
- setAutocompleteParams({ text: autocompleteText, type: '/' });
- return;
- }
- if (lastWord.match(/^#/)) {
- setAutocompleteParams({ text: autocompleteText, type: '#' });
- return;
- }
- if (lastWord.match(/^@/)) {
- setAutocompleteParams({ text: autocompleteText, type: '@' });
- return;
- }
- if (lastWord.match(/^:/)) {
- setAutocompleteParams({ text: autocompleteText, type: ':' });
- return;
- }
- if (lastWord.match(/^!/) && room?.t === 'l') {
- setAutocompleteParams({ text: autocompleteText, type: '!' });
- return;
- }
- stopAutocomplete();
- }, textInputDebounceTime);
-
- const handleTyping = (isTyping: boolean) => {
- if (sharing || !rid) return;
- dispatch(userTyping(rid, isTyping, tmid ? { tmid } : {}));
- };
-
- return (
- {
- inputRef.current = component;
- }}
- blurOnSubmit={false}
- onChangeText={onChangeText}
- onTouchStart={onTouchStart}
- onSelectionChange={onSelectionChange}
- onFocus={onFocus}
- onBlur={onBlur}
- underlineColorAndroid='transparent'
- defaultValue=''
- multiline
- {...(autocompleteType ? { autoComplete: 'off', autoCorrect: false, autoCapitalize: 'none' } : {})}
- keyboardAppearance={theme === 'light' ? 'light' : 'dark'}
- // eslint-disable-next-line no-nested-ternary
- testID={`message-composer-input${tmid ? '-thread' : sharing ? '-share' : ''}`}
- />
- );
-});
+ stopAutocomplete();
+ }, textInputDebounceTime);
+
+ const handleTyping = (isTyping: boolean) => {
+ if (sharing || !rid) return;
+ dispatch(userTyping(rid, isTyping, tmid ? { tmid } : {}));
+ };
+
+ return (
+ {
+ inputRef.current = component;
+ }}
+ blurOnSubmit={false}
+ onChangeText={onChangeText}
+ onTouchStart={onTouchStart}
+ onSelectionChange={onSelectionChange}
+ onFocus={onFocus}
+ onBlur={onBlur}
+ underlineColorAndroid='transparent'
+ defaultValue=''
+ multiline
+ {...(autocompleteType ? { autoComplete: 'off', autoCorrect: false, autoCapitalize: 'none' } : {})}
+ keyboardAppearance={theme === 'light' ? 'light' : 'dark'}
+ // eslint-disable-next-line no-nested-ternary
+ testID={`message-composer-input${tmid ? '-thread' : sharing ? '-share' : ''}`}
+ />
+ );
+ })
+);
const styles = StyleSheet.create({
textInput: {
diff --git a/app/containers/MessageComposer/hooks/useChooseMedia.test.tsx b/app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
index a827fc792b0..bffc8304d2a 100644
--- a/app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
+++ b/app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
@@ -20,7 +20,7 @@ jest.mock('../../../views/RoomView/stores/ComposerStore', () => ({
}));
jest.mock('../../message/stores/MessageActionStore', () => ({
- useMessageAction: jest.fn(),
+ useMessageActionKind: jest.fn(),
useQuotedMessageIds: jest.fn(() => [])
}));
@@ -53,7 +53,7 @@ const mockUseAppSelector = require('../../../lib/hooks/useAppSelector').useAppSe
const mockUseMessageComposerApi = require('../context').useMessageComposerApi as jest.Mock;
const mockUseSetQuotesAndText = require('../../../views/RoomView/stores/ComposerStore').useSetQuotesAndText as jest.Mock;
const mockUseGetText = require('../../../views/RoomView/stores/ComposerStore').useGetText as jest.Mock;
-const mockUseMessageAction = require('../../message/stores/MessageActionStore').useMessageAction as jest.Mock;
+const mockUseMessageActionKind = require('../../message/stores/MessageActionStore').useMessageActionKind as jest.Mock;
const mockUseQuotedMessageIds = require('../../message/stores/MessageActionStore').useQuotedMessageIds as jest.Mock;
const mockUseAltTextSupported = require('../../../lib/hooks/useAltTextSupported').useAltTextSupported as jest.Mock;
const mockGetSubscriptionByRoomId = require('../../../lib/database/services/Subscription').getSubscriptionByRoomId as jest.Mock;
@@ -77,7 +77,7 @@ describe('useChooseMedia', () => {
mockUseMessageComposerApi.mockReturnValue({ addAttachments });
mockUseSetQuotesAndText.mockReturnValue(jest.fn());
mockUseGetText.mockReturnValue(jest.fn(() => 'draft'));
- mockUseMessageAction.mockReturnValue(null);
+ mockUseMessageActionKind.mockReturnValue(null);
mockGetSubscriptionByRoomId.mockResolvedValue({ rid: 'room-id', t: 'c' });
mockGetThreadById.mockResolvedValue({ id: 'thread-id' });
});
@@ -136,7 +136,7 @@ describe('useChooseMedia', () => {
it('forwards quoted message ids to ShareView as selectedMessages', async () => {
mockUseAltTextSupported.mockReturnValue(false);
- mockUseMessageAction.mockReturnValue({ kind: 'quote', messageIds: ['msg-1', 'msg-2'] });
+ mockUseMessageActionKind.mockReturnValue('quote');
mockUseQuotedMessageIds.mockReturnValue(['msg-1', 'msg-2']);
mockGetDocumentAsync.mockResolvedValue({
canceled: false,
@@ -155,7 +155,7 @@ describe('useChooseMedia', () => {
it('does not quote the message when the action is edit', async () => {
mockUseAltTextSupported.mockReturnValue(false);
- mockUseMessageAction.mockReturnValue({ kind: 'edit', messageId: 'msg-1' });
+ mockUseMessageActionKind.mockReturnValue('edit');
mockUseQuotedMessageIds.mockReturnValue([]);
mockGetDocumentAsync.mockResolvedValue({
canceled: false,
diff --git a/app/containers/MessageComposer/hooks/useChooseMedia.ts b/app/containers/MessageComposer/hooks/useChooseMedia.ts
index 0f723e86421..d953ad83916 100644
--- a/app/containers/MessageComposer/hooks/useChooseMedia.ts
+++ b/app/containers/MessageComposer/hooks/useChooseMedia.ts
@@ -10,7 +10,7 @@ import { getThreadById } from '../../../lib/database/services/Thread';
import Navigation from '../../../lib/navigation/appNavigation';
import { useAppSelector } from '../../../lib/hooks/useAppSelector';
import { useGetText, useSetQuotesAndText } from '../../../views/RoomView/stores/ComposerStore';
-import { useMessageAction, useQuotedMessageIds } from '../../message/stores/MessageActionStore';
+import { useMessageActionKind, useQuotedMessageIds } from '../../message/stores/MessageActionStore';
import { type IShareAttachment } from '../../../definitions';
import ImagePicker, { type ImageOrVideo } from '../../../lib/methods/helpers/ImagePicker/ImagePicker';
import { useMessageComposerApi } from '../context';
@@ -32,7 +32,7 @@ export const useChooseMedia = ({
const { addAttachments } = useMessageComposerApi();
const setQuotesAndText = useSetQuotesAndText();
const getText = useGetText();
- const action = useMessageAction();
+ const actionKind = useMessageActionKind();
const quotedMessageIds = useQuotedMessageIds();
const altTextSupported = useAltTextSupported();
const allowList = FileUpload_MediaTypeWhiteList as string;
@@ -117,7 +117,7 @@ export const useChooseMedia = ({
room,
thread: thread || tmid,
attachments,
- action: action?.kind ?? null,
+ action: actionKind,
finishShareView,
startShareView
});