diff --git a/app/containers/MessageComposer/MessageComposer.tsx b/app/containers/MessageComposer/MessageComposer.tsx index e43c9aa97..56b25d8f4 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, NativeSyntheticEvent, TextInputProps } from 'react-native'; +import { View, StyleSheet, NativeModules } 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, ITrackingViewHeightEvent } from './interfaces'; -import { isAndroid, isIOS } from '../../lib/methods/helpers'; +import { IComposerInput, ITrackingView } from './interfaces'; +import { isIOS } from '../../lib/methods/helpers'; import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import { useTheme } from '../../theme'; import { EventTypes } from '../EmojiPicker/interfaces'; @@ -29,6 +29,7 @@ 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: { @@ -58,14 +59,16 @@ export const MessageComposer = ({ setInput: () => {}, onAutocompleteItemSelected: () => {} }); - const trackingViewRef = useRef({ resetTracking: () => {} }); + const trackingViewRef = useRef({ resetTracking: () => {}, getNativeProps: () => ({ trackingViewHeight: 0 }) }); 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, setHeight } = useMessageComposerApi(); + const { openSearchEmojiKeyboard, closeEmojiKeyboard, closeSearchEmojiKeyboard, setTrackingViewHeight } = + useMessageComposerApi(); const recordingAudio = useRecordingAudio(); + useKeyboardListener(trackingViewRef); useFocusEffect( useCallback(() => { @@ -188,12 +191,8 @@ 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 onHeightChanged = (height: number) => { + setTrackingViewHeight(height); }; const backgroundColor = action === 'edit' ? colors.statusBackgroundWarning2 : colors.surfaceLight; @@ -220,25 +219,23 @@ 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} - onHeightChange={onHeightChange} - /> - 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} + onHeightChanged={onHeightChanged} + /> + composerInputComponentRef.current.onAutocompleteItemSelected(item)} /> + ); }; diff --git a/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx b/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx index 51367dbca..0b9488dd7 100644 --- a/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx +++ b/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx @@ -8,7 +8,7 @@ import { useAutocomplete } from '../../hooks'; import { IAutocompleteItemProps } from '../../interfaces'; import { AutocompletePreview } from './AutocompletePreview'; import { useRoomContext } from '../../../../views/RoomView/context'; -import { useStyle, getBottom } from './styles'; +import { useStyle } from './styles'; export const Autocomplete = ({ onPress }: { onPress: IAutocompleteItemProps['onPress'] }): ReactElement | null => { const { rid } = useRoomContext(); @@ -23,13 +23,12 @@ export const Autocomplete = ({ onPress }: { onPress: IAutocompleteItemProps['onP commandParams: params }); const [styles, colors] = useStyle(); + const viewBottom = trackingViewHeight + keyboardHeight + (keyboardHeight > 0 ? 0 : bottom) - 4; if (items.length === 0 || !type) { return null; } - const viewBottom = getBottom(trackingViewHeight, keyboardHeight, bottom); - if (type !== '/preview') { return ( - trackingViewHeight + keyboardHeight + (keyboardHeight > 0 ? 0 : bottomSafeArea) - 4; export const useStyle = () => { const { colors } = useTheme(); diff --git a/app/containers/MessageComposer/context.tsx b/app/containers/MessageComposer/context.tsx index 2fc670bd4..83985a276 100644 --- a/app/containers/MessageComposer/context.tsx +++ b/app/containers/MessageComposer/context.tsx @@ -5,7 +5,8 @@ import { IAutocompleteBase, TMicOrSend } from './interfaces'; import { animateNextTransition } from '../../lib/methods/helpers'; type TMessageComposerContextApi = { - setHeight: (keyboardHeight: number, trackingViewHeight: number) => void; + setKeyboardHeight: (height: number) => void; + setTrackingViewHeight: (height: number) => void; openEmojiKeyboard(): void; closeEmojiKeyboard(): void; openSearchEmojiKeyboard(): void; @@ -74,7 +75,8 @@ type Actions = | { type: 'updateEmojiKeyboard'; showEmojiKeyboard: boolean } | { type: 'updateEmojiSearchbar'; showEmojiSearchbar: boolean } | { type: 'updateFocused'; focused: boolean } - | { type: 'updateHeight'; trackingViewHeight: number; keyboardHeight: number } + | { type: 'updateTrackingViewHeight'; trackingViewHeight: number } + | { type: 'updateKeyboardHeight'; keyboardHeight: number } | { type: 'openEmojiKeyboard' } | { type: 'closeEmojiKeyboard' } | { type: 'openSearchEmojiKeyboard' } @@ -94,8 +96,10 @@ const reducer = (state: State, action: Actions): State => { case 'updateFocused': animateNextTransition(); return { ...state, focused: action.focused }; - case 'updateHeight': - return { ...state, trackingViewHeight: action.trackingViewHeight, keyboardHeight: action.keyboardHeight }; + case 'updateTrackingViewHeight': + return { ...state, trackingViewHeight: action.trackingViewHeight }; + case 'updateKeyboardHeight': + return { ...state, keyboardHeight: action.keyboardHeight }; case 'openEmojiKeyboard': return { ...state, showEmojiKeyboard: true, showEmojiSearchbar: false }; case 'openSearchEmojiKeyboard': @@ -120,13 +124,19 @@ const reducer = (state: State, action: Actions): State => { }; export const MessageComposerProvider = ({ children }: { children: ReactElement }): ReactElement => { - const [state, dispatch] = useReducer(reducer, { keyboardHeight: 0, autocompleteParams: { text: '', type: null } } as State); + const [state, dispatch] = useReducer(reducer, { + keyboardHeight: 0, + trackingViewHeight: 0, + autocompleteParams: { text: '', type: null } + } as State); const api = useMemo(() => { - const setFocused: TMessageComposerContextApi['setFocused'] = focused => dispatch({ type: 'updateFocused', focused }); + const setFocused = (focused: boolean) => dispatch({ type: 'updateFocused', focused }); - const setHeight: TMessageComposerContextApi['setHeight'] = (keyboardHeight, trackingViewHeight) => - dispatch({ type: 'updateHeight', keyboardHeight, trackingViewHeight }); + const setKeyboardHeight = (keyboardHeight: number) => dispatch({ type: 'updateKeyboardHeight', keyboardHeight }); + + const setTrackingViewHeight = (trackingViewHeight: number) => + dispatch({ type: 'updateTrackingViewHeight', trackingViewHeight }); const openEmojiKeyboard = () => dispatch({ type: 'openEmojiKeyboard' }); @@ -136,23 +146,21 @@ export const MessageComposerProvider = ({ children }: { children: ReactElement } const closeSearchEmojiKeyboard = () => dispatch({ type: 'closeSearchEmojiKeyboard' }); - const setMicOrSend: TMessageComposerContextApi['setMicOrSend'] = micOrSend => dispatch({ type: 'setMicOrSend', micOrSend }); + const setMicOrSend = (micOrSend: TMicOrSend) => dispatch({ type: 'setMicOrSend', micOrSend }); - const setMarkdownToolbar: TMessageComposerContextApi['setMarkdownToolbar'] = showMarkdownToolbar => - dispatch({ type: 'setMarkdownToolbar', showMarkdownToolbar }); + const setMarkdownToolbar = (showMarkdownToolbar: boolean) => dispatch({ type: 'setMarkdownToolbar', showMarkdownToolbar }); - const setAlsoSendThreadToChannel: TMessageComposerContextApi['setAlsoSendThreadToChannel'] = alsoSendThreadToChannel => + const setAlsoSendThreadToChannel = (alsoSendThreadToChannel: boolean) => dispatch({ type: 'setAlsoSendThreadToChannel', alsoSendThreadToChannel }); - const setRecordingAudio: TMessageComposerContextApi['setRecordingAudio'] = recordingAudio => - dispatch({ type: 'setRecordingAudio', recordingAudio }); + const setRecordingAudio = (recordingAudio: boolean) => dispatch({ type: 'setRecordingAudio', recordingAudio }); - const setAutocompleteParams: TMessageComposerContextApi['setAutocompleteParams'] = params => - dispatch({ type: 'setAutocompleteParams', params }); + const setAutocompleteParams = (params: IAutocompleteBase) => dispatch({ type: 'setAutocompleteParams', params }); return { setFocused, - setHeight, + setKeyboardHeight, + setTrackingViewHeight, openEmojiKeyboard, closeEmojiKeyboard, openSearchEmojiKeyboard, diff --git a/app/containers/MessageComposer/hooks/index.ts b/app/containers/MessageComposer/hooks/index.ts index cebac0dc5..f84b052af 100644 --- a/app/containers/MessageComposer/hooks/index.ts +++ b/app/containers/MessageComposer/hooks/index.ts @@ -1,5 +1,6 @@ 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 new file mode 100644 index 000000000..eb7e3b0ca --- /dev/null +++ b/app/containers/MessageComposer/hooks/useKeyboardListener.ts @@ -0,0 +1,26 @@ +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 bbdf2206c..ac68e05fe 100644 --- a/app/containers/MessageComposer/interfaces.ts +++ b/app/containers/MessageComposer/interfaces.ts @@ -37,11 +37,7 @@ export type TMarkdownStyle = 'bold' | 'italic' | 'strike' | 'code' | 'code-block export interface ITrackingView { resetTracking: () => void; -} - -export interface ITrackingViewHeightEvent { - height: number; - keyboardHeight: number; + getNativeProps: () => any; } export type TAutocompleteType = '@' | '#' | '!' | ':' | '/' | '/preview' | 'loading' | null; diff --git a/package.json b/package.json index 429cd528c..f30b85127 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 447e96473..ce1161626 100644 --- a/patches/react-native-ui-lib+7.0.0.patch +++ b/patches/react-native-ui-lib+7.0.0.patch @@ -1,56 +1,8 @@ -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()} -