diff --git a/Mobile-Expensify b/Mobile-Expensify index 768d69540612..7ffe8a7f1b47 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 768d695406126652ce222a46c95e643ba2e51e45 +Subproject commit 7ffe8a7f1b471c697f9823b8cd4a2c19b200fa6f diff --git a/android/app/build.gradle b/android/app/build.gradle index 09bf1473e841..7fd4fd9dd59d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009007801 - versionName "9.0.78-1" + versionCode 1009007802 + versionName "9.0.78-2" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-1.png b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-1.png new file mode 100644 index 000000000000..18318f782466 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-1.png differ diff --git a/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-2.png b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-2.png new file mode 100644 index 000000000000..641c32a6a6b6 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-2.png differ diff --git a/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-3.png b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-3.png new file mode 100644 index 000000000000..48c6f12fb75c Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-3.png differ diff --git a/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-4.png b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-4.png new file mode 100644 index 000000000000..5f8af1e46ac4 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-4.png differ diff --git a/docs/assets/images/OldDot - Create & Pay Bills 1.png b/docs/assets/images/OldDot - Create & Pay Bills 1.png new file mode 100644 index 000000000000..a880e012408a Binary files /dev/null and b/docs/assets/images/OldDot - Create & Pay Bills 1.png differ diff --git a/docs/assets/images/OldDot - Create & Pay Bills 2.png b/docs/assets/images/OldDot - Create & Pay Bills 2.png new file mode 100644 index 000000000000..ce022a95c6a1 Binary files /dev/null and b/docs/assets/images/OldDot - Create & Pay Bills 2.png differ diff --git a/docs/assets/images/OldDot - Create & Pay Bills 3.png b/docs/assets/images/OldDot - Create & Pay Bills 3.png new file mode 100644 index 000000000000..071bcc997934 Binary files /dev/null and b/docs/assets/images/OldDot - Create & Pay Bills 3.png differ diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 6a6d5bef5d7d..3374f9c36b3f 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.78.1 + 9.0.78.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index c996883405b2..6f72c68b009d 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.78.1 + 9.0.78.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 4e4305e1cbab..328278e16cf3 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.78 CFBundleVersion - 9.0.78.1 + 9.0.78.2 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index a1e8c86e2a78..ae031453f883 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.78-1", + "version": "9.0.78-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.78-1", + "version": "9.0.78-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index b2248ab75cc2..3b5a25abb224 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.78-1", + "version": "9.0.78-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 0cddb32f5aeb..a02767d24c87 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -1,7 +1,7 @@ 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'; @@ -9,6 +9,7 @@ import type {ComposerProps} from '@components/Composer/types'; 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 = true, ...props }: ComposerProps, ref: ForwardedRef, @@ -49,7 +51,11 @@ function Composer( const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const [contextMenuHidden, setContextMenuHidden] = useState(true); + const {inputCallbackRef, inputRef: autoFocusInputRef} = useAutoFocusInput(); + const keyboardState = useKeyboardState(); + const isKeyboardShown = keyboardState?.isKeyboardShown ?? false; useEffect(() => { if (autoFocus === !!autoFocusInputRef.current) { @@ -58,6 +64,13 @@ function Composer( inputCallbackRef(autoFocus ? textInput.current : null); }, [autoFocus, inputCallbackRef, autoFocusInputRef]); + useEffect(() => { + if (!showSoftInputOnFocus || !isKeyboardShown) { + return; + } + setContextMenuHidden(false); + }, [showSoftInputOnFocus, isKeyboardShown]); + useEffect(() => { if (!textInput.current || !textInput.current.setSelection || !selection || isComposerFullSize) { return; @@ -158,6 +171,8 @@ function Composer( props?.onBlur?.(e); }} onClear={onClear} + showSoftInputOnFocus={showSoftInputOnFocus} + contextMenuHidden={contextMenuHidden} /> ); } diff --git a/src/components/Composer/implementation/index.tsx b/src/components/Composer/implementation/index.tsx index 5af76a2406b5..9171132964f6 100755 --- a/src/components/Composer/implementation/index.tsx +++ b/src/components/Composer/implementation/index.tsx @@ -5,7 +5,7 @@ import type {BaseSyntheticEvent, ForwardedRef} from 'react'; import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; // eslint-disable-next-line no-restricted-imports import type {NativeSyntheticEvent, TextInput, TextInputKeyPressEventData, TextInputSelectionChangeEventData} from 'react-native'; -import {DeviceEventEmitter, StyleSheet} from 'react-native'; +import {DeviceEventEmitter, InteractionManager, StyleSheet} from 'react-native'; import type {ComposerProps} from '@components/Composer/types'; import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput'; import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; @@ -50,6 +50,7 @@ function Composer( isComposerFullSize = false, shouldContainScroll = true, isGroupPolicyReport = false, + showSoftInputOnFocus = true, ...props }: ComposerProps, ref: ForwardedRef, @@ -74,6 +75,11 @@ function Composer( }); const [hasMultipleLines, setHasMultipleLines] = useState(false); const [isRendered, setIsRendered] = useState(false); + + // On mobile safari, the cursor will move from right to left with inputMode set to none during report transition + // To avoid that we should hide the cursor util the transition is finished + const [shouldTransparentCursor, setShouldTransparentCursor] = useState(!showSoftInputOnFocus && Browser.isMobileSafari()); + const isScrollBarVisible = useIsScrollBarVisible(textInput, value ?? ''); const [prevScroll, setPrevScroll] = useState(); const [prevHeight, setPrevHeight] = useState(); @@ -260,6 +266,15 @@ function Composer( setIsRendered(true); }, []); + useEffect(() => { + if (!shouldTransparentCursor) { + return; + } + InteractionManager.runAfterInteractions(() => { + setShouldTransparentCursor(false); + }); + }, [shouldTransparentCursor]); + const clear = useCallback(() => { if (!textInput.current) { return; @@ -347,11 +362,12 @@ function Composer( placeholderTextColor={theme.placeholderText} ref={(el) => (textInput.current = el)} selection={selection} - style={[inputStyleMemo]} + style={[inputStyleMemo, shouldTransparentCursor ? {caretColor: 'transparent'} : undefined]} markdownStyle={markdownStyle} value={value} defaultValue={defaultValue} autoFocus={autoFocus} + inputMode={showSoftInputOnFocus ? 'text' : 'none'} /* 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 3df5508f1dd7..6ea3bdb2f824 100644 --- a/src/components/Composer/types.ts +++ b/src/components/Composer/types.ts @@ -68,6 +68,9 @@ type ComposerProps = Omit & { /** Indicates whether the composer is in a group policy report. Used for disabling report mentioning style in markdown input */ isGroupPolicyReport?: boolean; + + /** Whether to show the keyboard on focus */ + showSoftInputOnFocus?: boolean; }; export type {TextSelection, ComposerProps, CustomSelectionChangeEvent}; diff --git a/src/components/ConnectToNetSuiteFlow/index.tsx b/src/components/ConnectToNetSuiteFlow/index.tsx index 1d33eb07df4f..7957896d4006 100644 --- a/src/components/ConnectToNetSuiteFlow/index.tsx +++ b/src/components/ConnectToNetSuiteFlow/index.tsx @@ -59,10 +59,11 @@ function ConnectToNetSuiteFlow({policyID}: ConnectToNetSuiteFlowProps) { if (threeDotsMenuContainerRef) { if (!shouldUseNarrowLayout) { threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { - setReuseConnectionPopoverPosition({ - horizontal: x + width, - vertical: y + height, - }); + const horizontal = x + width; + const vertical = y + height; + if (reuseConnectionPopoverPosition.horizontal !== horizontal || reuseConnectionPopoverPosition.vertical !== vertical) { + setReuseConnectionPopoverPosition({horizontal, vertical}); + } }); } diff --git a/src/components/ConnectToSageIntacctFlow/index.tsx b/src/components/ConnectToSageIntacctFlow/index.tsx index f93fce9c668a..807082365042 100644 --- a/src/components/ConnectToSageIntacctFlow/index.tsx +++ b/src/components/ConnectToSageIntacctFlow/index.tsx @@ -64,10 +64,11 @@ function ConnectToSageIntacctFlow({policyID}: ConnectToSageIntacctFlowProps) { if (threeDotsMenuContainerRef) { if (!shouldUseNarrowLayout) { threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { - setReuseConnectionPopoverPosition({ - horizontal: x + width, - vertical: y + height, - }); + const horizontal = x + width; + const vertical = y + height; + if (reuseConnectionPopoverPosition.horizontal !== horizontal || reuseConnectionPopoverPosition.vertical !== vertical) { + setReuseConnectionPopoverPosition({horizontal, vertical}); + } }); } diff --git a/src/languages/es.ts b/src/languages/es.ts index ed16213be0d0..cb7f53424958 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4324,11 +4324,11 @@ const translations = { planTypePage: { planTypes: { team: { - label: 'Collect', + label: 'Recopilar', description: 'Para equipos que buscan automatizar sus procesos.', }, corporate: { - label: 'Recolectar', + label: 'Controlar', description: 'Para organizaciones con requisitos avanzados.', }, }, diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index e6123733b0e8..134364ddbad6 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -79,8 +79,8 @@ function hideEmojiPicker(isNavigating?: boolean) { /** * Whether Emoji Picker is active for the given id. */ -function isActive(id: string): boolean { - if (!emojiPickerRef.current) { +function isActive(id?: string): boolean { + if (!emojiPickerRef.current || !id) { return false; } diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index c5f3bb442ab9..9de6b2632660 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -253,7 +253,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)), @@ -281,6 +280,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '-1'}`]; const isTopMostReportId = currentReportID === reportIDFromRoute; const didSubscribeToReportLeavingEvents = useRef(false); + const [showSoftInputOnFocus, setShowSoftInputOnFocus] = useState(false); useEffect(() => { if (!report?.reportID || shouldHideReport) { @@ -295,7 +295,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro Navigation.dismissModal(); return; } - Navigation.goBack(ROUTES.HOME, false, true); + Navigation.goBack(undefined, false, true); }, [isInNarrowPaneModal]); let headerView = ( @@ -758,7 +758,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro ) : null} diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 7b0d6663facf..b56109b64c40 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -40,7 +40,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'; @@ -126,27 +125,26 @@ type ComposerWithSuggestionsProps = Partial & { /** The ref to the next modal will open */ isNextModalWillOpenRef: MutableRefObject; - /** 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; /** policy ID of the report */ - policyID: string; + policyID?: string; + + /** Whether to show the keyboard on focus */ + showSoftInputOnFocus: boolean; + + /** A method to update showSoftInputOnFocus */ + setShowSoftInputOnFocus: (value: boolean) => void; + + /** Whether the main composer was hidden */ + didHideComposerInput?: boolean; }; type SwitchToCurrentReportProps = { @@ -187,10 +185,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. @@ -201,11 +195,8 @@ function ComposerWithSuggestions( { // Props: Report reportID, - parentReportID, includeChronos, - isEmptyChat, lastReportAction, - parentReportActionID, isGroupPolicyReport, policyID, @@ -236,6 +227,9 @@ function ComposerWithSuggestions( // For testing children, + showSoftInputOnFocus, + setShowSoftInputOnFocus, + didHideComposerInput, }: ComposerWithSuggestionsProps, ref: ForwardedRef, ) { @@ -257,14 +251,12 @@ function ComposerWithSuggestions( } return draftComment; }); + const commentRef = useRef(value); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const [modal] = useOnyx(ONYXKEYS.MODAL); const [preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: EmojiUtils.getPreferredSkinToneIndex}); const [editFocused] = useOnyx(ONYXKEYS.INPUT_FOCUSED); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID || '-1'}`, {canEvict: false, initWithStoredValues: false}); const lastTextRef = useRef(value); useEffect(() => { @@ -274,13 +266,7 @@ function ComposerWithSuggestions( const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; - const parentReportAction = useMemo(() => parentReportActions?.[parentReportActionID ?? '-1'], [parentReportActionID, parentReportActions]); - const shouldAutoFocus = - !modal?.isVisible && - Modal.areAllModalsHidden() && - isFocused && - (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction) && !ReportUtils.isTaskReport(report))) && - shouldShowComposeInput; + const shouldAutoFocus = !modal?.isVisible && shouldShowComposeInput && Modal.areAllModalsHidden() && isFocused && !didHideComposerInput; const valueRef = useRef(value); valueRef.current = value; @@ -643,7 +629,15 @@ function ComposerWithSuggestions( // We want to focus or refocus the input when a modal has been closed or the underlying screen is refocused. // We avoid doing this on native platforms since the software keyboard popping // open creates a jarring and broken UX. - if (!((willBlurTextInputOnTapOutside || shouldAutoFocus) && !isNextModalWillOpenRef.current && !modal?.isVisible && isFocused && (!!prevIsModalVisible || !prevIsFocused))) { + if ( + !( + (willBlurTextInputOnTapOutside || (shouldAutoFocus && canFocusInputOnScreenFocus())) && + !isNextModalWillOpenRef.current && + !modal?.isVisible && + isFocused && + (!!prevIsModalVisible || !prevIsFocused) + ) + ) { return; } @@ -775,6 +769,19 @@ function ComposerWithSuggestions( onScroll={hideSuggestionMenu} shouldContainScroll={Browser.isMobileSafari()} isGroupPolicyReport={isGroupPolicyReport} + showSoftInputOnFocus={showSoftInputOnFocus} + onTouchStart={() => { + if (showSoftInputOnFocus) { + return; + } + if (Browser.isMobileSafari()) { + setTimeout(() => { + setShowSoftInputOnFocus(true); + }, CONST.ANIMATED_TRANSITION); + return; + } + setShowSoftInputOnFocus(true); + }} /> diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 78d3288d05f0..c3caaf16a3ef 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -64,7 +64,7 @@ type SuggestionsRef = { getIsSuggestionsMenuVisible: () => boolean; }; -type ReportActionComposeProps = Pick & { +type ReportActionComposeProps = Pick & { /** A method to call when the form is submitted */ onSubmit: (newComment: string) => void; @@ -88,6 +88,15 @@ type ReportActionComposeProps = Pick void; + + /** Whether the main composer was hidden */ + didHideComposerInput?: boolean; }; // We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will @@ -107,11 +116,13 @@ function ReportActionCompose({ report, reportID, isReportReadyForDisplay = true, - isEmptyChat, lastReportAction, shouldShowEducationalTooltip, + showSoftInputOnFocus, onComposerFocus, onComposerBlur, + setShowSoftInputOnFocus, + didHideComposerInput, }: ReportActionComposeProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -324,7 +335,7 @@ function ReportActionCompose({ // We are returning a callback here as we want to incoke the method on unmount only useEffect( () => () => { - if (!EmojiPickerActions.isActive(report?.reportID ?? '-1')) { + if (!EmojiPickerActions.isActive(report?.reportID)) { return; } EmojiPickerActions.hideEmojiPicker(); @@ -487,12 +498,9 @@ function ReportActionCompose({ isScrollLikelyLayoutTriggered={isScrollLikelyLayoutTriggered} raiseIsScrollLikelyLayoutTriggered={raiseIsScrollLikelyLayoutTriggered} reportID={reportID} - policyID={report?.policyID ?? '-1'} - parentReportID={report?.parentReportID} - parentReportActionID={report?.parentReportActionID} + policyID={report?.policyID} includeChronos={ReportUtils.chatIncludesChronos(report)} isGroupPolicyReport={isGroupPolicyReport} - isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} isMenuVisible={isMenuVisible} inputPlaceholder={inputPlaceholder} @@ -507,7 +515,10 @@ function ReportActionCompose({ onFocus={onFocus} onBlur={onBlur} measureParentContainer={measureContainer} + showSoftInputOnFocus={showSoftInputOnFocus} + setShowSoftInputOnFocus={setShowSoftInputOnFocus} onValueChange={onValueChange} + didHideComposerInput={didHideComposerInput} /> { diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index f46e2a9476b3..174549806db9 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -1,6 +1,6 @@ import {Str} from 'expensify-common'; import lodashIsEqual from 'lodash/isEqual'; -import React, {memo, useCallback} from 'react'; +import React, {memo, useCallback, useEffect, useState} from 'react'; import {Keyboard, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; @@ -45,9 +45,6 @@ type ReportFooterProps = { /** The last report action */ lastReportAction?: OnyxEntry; - /** Whether the chat is empty */ - isEmptyChat?: boolean; - /** The pending action when we are adding a chat */ pendingAction?: PendingAction; @@ -62,6 +59,12 @@ type ReportFooterProps = { /** A method to call when the input is blur */ onComposerBlur: () => void; + + /** Whether to show the keyboard on focus */ + showSoftInputOnFocus: boolean; + + /** A method to update showSoftInputOnFocus */ + setShowSoftInputOnFocus: (value: boolean) => void; }; function ReportFooter({ @@ -70,11 +73,12 @@ function ReportFooter({ report = {reportID: '-1'}, reportMetadata, policy, - isEmptyChat = true, isReportReadyForDisplay = true, isComposerFullSize = false, + showSoftInputOnFocus, onComposerBlur, onComposerFocus, + setShowSoftInputOnFocus, }: ReportFooterProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); @@ -99,7 +103,7 @@ function ReportFooter({ } }, }); - const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`); + const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`); const chatFooterStyles = {...styles.chatFooter, minHeight: !isOffline ? CONST.CHAT_FOOTER_MIN_HEIGHT : 0}; const isArchivedRoom = ReportUtils.isArchivedRoom(report, reportNameValuePairs); @@ -168,6 +172,15 @@ function ReportFooter({ [report.reportID, handleCreateTask], ); + const [didHideComposerInput, setDidHideComposerInput] = useState(!shouldShowComposeInput); + + useEffect(() => { + if (didHideComposerInput || shouldShowComposeInput) { + return; + } + setDidHideComposerInput(true); + }, [shouldShowComposeInput, didHideComposerInput]); + return ( <> {!!shouldHideComposer && ( @@ -209,12 +222,14 @@ function ReportFooter({ onComposerBlur={onComposerBlur} reportID={report.reportID} report={report} - isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} pendingAction={pendingAction} isComposerFullSize={isComposerFullSize} isReportReadyForDisplay={isReportReadyForDisplay} shouldShowEducationalTooltip={didScreenTransitionEnd && shouldShowEducationalTooltip} + showSoftInputOnFocus={showSoftInputOnFocus} + setShowSoftInputOnFocus={setShowSoftInputOnFocus} + didHideComposerInput={didHideComposerInput} /> @@ -231,9 +246,9 @@ 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.showSoftInputOnFocus === nextProps.showSoftInputOnFocus && lodashIsEqual(prevProps.reportMetadata, nextProps.reportMetadata) && lodashIsEqual(prevProps.policy?.employeeList, nextProps.policy?.employeeList) && lodashIsEqual(prevProps.policy?.role, nextProps.policy?.role), diff --git a/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx b/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx index 29474431cd72..7ac4329c3a50 100644 --- a/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx +++ b/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx @@ -7,6 +7,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {connectToSageIntacct} from '@libs/actions/connections/SageIntacct'; @@ -24,6 +25,7 @@ type SageIntacctPrerequisitesPageProps = PlatformStackScreenProps {translate('workspace.intacct.enterCredentials')} - {formItems.map((formItem) => ( + {formItems.map((formItem, index) => ( {}} /> );