Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e0edb3d
wip: android image pasting
divyanshu-patil Dec 14, 2025
3c27dd2
fix: keep spacing while setting input and trim while sending
divyanshu-patil Dec 14, 2025
88b87f3
fix: message modal opening and user fast typing cursor jumps
divyanshu-patil Dec 14, 2025
3a66e6a
feat: image pasting with ShareView
divyanshu-patil Dec 14, 2025
ff68f0b
fix: autoComplete typo in ComposerInput
divyanshu-patil Dec 14, 2025
855ee28
fix: null check on room, forceUpdateDraftMessage param to ensure draf…
divyanshu-patil Dec 14, 2025
2bac128
refactor: IShareAttachment types, image paste handler name
divyanshu-patil Dec 14, 2025
b9cfb96
chore: upate react-native-typerich version to 0.1.10
divyanshu-patil Dec 14, 2025
9719e2d
cleanup: logs
divyanshu-patil Dec 15, 2025
bb91f3c
fix: ios app crash on opening app
divyanshu-patil Dec 15, 2025
bf0781b
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 15, 2025
c437c36
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 17, 2025
70a0b99
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 18, 2025
c21e7dc
fix: context menu text paste, draft saving, programmatic multiline se…
divyanshu-patil Dec 19, 2025
8b69178
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 22, 2025
25e792c
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 23, 2025
9735282
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 23, 2025
57b6564
chore: added api changes
divyanshu-patil Dec 23, 2025
4c7e354
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 23, 2025
cbd3295
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 24, 2025
6aa6785
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 30, 2025
7d48f67
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Dec 31, 2025
d0fd144
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Jan 7, 2026
552dd14
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Jan 9, 2026
a286878
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Jan 18, 2026
5084495
Merge branch 'develop' into feat/android_image_pasting
divyanshu-patil Jan 22, 2026
7147f9c
feat: iOS image pasting
divyanshu-patil Jan 22, 2026
5e12559
refactor: removed editable prop as it should be default
divyanshu-patil Jan 23, 2026
61ccbe2
fix(ios): autocorrect not leaving space in middle of message
divyanshu-patil Jan 25, 2026
b26835f
Merge branch 'develop' into feat/image_pasting
divyanshu-patil Jan 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/containers/MessageComposer/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const MessageComposer = ({
}): ReactElement | null => {
'use memo';

const composerInputRef = useRef(null);
const composerInputRef = useRef<any>(null);
const composerInputComponentRef = useRef<IComposerInput>({
getTextAndClear: () => '',
getText: () => '',
Expand Down Expand Up @@ -170,7 +170,10 @@ export const MessageComposer = ({
};

const accessibilityFocusOnInput = () => {
const node = findNodeHandle(composerInputRef.current);
const input = composerInputRef.current;

const hostRef = input?.getNativeRef?.() ?? input;
const node = findNodeHandle(hostRef);
if (node) {
AccessibilityInfo.setAccessibilityFocus(node);
}
Expand Down
147 changes: 113 additions & 34 deletions app/containers/MessageComposer/components/ComposerInput.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle } from 'react';
import { TextInput, StyleSheet, type TextInputProps, InteractionManager } from 'react-native';
import { StyleSheet, type TextInputProps, InteractionManager, Alert } from 'react-native';
import { useDebouncedCallback } from 'use-debounce';
import { useDispatch } from 'react-redux';
import { type RouteProp, useFocusEffect, useRoute } from '@react-navigation/native';
import { type OnChangeSelectionEvent, type onPasteImageEventData, TypeRichTextInput } from 'react-native-typerich';

import { canUploadFile } from '../../../lib/methods/helpers';
import { textInputDebounceTime } from '../../../lib/constants/debounceConfig';
import I18n from '../../../i18n';
import {
Expand All @@ -15,7 +17,7 @@ import {
} from '../interfaces';
import { useAutocompleteParams, useFocused, useMessageComposerApi, useMicOrSend } from '../context';
import { fetchIsAllOrHere, getMentionRegexp } from '../helpers';
import { useAutoSaveDraft } from '../hooks';
import { useAutoSaveDraft, useCanUploadFile } from '../hooks';
import sharedStyles from '../../../views/Styles';
import { useTheme } from '../../../theme';
import { userTyping } from '../../../actions/room';
Expand All @@ -42,6 +44,9 @@ import { usePrevious } from '../../../lib/hooks/usePrevious';
import { type ChatsStackParamList } from '../../../stacks/types';
import { loadDraftMessage } from '../../../lib/methods/draftMessage';
import useIOSBackSwipeHandler from '../hooks/useIOSBackSwipeHandler';
import { getSubscriptionByRoomId } from '../../../lib/database/services/Subscription';
import { getThreadById } from '../../../lib/database/services/Thread';
import { type IShareAttachment } from '../../../definitions';

const defaultSelection: IInputSelection = { start: 0, end: 0 };

Expand All @@ -68,6 +73,11 @@ export const ComposerInput = memo(
const usedCannedResponse = route.params?.usedCannedResponse;
const prevAction = usePrevious(action);

const permissionToUpload = useCanUploadFile(rid);
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = useAppSelector(state => state.settings);
const allowList = FileUpload_MediaTypeWhiteList as string;
const maxFileSize = FileUpload_MaxFileSize as number;

// subscribe to changes on mic state to update draft after a message is sent
useMicOrSend();
const { saveMessageDraft } = useAutoSaveDraft(textRef.current);
Expand Down Expand Up @@ -143,6 +153,8 @@ export const ComposerInput = memo(
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 });
// todo mention command here

setAutocompleteParams({ text: '', type: '@' });
});
});
Expand Down Expand Up @@ -175,7 +187,7 @@ export const ComposerInput = memo(
saveMessageDraft('');
}

inputRef.current?.setNativeProps?.({ text });
inputRef.current?.setText(text);

if (selection) {
// setSelection won't trigger onSelectionChange, so we need it to be ran after new text is set
Expand All @@ -201,25 +213,27 @@ export const ComposerInput = memo(
setInput(text);
};

const onSelectionChange: TextInputProps['onSelectionChange'] = e => {
selectionRef.current = e.nativeEvent.selection;
};

const onFocus: TextInputProps['onFocus'] = () => {
setFocused(true);
const onChangeSelection = (e: OnChangeSelectionEvent) => {
const { start, end } = e;
const selection = { start, end };
selectionRef.current = selection;
};

const onTouchStart: TextInputProps['onTouchStart'] = () => {
const handleFocus = () => {
setFocused(true);
};

const onBlur: TextInputProps['onBlur'] = () => {
const handleBlur = () => {
if (!iOSBackSwipe.current) {
setFocused(false);
stopAutocomplete();
}
};

const onTouchStart: TextInputProps['onTouchStart'] = () => {
setFocused(true);
};

const onAutocompleteItemSelected: IAutocompleteItemProps['onPress'] = async item => {
if (item.type === 'loading') {
return null;
Expand Down Expand Up @@ -364,28 +378,93 @@ export const ComposerInput = memo(
dispatch(userTyping(rid, isTyping, tmid ? { tmid } : {}));
};

const startShareView = () => ({
selectedMessages,
text: ''
});

const finishShareView = (text = '', quotes = []) => setQuotesAndText?.(text, quotes);

const handleOnImagePaste = async (e: onPasteImageEventData) => {
console.log(e);
if (e.error?.message) {
handleError(e.error.message);
console.log('error detected');
return;
}
if (!rid) return;

const room = await getSubscriptionByRoomId(rid);

if (!room) {
handleError('Room not found');
return;
}

let thread;
if (tmid) {
thread = await getThreadById(tmid);
}

const file = {
filename: e.fileName,
size: e.fileSize,
mime: e.type,
path: e.uri
} as IShareAttachment;

const canUploadResult = canUploadFile({
file,
allowList,
maxFileSize,
permissionToUploadFile: permissionToUpload
});
if (canUploadResult.success) {
Navigation.navigate('ShareView', {
room,
thread: thread || tmid,
attachments: [file],
action,
finishShareView,
startShareView
});
} else {
console.log('can upload error');

handleError(canUploadResult.error);
}
};

const handleError = (error?: string) => {
Alert.alert(I18n.t('Error_uploading'), error && I18n.isTranslated(error) ? I18n.t(error) : error);
};

return (
<TextInput
style={[styles.textInput, { color: colors.fontDefault }]}
placeholder={placeholder}
placeholderTextColor={colors.fontAnnotation}
ref={component => {
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' : ''}`}
/>
<>
<TypeRichTextInput
style={[styles.textInput]}
color={colors.fontDefault}
placeholder={placeholder}
placeholderTextColor={colors.fontAnnotation}
ref={component => {
inputRef.current = component;
}}
// blurOnSubmit={false} // not needed
onChangeText={onChangeText}
onTouchStart={onTouchStart}
onChangeSelection={onChangeSelection}
onFocus={handleFocus} // typerich onFocus / onBlur events doesn't pass any arguments to callbacks
onBlur={handleBlur}
// underlineColorAndroid='transparent' // by default behaiviour
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' : ''}`}
onPasteImageData={handleOnImagePaste}
/>
</>
);
})
);
Expand All @@ -397,9 +476,9 @@ const styles = StyleSheet.create({
maxHeight: MAX_HEIGHT,
paddingTop: 12,
paddingBottom: 12,
fontSize: 16,
textAlignVertical: 'center',
...sharedStyles.textRegular,
lineHeight: 22
lineHeight: 22,
fontSize: 16
}
});
34 changes: 31 additions & 3 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ PODS:
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- MobileCrypto (0.2.0):
- MobileCrypto (0.2.1):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -2162,6 +2162,30 @@ PODS:
- React-logger (= 0.79.4)
- React-perflogger (= 0.79.4)
- React-utils (= 0.79.4)
- ReactNativeTypeRich (2.2.4):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNBootSplash (6.3.8):
- DoubleConversion
- glog
Expand Down Expand Up @@ -2736,6 +2760,7 @@ DEPENDENCIES:
- ReactAppDependencyProvider (from `build/generated/ios`)
- ReactCodegen (from `build/generated/ios`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- ReactNativeTypeRich (from `../node_modules/react-native-typerich`)
- RNBootSplash (from `../node_modules/react-native-bootsplash`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
Expand Down Expand Up @@ -2994,6 +3019,8 @@ EXTERNAL SOURCES:
:path: build/generated/ios
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
ReactNativeTypeRich:
:path: "../node_modules/react-native-typerich"
RNBootSplash:
:path: "../node_modules/react-native-bootsplash"
RNCAsyncStorage:
Expand Down Expand Up @@ -3082,7 +3109,7 @@ SPEC CHECKSUMS:
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
MobileCrypto: 60a1e43e26a9d6851ae2aa7294b8041c9e9220b7
MobileCrypto: a424494b2f45bec9dbe60e3f6d16a40aedefe7b7
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
Expand Down Expand Up @@ -3159,6 +3186,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: bf62814e0fde923f73fc64b7e82d76c63c284da9
ReactCodegen: 2f22969ab54e1aace69c9b5d3085e0a3b405a9a6
ReactCommon: 177fca841e97b2c0e288e86097b8be04c6e7ae36
ReactNativeTypeRich: 4702fc21cb380789c8c66d5ee652aab0e6719588
RNBootSplash: 1280eeb18d887de0a45bb4923d4fc56f25c8b99c
RNCAsyncStorage: edb872909c88d8541c0bfade3f86cd7784a7c6b3
RNCClipboard: 4fd4b093bd9d0be5ad62ea73884eda7745ad23d0
Expand All @@ -3185,7 +3213,7 @@ SPEC CHECKSUMS:
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
WatermelonDB: 4c846c8cb94eef3cba90fa034d15310163226703
Yoga: dfabf1234ccd5ac41d1b1d43179f024366ae9831
Yoga: 2a3a4c38a8441b6359d5e5914d35db7b2b67aebd
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5

PODFILE CHECKSUM: 199f6fbbe6fb415c822cca992e6152000ac55b3e
Expand Down
8 changes: 4 additions & 4 deletions ios/RocketChatRN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1825,7 +1825,7 @@
inputFileListPaths = (
);
inputPaths = (
"$TARGET_BUILD_DIR/$INFOPLIST_PATH",
$TARGET_BUILD_DIR/$INFOPLIST_PATH,
);
name = "Upload source maps to Bugsnag";
outputFileListPaths = (
Expand All @@ -1845,7 +1845,7 @@
inputFileListPaths = (
);
inputPaths = (
"$TARGET_BUILD_DIR/$INFOPLIST_PATH",
$TARGET_BUILD_DIR/$INFOPLIST_PATH,
);
name = "Upload source maps to Bugsnag";
outputFileListPaths = (
Expand Down Expand Up @@ -2593,7 +2593,7 @@
"$(inherited)",
"$(SRCROOT)/../node_modules/rn-extensions-share/ios/**",
"$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**",
"$PODS_CONFIGURATION_BUILD_DIR/Firebase",
$PODS_CONFIGURATION_BUILD_DIR/Firebase,
);
INFOPLIST_FILE = ShareRocketChatRN/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
Expand Down Expand Up @@ -2669,7 +2669,7 @@
"$(inherited)",
"$(SRCROOT)/../node_modules/rn-extensions-share/ios/**",
"$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**",
"$PODS_CONFIGURATION_BUILD_DIR/Firebase",
$PODS_CONFIGURATION_BUILD_DIR/Firebase,
);
INFOPLIST_FILE = ShareRocketChatRN/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
"react-native-skeleton-placeholder": "5.2.4",
"react-native-slowlog": "1.0.2",
"react-native-svg": "^15.12.1",
"react-native-typerich": "^2.2.4",
"react-native-url-polyfill": "2.0.0",
"react-native-webview": "^13.15.0",
"react-redux": "8.0.5",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12854,6 +12854,11 @@ react-native-svg@^15.12.1:
css-tree "^1.1.3"
warn-once "0.1.1"

react-native-typerich@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/react-native-typerich/-/react-native-typerich-2.2.4.tgz#da51f5f3990a287ffad348c202f432957303363c"
integrity sha512-0TQbkpwDvbR0mQk4d/l5CcVXphvFo0VnKt4XhO5wYZ+6SlsVmBrTVuMZ0ASbD2xOfNI5pHgcRbW6YgWHbq+uBg==

react-native-url-polyfill@2.0.0, react-native-url-polyfill@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz#db714520a2985cff1d50ab2e66279b9f91ffd589"
Expand Down