diff --git a/app/containers/MessageComposer/MessageComposer.tsx b/app/containers/MessageComposer/MessageComposer.tsx index 19e1e4101..e43c9aa97 100644 --- a/app/containers/MessageComposer/MessageComposer.tsx +++ b/app/containers/MessageComposer/MessageComposer.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useRef, useImperativeHandle, useCallback } from 'react'; -import { View, StyleSheet, NativeModules } from 'react-native'; +import { View, StyleSheet, NativeModules, NativeSyntheticEvent, TextInputProps } from 'react-native'; import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard'; import { useBackHandler } from '@react-native-community/hooks'; import { Q } from '@nozbe/watermelondb'; @@ -16,8 +16,8 @@ import { useShowEmojiKeyboard, useShowEmojiSearchbar } from './context'; -import { IComposerInput, ITrackingView } from './interfaces'; -import { isIOS } from '../../lib/methods/helpers'; +import { IComposerInput, ITrackingView, ITrackingViewHeightEvent } from './interfaces'; +import { isAndroid, isIOS } from '../../lib/methods/helpers'; import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import { useTheme } from '../../theme'; import { EventTypes } from '../EmojiPicker/interfaces'; @@ -29,7 +29,6 @@ import { Services } from '../../lib/services'; import log from '../../lib/methods/helpers/log'; import { prepareQuoteMessage } from './helpers'; import { RecordAudio } from './components/RecordAudio'; -import { useKeyboardListener } from './hooks'; const styles = StyleSheet.create({ container: { @@ -59,15 +58,14 @@ export const MessageComposer = ({ setInput: () => {}, onAutocompleteItemSelected: () => {} }); - const trackingViewRef = useRef({ resetTracking: () => {}, getNativeProps: () => ({ trackingViewHeight: 0 }) }); + const trackingViewRef = useRef({ resetTracking: () => {} }); const { colors, theme } = useTheme(); const { rid, tmid, action, selectedMessages, sharing, editRequest, onSendMessage } = useRoomContext(); const showEmojiKeyboard = useShowEmojiKeyboard(); const showEmojiSearchbar = useShowEmojiSearchbar(); const alsoSendThreadToChannel = useAlsoSendThreadToChannel(); - const { openSearchEmojiKeyboard, closeEmojiKeyboard, closeSearchEmojiKeyboard } = useMessageComposerApi(); + const { openSearchEmojiKeyboard, closeEmojiKeyboard, closeSearchEmojiKeyboard, setHeight } = useMessageComposerApi(); const recordingAudio = useRecordingAudio(); - useKeyboardListener(trackingViewRef); useFocusEffect( useCallback(() => { @@ -190,6 +188,14 @@ export const MessageComposer = ({ } }; + const onHeightChange = (event: NativeSyntheticEvent) => { + setHeight(event.nativeEvent.keyboardHeight, event.nativeEvent.height); + }; + + const handleLayout: TextInputProps['onLayout'] = e => { + setHeight(0, e.nativeEvent.layout.height); + }; + const backgroundColor = action === 'edit' ? colors.statusBackgroundWarning2 : colors.surfaceLight; const renderContent = () => { @@ -214,22 +220,25 @@ export const MessageComposer = ({ }; return ( - - (trackingViewRef.current = ref)} - renderContent={renderContent} - kbInputRef={composerInputRef} - kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null} - kbInitialProps={{ theme }} - onKeyboardResigned={onKeyboardResigned} - onItemSelected={onKeyboardItemSelected} - trackInteractive - requiresSameParentToManageScrollView - addBottomView - bottomViewColor={backgroundColor} - iOSScrollBehavior={NativeModules.KeyboardTrackingViewTempManager?.KeyboardTrackingScrollBehaviorFixedOffset} - /> - composerInputComponentRef.current.onAutocompleteItemSelected(item)} /> - + + + (trackingViewRef.current = ref)} + renderContent={renderContent} + kbInputRef={composerInputRef} + kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null} + kbInitialProps={{ theme }} + onKeyboardResigned={onKeyboardResigned} + onItemSelected={onKeyboardItemSelected} + trackInteractive + requiresSameParentToManageScrollView + addBottomView + bottomViewColor={backgroundColor} + iOSScrollBehavior={NativeModules.KeyboardTrackingViewTempManager?.KeyboardTrackingScrollBehaviorFixedOffset} + onHeightChange={onHeightChange} + /> + composerInputComponentRef.current.onAutocompleteItemSelected(item)} /> + + ); }; diff --git a/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx b/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx index e3a450b74..51367dbca 100644 --- a/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx +++ b/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx @@ -1,5 +1,6 @@ import React, { ReactElement } from 'react'; import { View, FlatList } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useAutocompleteParams, useKeyboardHeight, useTrackingViewHeight } from '../../context'; import { AutocompleteItem } from './AutocompleteItem'; @@ -13,6 +14,7 @@ export const Autocomplete = ({ onPress }: { onPress: IAutocompleteItemProps['onP const { rid } = useRoomContext(); const trackingViewHeight = useTrackingViewHeight(); const keyboardHeight = useKeyboardHeight(); + const { bottom } = useSafeAreaInsets(); const { text, type, params } = useAutocompleteParams(); const items = useAutocomplete({ rid, @@ -26,13 +28,15 @@ export const Autocomplete = ({ onPress }: { onPress: IAutocompleteItemProps['onP return null; } + const viewBottom = getBottom(trackingViewHeight, keyboardHeight, bottom); + if (type !== '/preview') { return ( @@ -49,9 +53,7 @@ export const Autocomplete = ({ onPress }: { onPress: IAutocompleteItemProps['onP if (type === '/preview') { return ( - + trackingViewHeight + keyboardHeight + 50; +export const getBottom = (trackingViewHeight: number, keyboardHeight: number, bottomSafeArea: number): number => + trackingViewHeight + keyboardHeight + (keyboardHeight > 0 ? 0 : bottomSafeArea) - 4; export const useStyle = () => { const { colors } = useTheme(); diff --git a/app/containers/MessageComposer/components/ComposerInput.tsx b/app/containers/MessageComposer/components/ComposerInput.tsx index 6a46c762f..4c7fc8508 100644 --- a/app/containers/MessageComposer/components/ComposerInput.tsx +++ b/app/containers/MessageComposer/components/ComposerInput.tsx @@ -32,7 +32,7 @@ export const ComposerInput = memo( const { colors, theme } = useTheme(); const { rid, tmid, sharing, action, selectedMessages } = useRoomContext(); const focused = useFocused(); - const { setFocused, setTrackingViewHeight, setMicOrSend, setAutocompleteParams } = useMessageComposerApi(); + const { setFocused, setMicOrSend, setAutocompleteParams } = useMessageComposerApi(); const autocompleteType = useAutocompleteParams()?.type; const textRef = React.useRef(''); const selectionRef = React.useRef(defaultSelection); @@ -180,10 +180,6 @@ export const ComposerInput = memo( stopAutocomplete(); }; - const handleLayout: TextInputProps['onLayout'] = e => { - setTrackingViewHeight(e.nativeEvent.layout.height); - }; - const onAutocompleteItemSelected: IAutocompleteItemProps['onPress'] = async item => { if (item.type === 'loading') { return null; @@ -330,7 +326,6 @@ export const ComposerInput = memo( return ( void; - setTrackingViewHeight: (height: number) => void; + setHeight: (keyboardHeight: number, trackingViewHeight: number) => void; openEmojiKeyboard(): void; closeEmojiKeyboard(): void; openSearchEmojiKeyboard(): void; @@ -75,8 +74,7 @@ type Actions = | { type: 'updateEmojiKeyboard'; showEmojiKeyboard: boolean } | { type: 'updateEmojiSearchbar'; showEmojiSearchbar: boolean } | { type: 'updateFocused'; focused: boolean } - | { type: 'updateTrackingViewHeight'; trackingViewHeight: number } - | { type: 'updateKeyboardHeight'; keyboardHeight: number } + | { type: 'updateHeight'; trackingViewHeight: number; keyboardHeight: number } | { type: 'openEmojiKeyboard' } | { type: 'closeEmojiKeyboard' } | { type: 'openSearchEmojiKeyboard' } @@ -96,10 +94,8 @@ const reducer = (state: State, action: Actions): State => { case 'updateFocused': animateNextTransition(); return { ...state, focused: action.focused }; - case 'updateTrackingViewHeight': - return { ...state, trackingViewHeight: action.trackingViewHeight }; - case 'updateKeyboardHeight': - return { ...state, keyboardHeight: action.keyboardHeight }; + case 'updateHeight': + return { ...state, trackingViewHeight: action.trackingViewHeight, keyboardHeight: action.keyboardHeight }; case 'openEmojiKeyboard': return { ...state, showEmojiKeyboard: true, showEmojiSearchbar: false }; case 'openSearchEmojiKeyboard': @@ -127,12 +123,10 @@ export const MessageComposerProvider = ({ children }: { children: ReactElement } const [state, dispatch] = useReducer(reducer, { keyboardHeight: 0, autocompleteParams: { text: '', type: null } } as State); const api = useMemo(() => { - const setFocused = (focused: boolean) => dispatch({ type: 'updateFocused', focused }); + const setFocused: TMessageComposerContextApi['setFocused'] = focused => dispatch({ type: 'updateFocused', focused }); - const setKeyboardHeight = (keyboardHeight: number) => dispatch({ type: 'updateKeyboardHeight', keyboardHeight }); - - const setTrackingViewHeight = (trackingViewHeight: number) => - dispatch({ type: 'updateTrackingViewHeight', trackingViewHeight }); + const setHeight: TMessageComposerContextApi['setHeight'] = (keyboardHeight, trackingViewHeight) => + dispatch({ type: 'updateHeight', keyboardHeight, trackingViewHeight }); const openEmojiKeyboard = () => dispatch({ type: 'openEmojiKeyboard' }); @@ -142,21 +136,23 @@ export const MessageComposerProvider = ({ children }: { children: ReactElement } const closeSearchEmojiKeyboard = () => dispatch({ type: 'closeSearchEmojiKeyboard' }); - const setMicOrSend = (micOrSend: TMicOrSend) => dispatch({ type: 'setMicOrSend', micOrSend }); + const setMicOrSend: TMessageComposerContextApi['setMicOrSend'] = micOrSend => dispatch({ type: 'setMicOrSend', micOrSend }); - const setMarkdownToolbar = (showMarkdownToolbar: boolean) => dispatch({ type: 'setMarkdownToolbar', showMarkdownToolbar }); + const setMarkdownToolbar: TMessageComposerContextApi['setMarkdownToolbar'] = showMarkdownToolbar => + dispatch({ type: 'setMarkdownToolbar', showMarkdownToolbar }); - const setAlsoSendThreadToChannel = (alsoSendThreadToChannel: boolean) => + const setAlsoSendThreadToChannel: TMessageComposerContextApi['setAlsoSendThreadToChannel'] = alsoSendThreadToChannel => dispatch({ type: 'setAlsoSendThreadToChannel', alsoSendThreadToChannel }); - const setRecordingAudio = (recordingAudio: boolean) => dispatch({ type: 'setRecordingAudio', recordingAudio }); + const setRecordingAudio: TMessageComposerContextApi['setRecordingAudio'] = recordingAudio => + dispatch({ type: 'setRecordingAudio', recordingAudio }); - const setAutocompleteParams = (params: IAutocompleteBase) => dispatch({ type: 'setAutocompleteParams', params }); + const setAutocompleteParams: TMessageComposerContextApi['setAutocompleteParams'] = params => + dispatch({ type: 'setAutocompleteParams', params }); return { setFocused, - setKeyboardHeight, - setTrackingViewHeight, + setHeight, openEmojiKeyboard, closeEmojiKeyboard, openSearchEmojiKeyboard, diff --git a/app/containers/MessageComposer/hooks/index.ts b/app/containers/MessageComposer/hooks/index.ts index f84b052af..cebac0dc5 100644 --- a/app/containers/MessageComposer/hooks/index.ts +++ b/app/containers/MessageComposer/hooks/index.ts @@ -1,6 +1,5 @@ export * from './useAutocomplete'; export * from './useCanUploadFile'; export * from './useChooseMedia'; -export * from './useKeyboardListener'; export * from './useSubscription'; export * from './useMessage'; diff --git a/app/containers/MessageComposer/hooks/useKeyboardListener.ts b/app/containers/MessageComposer/hooks/useKeyboardListener.ts deleted file mode 100644 index eb7e3b0ca..000000000 --- a/app/containers/MessageComposer/hooks/useKeyboardListener.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { MutableRefObject, useEffect } from 'react'; -import { Keyboard } from 'react-native'; - -import { useMessageComposerApi } from '../context'; -import { ITrackingView } from '../interfaces'; - -export const useKeyboardListener = (ref: MutableRefObject) => { - const { setKeyboardHeight } = useMessageComposerApi(); - useEffect(() => { - const showListener = Keyboard.addListener('keyboardWillShow', async () => { - if (ref?.current) { - const props = await ref.current.getNativeProps(); - setKeyboardHeight(props.keyboardHeight); - } - }); - - const hideListener = Keyboard.addListener('keyboardWillHide', () => { - setKeyboardHeight(0); - }); - - return () => { - showListener.remove(); - hideListener.remove(); - }; - }, [ref, setKeyboardHeight]); -}; diff --git a/app/containers/MessageComposer/interfaces.ts b/app/containers/MessageComposer/interfaces.ts index ac68e05fe..bbdf2206c 100644 --- a/app/containers/MessageComposer/interfaces.ts +++ b/app/containers/MessageComposer/interfaces.ts @@ -37,7 +37,11 @@ export type TMarkdownStyle = 'bold' | 'italic' | 'strike' | 'code' | 'code-block export interface ITrackingView { resetTracking: () => void; - getNativeProps: () => any; +} + +export interface ITrackingViewHeightEvent { + height: number; + keyboardHeight: number; } export type TAutocompleteType = '@' | '#' | '!' | ':' | '/' | '/preview' | 'loading' | null; diff --git a/package.json b/package.json index f30b85127..429cd528c 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "react-native-scrollable-tab-view": "ptomasroos/react-native-scrollable-tab-view", "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.2", "react-native-skeleton-placeholder": "^5.2.3", - "react-native-slowlog": "^1.0.2", + "react-native-slowlog": "1.0.2", "react-native-svg": "13.8.0", "react-native-ui-lib": "RocketChat/react-native-ui-lib#ef50151b8d9c1627ef527c620a1472868f9f4df8", "react-native-url-polyfill": "2.0.0", diff --git a/patches/react-native-ui-lib+7.0.0.patch b/patches/react-native-ui-lib+7.0.0.patch index ce1161626..447e96473 100644 --- a/patches/react-native-ui-lib+7.0.0.patch +++ b/patches/react-native-ui-lib+7.0.0.patch @@ -1,8 +1,56 @@ +diff --git a/node_modules/react-native-ui-lib/lib/components/Keyboard/KeyboardInput/KeyboardAccessoryView.js b/node_modules/react-native-ui-lib/lib/components/Keyboard/KeyboardInput/KeyboardAccessoryView.js +index cfe1d35..a191c82 100644 +--- a/node_modules/react-native-ui-lib/lib/components/Keyboard/KeyboardInput/KeyboardAccessoryView.js ++++ b/node_modules/react-native-ui-lib/lib/components/Keyboard/KeyboardInput/KeyboardAccessoryView.js +@@ -123,7 +123,12 @@ class KeyboardAccessoryView extends Component { + * Whether or not to handle SafeArea + * default: true + */ +- useSafeArea: PropTypes.bool ++ useSafeArea: PropTypes.bool, ++ ++ onHeightChange: PropTypes.shape({ ++ height: PropTypes.number, ++ keyboardHeight: PropTypes.number ++ }) + }; + + static iosScrollBehaviors = IOS_SCROLL_BEHAVIORS; +@@ -271,6 +276,7 @@ class KeyboardAccessoryView extends Component { + addBottomView={addBottomView} + bottomViewColor={this.props.bottomViewColor} + allowHitsOutsideBounds={allowHitsOutsideBounds} ++ onHeightChange={this.props.onHeightChange} + > + {renderContent && renderContent()} +