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:
Daniel Espino García 2023-02-22 18:03:21 +01:00 committed by GitHub
parent bc3ace278b
commit 98f25046af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 78 additions and 14 deletions

View file

@ -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
View 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];
}

View file

@ -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}

View file

@ -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}