diff --git a/src/components/Composer/index.native.tsx b/src/components/Composer/index.native.tsx index 9b886aa49c1..da2fc1a9b29 100644 --- a/src/components/Composer/index.native.tsx +++ b/src/components/Composer/index.native.tsx @@ -1,13 +1,14 @@ import type {MarkdownStyle} from '@expensify/react-native-live-markdown'; import mimeDb from 'mime-db'; import type {ForwardedRef} from 'react'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {NativeSyntheticEvent, TextInput, TextInputChangeEventData, TextInputPasteEventData} from 'react-native'; import {StyleSheet} from 'react-native'; import type {FileObject} from '@components/AttachmentModal'; import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput'; import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useKeyboardState from '@hooks/useKeyboardState'; import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useResetComposerFocus from '@hooks/useResetComposerFocus'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -37,6 +38,7 @@ function Composer( selection, value, isGroupPolicyReport = false, + showSoftInputOnFocus, ...props }: ComposerProps, ref: ForwardedRef, @@ -48,8 +50,11 @@ function Composer( const markdownStyle = useMarkdownStyle(value, !isGroupPolicyReport ? excludeReportMentionStyle : excludeNoStyles); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const [contextMenuHidden, setContextMenuHidden] = useState(!showSoftInputOnFocus); const {inputCallbackRef, inputRef: autoFocusInputRef} = useAutoFocusInput(); + const keyboardState = useKeyboardState(); + const isKeyboardShown = keyboardState?.isKeyboardShown ?? false; useEffect(() => { if (autoFocus === !!autoFocusInputRef.current) { @@ -109,6 +114,13 @@ function Composer( const maxHeightStyle = useMemo(() => StyleUtils.getComposerMaxHeightStyle(maxLines, isComposerFullSize), [StyleUtils, isComposerFullSize, maxLines]); const composerStyle = useMemo(() => StyleSheet.flatten([style, textContainsOnlyEmojis ? styles.onlyEmojisTextLineHeight : {}]), [style, textContainsOnlyEmojis, styles]); + useEffect(() => { + if (!showSoftInputOnFocus || !isKeyboardShown) { + return; + } + setContextMenuHidden(false); + }, [showSoftInputOnFocus, isKeyboardShown]); + return ( ); } diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 72116a346c0..55d14a116c3 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -73,6 +73,7 @@ function Composer( isComposerFullSize = false, shouldContainScroll = true, isGroupPolicyReport = false, + showSoftInputOnFocus = true, ...props }: ComposerProps, ref: ForwardedRef, @@ -388,6 +389,7 @@ function Composer( value={value} defaultValue={defaultValue} autoFocus={autoFocus} + inputMode={!showSoftInputOnFocus ? 'none' : 'text'} /* eslint-disable-next-line react/jsx-props-no-spreading */ {...props} onSelectionChange={addCursorPositionToSelectionChange} diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts index 41138970c54..616bc102c0f 100644 --- a/src/components/Composer/types.ts +++ b/src/components/Composer/types.ts @@ -77,6 +77,8 @@ type ComposerProps = Omit & { /** Indicates whether the composer is in a group policy report. Used for disabling report mentioning style in markdown input */ isGroupPolicyReport?: boolean; + + showSoftInputOnFocus?: boolean; }; export type {TextSelection, ComposerProps, CustomSelectionChangeEvent}; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 69c4402e959..c9287f871aa 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -238,7 +238,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro const {reportPendingAction, reportErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report); const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}]; - const isEmptyChat = useMemo(() => ReportUtils.isEmptyReport(report), [report]); const isOptimisticDelete = report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; const indexOfLinkedMessage = useMemo( (): number => reportActions.findIndex((obj) => String(obj.reportActionID) === String(reportActionIDFromRoute)), @@ -802,7 +801,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro policy={policy} pendingAction={reportPendingAction} isComposerFullSize={!!isComposerFullSize} - isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} workspaceTooltip={workspaceTooltip} /> diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index e63bd952b4a..752d35a6cbe 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -29,7 +29,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Browser from '@libs/Browser'; -import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import {forceClearInput} from '@libs/ComponentUtils'; import * as ComposerUtils from '@libs/ComposerUtils'; import convertToLTRForComposer from '@libs/convertToLTRForComposer'; @@ -40,7 +39,6 @@ import getPlatform from '@libs/getPlatform'; import * as KeyDownListener from '@libs/KeyboardShortcut/KeyDownPressListener'; import Parser from '@libs/Parser'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside'; @@ -66,9 +64,6 @@ type SyncSelection = { type NewlyAddedChars = {startIndex: number; endIndex: number; diff: string}; type ComposerWithSuggestionsOnyxProps = { - /** The parent report actions for the report */ - parentReportActions: OnyxEntry; - /** The modal state */ modal: OnyxEntry; @@ -150,22 +145,12 @@ type ComposerWithSuggestionsProps = ComposerWithSuggestionsOnyxProps & /** Whether the edit is focused */ editFocused: boolean; - /** Wheater chat is empty */ - isEmptyChat?: boolean; - /** The last report action */ lastReportAction?: OnyxEntry; /** Whether to include chronos */ includeChronos?: boolean; - /** The parent report action ID */ - parentReportActionID?: string; - - /** The parent report ID */ - // eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC - parentReportID: string | undefined; - /** Whether report is from group policy */ isGroupPolicyReport: boolean; @@ -211,10 +196,6 @@ const debouncedBroadcastUserIsTyping = lodashDebounce( const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc(); -// We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will -// prevent auto focus on existing chat for mobile device -const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); - /** * This component holds the value and selection state. * If a component really needs access to these state values it should be put here. @@ -226,14 +207,11 @@ function ComposerWithSuggestions( // Onyx modal, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, - parentReportActions, // Props: Report reportID, includeChronos, - isEmptyChat, lastReportAction, - parentReportActionID, isGroupPolicyReport, policyID, @@ -298,17 +276,13 @@ function ComposerWithSuggestions( const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; - const parentReportAction = parentReportActions?.[parentReportActionID ?? '-1']; - const shouldAutoFocus = - !modal?.isVisible && - Modal.areAllModalsHidden() && - isFocused && - (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction))) && - shouldShowComposeInput; + const shouldAutoFocus = !modal?.isVisible && shouldShowComposeInput && Modal.areAllModalsHidden() && isFocused; const valueRef = useRef(value); valueRef.current = value; + const [showSoftInputOnFocus, setShowSoftInputOnFocus] = useState(false); + const [selection, setSelection] = useState(() => ({start: value.length, end: value.length, positionX: 0, positionY: 0})); const [composerHeight, setComposerHeight] = useState(0); @@ -798,6 +772,19 @@ function ComposerWithSuggestions( shouldCalculateCaretPosition onLayout={onLayout} onScroll={hideSuggestionMenu} + showSoftInputOnFocus={showSoftInputOnFocus} + onTouchStart={() => { + if (showSoftInputOnFocus) { + return; + } + if (Browser.isMobileSafari()) { + setTimeout(() => { + setShowSoftInputOnFocus(true); + }, CONST.ANIMATED_TRANSITION); + return; + } + setShowSoftInputOnFocus(true); + }} shouldContainScroll={Browser.isMobileSafari()} isGroupPolicyReport={isGroupPolicyReport} /> @@ -848,11 +835,6 @@ export default withOnyx `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, - canEvict: false, - initWithStoredValues: false, - }, })(memo(ComposerWithSuggestionsWithRef)); export type {ComposerWithSuggestionsProps, ComposerRef}; diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index e4b0eef6ca5..3f1e5431f3e 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -28,7 +28,6 @@ import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import {getDraftComment} from '@libs/DraftCommentUtils'; import getModalState from '@libs/getModalState'; @@ -64,7 +63,7 @@ type SuggestionsRef = { }; type ReportActionComposeProps = WithCurrentUserPersonalDetailsProps & - Pick & { + Pick & { /** A method to call when the form is submitted */ onSubmit: (newComment: string) => void; @@ -90,10 +89,6 @@ type ReportActionComposeProps = WithCurrentUserPersonalDetailsProps & shouldShowEducationalTooltip?: boolean; }; -// We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will -// prevent auto focus on existing chat for mobile device -const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); - const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc(); // eslint-disable-next-line import/no-mutable-exports @@ -108,7 +103,6 @@ function ReportActionCompose({ report, reportID, isReportReadyForDisplay = true, - isEmptyChat, lastReportAction, shouldShowEducationalTooltip, onComposerFocus, @@ -129,7 +123,7 @@ function ReportActionCompose({ */ const [isFocused, setIsFocused] = useState(() => { const initialModalState = getModalState(); - return shouldFocusInputOnScreenFocus && shouldShowComposeInput && !initialModalState?.isVisible && !initialModalState?.willAlertModalBecomeVisible; + return shouldShowComposeInput && !initialModalState?.isVisible && !initialModalState?.willAlertModalBecomeVisible; }); const [isFullComposerAvailable, setIsFullComposerAvailable] = useState(isComposerFullSize); const [shouldHideEducationalTooltip, setShouldHideEducationalTooltip] = useState(false); @@ -465,11 +459,8 @@ function ReportActionCompose({ raiseIsScrollLikelyLayoutTriggered={raiseIsScrollLikelyLayoutTriggered} reportID={reportID} policyID={report?.policyID ?? '-1'} - parentReportID={report?.parentReportID} - parentReportActionID={report?.parentReportActionID} includeChronos={ReportUtils.chatIncludesChronos(report)} isGroupPolicyReport={isGroupPolicyReport} - isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} isMenuVisible={isMenuVisible} inputPlaceholder={inputPlaceholder} diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index 2bf744868a9..74ff56d21db 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -47,9 +47,6 @@ type ReportFooterProps = { /** Whether to show educational tooltip in workspace chat for first-time user */ workspaceTooltip: OnyxEntry; - /** Whether the chat is empty */ - isEmptyChat?: boolean; - /** The pending action when we are adding a chat */ pendingAction?: PendingAction; @@ -72,7 +69,6 @@ function ReportFooter({ report = {reportID: '-1'}, reportMetadata, policy, - isEmptyChat = true, isReportReadyForDisplay = true, isComposerFullSize = false, workspaceTooltip, @@ -213,7 +209,6 @@ function ReportFooter({ onComposerBlur={onComposerBlur} reportID={report.reportID} report={report} - isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} pendingAction={pendingAction} isComposerFullSize={isComposerFullSize} @@ -235,7 +230,6 @@ export default memo( lodashIsEqual(prevProps.report, nextProps.report) && prevProps.pendingAction === nextProps.pendingAction && prevProps.isComposerFullSize === nextProps.isComposerFullSize && - prevProps.isEmptyChat === nextProps.isEmptyChat && prevProps.lastReportAction === nextProps.lastReportAction && prevProps.isReportReadyForDisplay === nextProps.isReportReadyForDisplay && prevProps.workspaceTooltip?.shouldShow === nextProps.workspaceTooltip?.shouldShow &&