// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {useCallback, useEffect, useRef, useState} from 'react'; import {useIntl} from 'react-intl'; import {Alert, Keyboard, type LayoutChangeEvent, Platform, SafeAreaView, View} from 'react-native'; import {deletePost, editPost} from '@actions/remote/post'; import Autocomplete from '@components/autocomplete'; import Loading from '@components/loading'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; import {useAutocompleteDefaultAnimatedValues} from '@hooks/autocomplete'; import {useKeyboardOverlap} 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'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import EditPostInput, {type EditPostInputRef} from './edit_post_input'; import type PostModel from '@typings/database/models/servers/post'; import type {AvailableScreens} from '@typings/screens/navigation'; const AUTOCOMPLETE_SEPARATION = 8; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { return { body: { flex: 1, backgroundColor: changeOpacity(theme.centerChannelColor, 0.03), }, container: { flex: 1, }, loader: { flex: 1, alignItems: 'center', justifyContent: 'center', }, }; }); const RIGHT_BUTTON = buildNavigationButton('edit-post', 'edit_post.save.button'); type EditPostProps = { componentId: AvailableScreens; closeButtonId: string; post: PostModel; maxPostSize: number; hasFilesAttached: boolean; canDelete: boolean; } const EditPost = ({componentId, maxPostSize, post, closeButtonId, hasFilesAttached, canDelete}: EditPostProps) => { const editingMessage = post.messageSource || post.message; const [postMessage, setPostMessage] = useState(editingMessage); const [cursorPosition, setCursorPosition] = useState(editingMessage.length); const [errorLine, setErrorLine] = useState(); const [errorExtra, setErrorExtra] = useState(); const [isUpdating, setIsUpdating] = useState(false); const [containerHeight, setContainerHeight] = useState(0); const [propagateValue, shouldProcessEvent] = useInputPropagation(); const mainView = useRef(null); const postInputRef = useRef(null); const theme = useTheme(); const intl = useIntl(); const serverUrl = useServerUrl(); const styles = getStyleSheet(theme); useEffect(() => { toggleSaveButton(false); }, []); useEffect(() => { const t = setTimeout(() => { postInputRef.current?.focus(); }, 320); return () => { clearTimeout(t); }; }, []); useDidUpdate(() => { // Workaround to avoid iOS emdash autocorrect in Code Blocks if (Platform.OS === 'ios') { onTextSelectionChange(); } }, [postMessage]); const onClose = useCallback(() => { Keyboard.dismiss(); dismissModal({componentId}); }, []); const onTextSelectionChange = useCallback((curPos: number = cursorPosition) => { setCursorPosition(curPos); }, [cursorPosition, postMessage]); const toggleSaveButton = useCallback((enabled = true) => { setButtons(componentId, { rightButtons: [{ ...RIGHT_BUTTON, color: theme.sidebarHeaderTextColor, text: intl.formatMessage({id: 'edit_post.save', defaultMessage: 'Save'}), enabled, }], }); }, [componentId, intl, theme]); 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}`; setErrorLine(line); setErrorExtra(extra); } toggleSaveButton(editingMessage !== message); }, [intl, maxPostSize, editingMessage, 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) { setIsUpdating(false); const errorMessage = intl.formatMessage({id: 'mobile.edit_post.error', defaultMessage: 'There was a problem editing this message. Please try again.'}); setErrorLine(errorMessage); postInputRef?.current?.focus(); } else { setIsUpdating(false); onClose(); } }, []); const handleDeletePost = useCallback(async () => { Alert.alert( intl.formatMessage({id: 'mobile.edit_post.delete_title', defaultMessage: 'Confirm Post Delete'}), intl.formatMessage({ id: 'mobile.edit_post.delete_question', defaultMessage: 'Are you sure you want to delete this Post?', }), [{ text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}), style: 'cancel', onPress: () => { setIsUpdating(false); toggleSaveButton(); setPostMessage(editingMessage); }, }, { text: intl.formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}), style: 'destructive', onPress: async () => { const res = await deletePost(serverUrl, post); handleUIUpdates(res); }, }], ); }, [serverUrl, editingMessage]); const onSavePostMessage = useCallback(async () => { setIsUpdating(true); setErrorLine(undefined); setErrorExtra(undefined); toggleSaveButton(false); if (!postMessage && canDelete && !hasFilesAttached) { handleDeletePost(); return; } const res = await editPost(serverUrl, post.id, postMessage); handleUIUpdates(res); }, [toggleSaveButton, serverUrl, post.id, postMessage, onClose]); const onLayout = useCallback((e: LayoutChangeEvent) => { setContainerHeight(e.nativeEvent.layout.height); }, []); useNavButtonPressed(RIGHT_BUTTON.id, componentId, onSavePostMessage, [postMessage]); useNavButtonPressed(closeButtonId, componentId, onClose, []); useAndroidHardwareBackHandler(componentId, onClose); const overlap = useKeyboardOverlap(mainView, containerHeight); const autocompletePosition = overlap + AUTOCOMPLETE_SEPARATION; const autocompleteAvailableSpace = containerHeight - autocompletePosition; const [animatedAutocompletePosition, animatedAutocompleteAvailableSpace] = useAutocompleteDefaultAnimatedValues(autocompletePosition, autocompleteAvailableSpace); if (isUpdating) { return ( ); } return ( <> {Boolean((errorLine || errorExtra)) && } ); }; export default EditPost;