Fix double tilde by waiting for text values to propagate to the native side (#7132)
Co-authored-by: Daniel Espino <danielespino@MacBook-Pro-de-Daniel.local>
This commit is contained in:
parent
bc3ace278b
commit
98f25046af
4 changed files with 78 additions and 14 deletions
|
|
@ -17,6 +17,7 @@ import {Events, Screens} from '@constants';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {useInputPropagation} from '@hooks/input';
|
||||
import {t} from '@i18n';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
import {extractFileInfo} from '@utils/file';
|
||||
|
|
@ -120,6 +121,7 @@ export default function PostInput({
|
|||
const style = getStyleSheet(theme);
|
||||
const serverUrl = useServerUrl();
|
||||
const managedConfig = useManagedConfig<ManagedConfig>();
|
||||
const [propagateValue, shouldProcessEvent] = useInputPropagation();
|
||||
|
||||
const lastTypingEventSent = useRef(0);
|
||||
|
||||
|
|
@ -180,6 +182,9 @@ export default function PostInput({
|
|||
}, [updateCursorPosition, cursorPosition]);
|
||||
|
||||
const handleTextChange = useCallback((newValue: string) => {
|
||||
if (!shouldProcessEvent(newValue)) {
|
||||
return;
|
||||
}
|
||||
updateValue(newValue);
|
||||
lastNativeValue.current = newValue;
|
||||
|
||||
|
|
@ -224,10 +229,16 @@ export default function PostInput({
|
|||
case 'enter':
|
||||
sendMessage();
|
||||
break;
|
||||
case 'shift-enter':
|
||||
updateValue((v) => v.substring(0, cursorPosition) + '\n' + v.substring(cursorPosition));
|
||||
case 'shift-enter': {
|
||||
let newValue: string;
|
||||
updateValue((v) => {
|
||||
newValue = v.substring(0, cursorPosition) + '\n' + v.substring(cursorPosition);
|
||||
return newValue;
|
||||
});
|
||||
updateCursorPosition((pos) => pos + 1);
|
||||
propagateValue(newValue!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [sendMessage, updateValue, cursorPosition, isTablet]);
|
||||
|
|
@ -266,6 +277,7 @@ export default function PostInput({
|
|||
const draft = value ? `${value} ${text} ` : `${text} `;
|
||||
updateValue(draft);
|
||||
updateCursorPosition(draft.length);
|
||||
propagateValue(draft);
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
});
|
||||
|
|
@ -274,10 +286,7 @@ export default function PostInput({
|
|||
|
||||
useEffect(() => {
|
||||
if (value !== lastNativeValue.current) {
|
||||
// May change when we implement Fabric
|
||||
inputRef.current?.setNativeProps({
|
||||
text: value,
|
||||
});
|
||||
propagateValue(value);
|
||||
lastNativeValue.current = value;
|
||||
}
|
||||
}, [value]);
|
||||
|
|
@ -310,6 +319,7 @@ export default function PostInput({
|
|||
testID={testID}
|
||||
underlineColorAndroid='transparent'
|
||||
textContentType='none'
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
25
app/hooks/input.ts
Normal file
25
app/hooks/input.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {useCallback, useRef} from 'react';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
export function useInputPropagation(): [(v: string) => void, (v: string) => boolean] {
|
||||
const waitForValue = useRef<string>();
|
||||
const waitToPropagate = useCallback((value: string) => {
|
||||
waitForValue.current = value;
|
||||
}, []);
|
||||
const shouldProcessEvent = useCallback((newValue: string) => {
|
||||
if (Platform.OS === 'android') {
|
||||
return true;
|
||||
}
|
||||
if (waitForValue.current === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (newValue === waitForValue.current) {
|
||||
waitForValue.current = undefined;
|
||||
}
|
||||
return false;
|
||||
}, []);
|
||||
|
||||
return [waitToPropagate, shouldProcessEvent];
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import {
|
|||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {useInputPropagation} from '@app/hooks/input';
|
||||
import Autocomplete from '@components/autocomplete';
|
||||
import ErrorText from '@components/error_text';
|
||||
import FloatingTextInput from '@components/floating_text_input_label';
|
||||
|
|
@ -126,6 +127,8 @@ export default function ChannelInfoForm({
|
|||
const dimensions = useWindowDimensions();
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const [propagateValue, shouldProcessEvent] = useInputPropagation();
|
||||
|
||||
const keyboardHeight = useKeyboardHeight();
|
||||
const [keyboardVisible, setKeyBoardVisible] = useState(false);
|
||||
const [scrollPosition, setScrollPosition] = useState(0);
|
||||
|
|
@ -193,6 +196,18 @@ export default function ChannelInfoForm({
|
|||
}
|
||||
}, [keyboardHeight]);
|
||||
|
||||
const onHeaderAutocompleteChange = useCallback((value: string) => {
|
||||
onHeaderChange(value);
|
||||
propagateValue(value);
|
||||
}, [onHeaderChange]);
|
||||
|
||||
const onHeaderInputChange = useCallback((value: string) => {
|
||||
if (!shouldProcessEvent(value)) {
|
||||
return;
|
||||
}
|
||||
onHeaderChange(value);
|
||||
}, [onHeaderChange]);
|
||||
|
||||
const onLayoutError = useCallback((e: LayoutChangeEvent) => {
|
||||
setErrorHeight(e.nativeEvent.layout.height);
|
||||
}, []);
|
||||
|
|
@ -371,7 +386,7 @@ export default function ChannelInfoForm({
|
|||
enablesReturnKeyAutomatically={true}
|
||||
label={labelHeader}
|
||||
placeholder={placeholderHeader}
|
||||
onChangeText={onHeaderChange}
|
||||
onChangeText={onHeaderInputChange}
|
||||
multiline={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
returnKeyType='next'
|
||||
|
|
@ -397,7 +412,7 @@ export default function ChannelInfoForm({
|
|||
</KeyboardAwareScrollView>
|
||||
<Autocomplete
|
||||
position={animatedAutocompletePosition}
|
||||
updateValue={onHeaderChange}
|
||||
updateValue={onHeaderAutocompleteChange}
|
||||
cursorPosition={header.length}
|
||||
value={header}
|
||||
nestedScrollEnabled={true}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
|||
import {useAutocompleteDefaultAnimatedValues} from '@hooks/autocomplete';
|
||||
import {useIsTablet, useKeyboardHeight, useModalPosition} from '@hooks/device';
|
||||
import useDidUpdate from '@hooks/did_update';
|
||||
import {useInputPropagation} from '@hooks/input';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import PostError from '@screens/edit_post/post_error';
|
||||
import {buildNavigationButton, dismissModal, setButtons} from '@screens/navigation';
|
||||
|
|
@ -61,6 +62,7 @@ const EditPost = ({componentId, maxPostSize, post, closeButtonId, hasFilesAttach
|
|||
const [errorExtra, setErrorExtra] = useState<string | undefined>();
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [containerHeight, setContainerHeight] = useState(0);
|
||||
const [propagateValue, shouldProcessEvent] = useInputPropagation();
|
||||
|
||||
const mainView = useRef<View>(null);
|
||||
const modalPosition = useModalPosition(mainView);
|
||||
|
|
@ -123,10 +125,8 @@ const EditPost = ({componentId, maxPostSize, post, closeButtonId, hasFilesAttach
|
|||
});
|
||||
}, [componentId, intl, theme]);
|
||||
|
||||
const onChangeText = useCallback((message: string) => {
|
||||
setPostMessage(message);
|
||||
const onChangeTextCommon = useCallback((message: string) => {
|
||||
const tooLong = message.trim().length > maxPostSize;
|
||||
|
||||
if (tooLong) {
|
||||
const line = intl.formatMessage({id: 'mobile.message_length.message_split_left', defaultMessage: 'Message exceeds the character limit'});
|
||||
const extra = `${message.trim().length} / ${maxPostSize}`;
|
||||
|
|
@ -134,7 +134,21 @@ const EditPost = ({componentId, maxPostSize, post, closeButtonId, hasFilesAttach
|
|||
setErrorExtra(extra);
|
||||
}
|
||||
toggleSaveButton(post.message !== message);
|
||||
}, [intl, maxPostSize, toggleSaveButton]);
|
||||
}, [intl, maxPostSize, post.message, toggleSaveButton]);
|
||||
|
||||
const onAutocompleteChangeText = useCallback((message: string) => {
|
||||
setPostMessage(message);
|
||||
propagateValue(message);
|
||||
onChangeTextCommon(message);
|
||||
}, [onChangeTextCommon]);
|
||||
|
||||
const onInputChangeText = useCallback((message: string) => {
|
||||
if (!shouldProcessEvent(message)) {
|
||||
return;
|
||||
}
|
||||
setPostMessage(message);
|
||||
onChangeTextCommon(message);
|
||||
}, [onChangeTextCommon]);
|
||||
|
||||
const handleUIUpdates = useCallback((res: {error?: unknown}) => {
|
||||
if (res.error) {
|
||||
|
|
@ -233,7 +247,7 @@ const EditPost = ({componentId, maxPostSize, post, closeButtonId, hasFilesAttach
|
|||
<EditPostInput
|
||||
hasError={Boolean(errorLine)}
|
||||
message={postMessage}
|
||||
onChangeText={onChangeText}
|
||||
onChangeText={onInputChangeText}
|
||||
onTextSelectionChange={onTextSelectionChange}
|
||||
ref={postInputRef}
|
||||
/>
|
||||
|
|
@ -244,7 +258,7 @@ const EditPost = ({componentId, maxPostSize, post, closeButtonId, hasFilesAttach
|
|||
hasFilesAttached={hasFilesAttached}
|
||||
nestedScrollEnabled={true}
|
||||
rootId={post.rootId}
|
||||
updateValue={onChangeText}
|
||||
updateValue={onAutocompleteChangeText}
|
||||
value={postMessage}
|
||||
cursorPosition={cursorPosition}
|
||||
position={animatedAutocompletePosition}
|
||||
|
|
|
|||
Loading…
Reference in a new issue