From feb57aed753c1f464331bafe2b27fda03c217dfe Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 11 Jan 2024 21:12:49 +0700 Subject: [PATCH 001/961] fix: 34120 --- src/components/DistanceEReceipt.js | 1 + src/components/Image/index.js | 10 +++++-- src/components/Image/index.native.js | 26 ++++++++++++++++++- src/components/ImageWithSizeCalculation.tsx | 5 +++- .../MoneyRequestConfirmationList.js | 1 + ...oraryForRefactorRequestConfirmationList.js | 1 + .../ReportActionItem/ReportActionItemImage.js | 12 ++++++--- src/components/ThumbnailImage.tsx | 4 ++- .../Navigation/AppNavigator/AuthScreens.tsx | 2 +- src/pages/ErrorPage/NotFoundPage.js | 1 + 10 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 0241eea44063..5ffece092770 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -72,6 +72,7 @@ function DistanceEReceipt({transaction}) { style={[styles.w100, styles.h100]} isAuthTokenRequired shouldDynamicallyResize={false} + objectPositionTop /> )} diff --git a/src/components/Image/index.js b/src/components/Image/index.js index ef1a69e19c12..58b7ec3383a4 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,5 +1,5 @@ import lodashGet from 'lodash/get'; -import React, {useEffect, useMemo} from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {Image as RNImage} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -9,6 +9,7 @@ import RESIZE_MODES from './resizeModes'; function Image(props) { const {source: propsSource, isAuthTokenRequired, onLoad, session} = props; + const [aspectRatio, setAspectRatio] = useState(); /** * Check if the image source is a URL - if so the `encryptedAuthToken` is appended * to the source. @@ -38,8 +39,12 @@ function Image(props) { } RNImage.getSize(source.uri, (width, height) => { onLoad({nativeEvent: {width, height}}); + + if (props.objectPositionTop) { + setAspectRatio(height ? width / height : 'auto'); + } }); - }, [onLoad, source]); + }, [onLoad, source, props.objectPositionTop]); // Omit the props which the underlying RNImage won't use const forwardedProps = _.omit(props, ['source', 'onLoad', 'session', 'isAuthTokenRequired']); @@ -49,6 +54,7 @@ function Image(props) { // eslint-disable-next-line react/jsx-props-no-spreading {...forwardedProps} source={source} + style={[forwardedProps.style, aspectRatio !== undefined && {aspectRatio, height: 'auto'}, props.objectPositionTop && !aspectRatio && {opacity: 0}]} /> ); } diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index f31cfb6936d9..edea86725ae7 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -1,11 +1,12 @@ import {Image as ImageComponent} from 'expo-image'; import lodashGet from 'lodash/get'; -import React from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; +import {Image as RNImage} from 'react-native'; const dimensionsCache = new Map(); @@ -16,6 +17,7 @@ function resolveDimensions(key) { function Image(props) { // eslint-disable-next-line react/destructuring-assignment const {source, isAuthTokenRequired, session, ...rest} = props; + const [aspectRatio, setAspectRatio] = useState(); let imageSource = source; if (source && source.uri && typeof source.uri === 'number') { @@ -33,6 +35,27 @@ function Image(props) { }; } + const newSource = useMemo(() => { + if (isAuthTokenRequired) { + const authToken = lodashGet(session, 'encryptedAuthToken', null); + return {uri: `${source.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; + } + return source; + }, [source, isAuthTokenRequired]); + + useEffect(() => { + if (props.onLoad == null) { + return; + } + RNImage.getSize(newSource.uri, (width, height) => { + props.onLoad({nativeEvent: {width, height}}); + + if (props.objectPositionTop) { + setAspectRatio(height ? width / height : 'auto'); + } + }); + }, [props.onLoad, newSource]); + return ( ); } diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index b13d863d97e1..7ebdd41379c1 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -29,6 +29,8 @@ type ImageWithSizeCalculationProps = { /** Whether the image requires an authToken */ isAuthTokenRequired: boolean; + + objectPositionTop: boolean; }; /** @@ -37,7 +39,7 @@ type ImageWithSizeCalculationProps = { * performing some calculation on a network image after fetching dimensions so * it can be appropriately resized. */ -function ImageWithSizeCalculation({url, style, onMeasure, isAuthTokenRequired}: ImageWithSizeCalculationProps) { +function ImageWithSizeCalculation({url, style, onMeasure, isAuthTokenRequired, objectPositionTop}: ImageWithSizeCalculationProps) { const styles = useThemeStyles(); const isLoadedRef = useRef(null); const [isImageCached, setIsImageCached] = useState(true); @@ -88,6 +90,7 @@ function ImageWithSizeCalculation({url, style, onMeasure, isAuthTokenRequired}: }} onError={onError} onLoad={imageLoadedSuccessfully} + objectPositionTop={objectPositionTop} /> {isLoading && !isImageCached && } diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 13dce9337673..aa33ef87a976 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -612,6 +612,7 @@ function MoneyRequestConfirmationList(props) { // but we don't need it to load the blob:// or file:// image when starting a money request / split bill // So if we have a thumbnail, it means we're retrieving the image from the server isAuthTokenRequired={!_.isEmpty(receiptThumbnail)} + objectPositionTop /> )} {props.shouldShowSmartScanFields && ( diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 36d424ea28f2..2888f7cf2cdf 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -646,6 +646,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // but we don't need it to load the blob:// or file:// image when starting a money request / split bill // So if we have a thumbnail, it means we're retrieving the image from the server isAuthTokenRequired={!_.isEmpty(receiptThumbnail)} + objectPositionTop /> )} {shouldShowSmartScanFields && ( diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index 1495dcbd9111..d8f461aec138 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -72,14 +72,18 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transactio style={[styles.w100, styles.h100]} isAuthTokenRequired shouldDynamicallyResize={false} + objectPositionTop /> ); } else { receiptImageComponent = ( - + + + ); } diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index 3c903ee9e78f..b0fecd07093d 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -26,6 +26,7 @@ type ThumbnailImageProps = { /** Should the image be resized on load or just fit container */ shouldDynamicallyResize?: boolean; + objectPositionTop?: boolean; }; type UpdateImageSizeParams = { @@ -71,7 +72,7 @@ function calculateThumbnailImageSize(width: number, height: number, windowHeight return {thumbnailWidth: Math.max(40, thumbnailScreenWidth), thumbnailHeight: Math.max(40, thumbnailScreenHeight)}; } -function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidth = 200, imageHeight = 200, shouldDynamicallyResize = true}: ThumbnailImageProps) { +function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidth, imageHeight, shouldDynamicallyResize = true, objectPositionTop = false}: ThumbnailImageProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {windowHeight} = useWindowDimensions(); @@ -103,6 +104,7 @@ function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidt url={previewSourceURL} onMeasure={updateImageSize} isAuthTokenRequired={isAuthTokenRequired} + objectPositionTop={objectPositionTop} /> diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 7286615e6ba6..caeb65fbaa3a 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -291,7 +291,7 @@ function AuthScreens({lastUpdateIDAppliedToClient, session, lastOpenedPublicRoom Navigation.navigate(ROUTES.HOME)}/>} /> ); From 0eeedc9bca10abc6554bd2eb5e412a35e9bbe835 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 11 Jan 2024 21:19:21 +0700 Subject: [PATCH 002/961] fix: redundant --- src/components/ThumbnailImage.tsx | 2 +- src/libs/Navigation/AppNavigator/AuthScreens.tsx | 2 +- src/pages/ErrorPage/NotFoundPage.js | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index b0fecd07093d..b04a67fbdc7d 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -72,7 +72,7 @@ function calculateThumbnailImageSize(width: number, height: number, windowHeight return {thumbnailWidth: Math.max(40, thumbnailScreenWidth), thumbnailHeight: Math.max(40, thumbnailScreenHeight)}; } -function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidth, imageHeight, shouldDynamicallyResize = true, objectPositionTop = false}: ThumbnailImageProps) { +function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidth = 200, imageHeight = 200, shouldDynamicallyResize = true, objectPositionTop = false}: ThumbnailImageProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {windowHeight} = useWindowDimensions(); diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index caeb65fbaa3a..7286615e6ba6 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -291,7 +291,7 @@ function AuthScreens({lastUpdateIDAppliedToClient, session, lastOpenedPublicRoom Navigation.navigate(ROUTES.HOME)}/>} + component={NotFoundPage} /> ); From 99c70920e29748b81f8aef54a607c3d9c3cfc51d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 15 Jan 2024 10:43:47 +0700 Subject: [PATCH 003/961] test --- .../ComposerWithSuggestionsEdit.tsx | 49 +++++++++++++++++++ .../report/ReportActionItemMessageEdit.tsx | 3 +- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx new file mode 100644 index 000000000000..2d742261419d --- /dev/null +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -0,0 +1,49 @@ +import { ComposerProps } from "@components/Composer/types"; +import Composer from "@components/Composer"; +import React, { ForwardedRef } from 'react'; +import { AnimatedProps } from "react-native-reanimated"; +import { TextInputProps } from "react-native"; + +type ComposerWithSuggestionsEditProps = { + +} + + +function ComposerWithSuggestionsEdit( + { + value, + maxLines = -1, + onKeyPress = () => {}, + style, + numberOfLines: numberOfLinesProp = 0, + onSelectionChange = () => {}, + selection = { + start: 0, + end: 0, + }, + onBlur = () => {}, + onFocus = () => {}, + onChangeText = () => {}, + id = undefined + }: ComposerWithSuggestionsEditProps & ComposerProps, + ref: ForwardedRef>>, +) { + return ( + + ) +} + +export default React.forwardRef(ComposerWithSuggestionsEdit); \ No newline at end of file diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 5934c4c333cb..a4d8d5da4889 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -38,6 +38,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; +import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; type ReportActionItemMessageEditProps = { /** All the data of the action */ @@ -410,7 +411,7 @@ function ReportActionItemMessageEdit( - { textInputRef.current = el; From dee95c99adb4d6529d71d3ea2c45748c9ccbfef9 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 15 Jan 2024 12:29:56 +0700 Subject: [PATCH 004/961] implement suggestion for edit composer --- src/pages/home/ReportScreen.js | 1 + .../ComposerWithSuggestionsEdit.tsx | 93 +++++++++++++++---- src/pages/home/report/ReportActionItem.js | 1 + .../report/ReportActionItemMessageEdit.tsx | 55 ++++++++++- src/pages/home/report/ReportActionsList.js | 2 + .../report/ReportActionsListItemRenderer.js | 3 + src/pages/home/report/ReportActionsView.js | 1 + 7 files changed, 136 insertions(+), 20 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 64e48ecd5509..59804340547a 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -523,6 +523,7 @@ function ReportScreen({ isLoadingOlderReportActions={reportMetadata.isLoadingOlderReportActions} isComposerFullSize={isComposerFullSize} policy={policy} + listHeight={listHeight} /> )} diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 2d742261419d..7acdb0c864ab 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,11 +1,25 @@ import { ComposerProps } from "@components/Composer/types"; import Composer from "@components/Composer"; -import React, { ForwardedRef } from 'react'; +import React, { Dispatch, ForwardedRef, MutableRefObject, SetStateAction, useRef, useState } from 'react'; import { AnimatedProps } from "react-native-reanimated"; import { TextInputProps } from "react-native"; +import Suggestions from '../Suggestions'; +import lodashGet from 'lodash/get'; +import * as SuggestionUtils from '@libs/SuggestionUtils'; +import useWindowDimensions from "@hooks/useWindowDimensions"; type ComposerWithSuggestionsEditProps = { - + setValue: Dispatch>, + setSelection: Dispatch>, + resetKeyboardInput: () => void, + isComposerFocused: boolean, + listHeight: number, + suggestionsRef: MutableRefObject, + updateDraft: (newValue: string) => void, + measureParentContainer: (callback: () => void) => void } @@ -24,25 +38,72 @@ function ComposerWithSuggestionsEdit( onBlur = () => {}, onFocus = () => {}, onChangeText = () => {}, + setValue = () => {}, + setSelection = () => {}, + resetKeyboardInput = () => {}, + isComposerFocused, + suggestionsRef, + listHeight, + updateDraft, + measureParentContainer, id = undefined }: ComposerWithSuggestionsEditProps & ComposerProps, ref: ForwardedRef>>, ) { + const {isSmallScreenWidth} = useWindowDimensions(); + + const suggestions = lodashGet(suggestionsRef, 'current.getSuggestions', () => [])(); + + const [composerHeight, setComposerHeight] = useState(0); + + const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions.length); + + console.log(hasEnoughSpaceForLargeSuggestion); + + const isAutoSuggestionPickerLarge = !isSmallScreenWidth || (isSmallScreenWidth && hasEnoughSpaceForLargeSuggestion); + + return ( - + <> + { + const composerLayoutHeight = e.nativeEvent.layout.height; + if (composerHeight === composerLayoutHeight) { + return; + } + setComposerHeight(composerLayoutHeight); + }} + /> + + + + ) } diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index e490c4601d10..496e48ae3519 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -512,6 +512,7 @@ function ReportActionItem(props) { shouldDisableEmojiPicker={ (ReportUtils.chatIncludesConcierge(props.report) && User.isBlockedFromConcierge(props.blockedFromConcierge)) || ReportUtils.isArchivedRoom(props.report) } + listHeight={props.listHeight} /> )} diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index a4d8d5da4889..ba247fc4be6d 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -3,7 +3,7 @@ import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {InteractionManager, Keyboard, View} from 'react-native'; +import {InteractionManager, Keyboard, NativeModules, View, findNodeHandle} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; import Composer from '@components/Composer'; @@ -39,6 +39,11 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; +import convertToLTRForComposer from '@libs/convertToLTRForComposer'; +import getPlatform from '@libs/getPlatform'; + +const {RNTextInputReset} = NativeModules; + type ReportActionItemMessageEditProps = { /** All the data of the action */ @@ -62,6 +67,8 @@ type ReportActionItemMessageEditProps = { /** Stores user's preferred skin tone */ preferredSkinTone?: number; + + listHeight: number; }; // native ids @@ -69,9 +76,9 @@ const emojiButtonID = 'emojiButton'; const messageEditInput = 'messageEditInput'; const isMobileSafari = Browser.isMobileSafari(); - +const isIOSNative = getPlatform() === CONST.PLATFORM.IOS; function ReportActionItemMessageEdit( - {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps, + {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, listHeight}: ReportActionItemMessageEditProps, forwardedRef: ForwardedRef, ) { const theme = useTheme(); @@ -124,6 +131,8 @@ function ReportActionItemMessageEdit( const isFocusedRef = useRef(false); const insertedEmojis = useRef([]); const draftRef = useRef(draft); + const suggestionsRef = useRef(); + const containerRef = useRef(null); useEffect(() => { if (ReportActionsUtils.isDeletedAction(action) || (action.message && draftMessage === action.message[0].html)) { @@ -253,6 +262,9 @@ function ReportActionItemMessageEdit( if (emojis?.length > 0) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (newEmojis?.length > 0) { + if (suggestionsRef.current) { + suggestionsRef.current.resetSuggestions(); + } insertedEmojis.current = [...insertedEmojis.current, ...newEmojis]; debouncedUpdateFrequentlyUsedEmojis(); } @@ -354,6 +366,8 @@ function ReportActionItemMessageEdit( */ const triggerSaveOrCancel = useCallback( (e: NativeSyntheticEvent | KeyboardEvent) => { + suggestionsRef.current.triggerHotkeyActions(e); + if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { return; } @@ -369,6 +383,25 @@ function ReportActionItemMessageEdit( [deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft], ); + const resetKeyboardInput = useCallback(() => { + if (!RNTextInputReset) { + return; + } + RNTextInputReset.resetKeyboardInput(findNodeHandle(textInputRef.current)); + }, [textInputRef]); + + const measureContainer = useCallback( + (callback) => { + if (!containerRef.current) { + return; + } + containerRef.current.measureInWindow(callback); + }, + // We added isComposerFullSize in dependencies so that when this value changes, we recalculate the position of the popup + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + /** * Focus the composer text input */ @@ -378,10 +411,13 @@ function ReportActionItemMessageEdit( validateCommentMaxLength(draft); }, [draft, validateCommentMaxLength]); + + return ( <> setSelection(e.nativeEvent.selection)} + onSelectionChange={(e) => { + suggestionsRef.current.onSelectionChange(e) + setSelection(e.nativeEvent.selection) + }} + setValue={setDraft} + setSelection={setSelection} + isComposerFocused={!!textInputRef.current && textInputRef.current.isFocused()} + resetKeyboardInput={resetKeyboardInput} + listHeight={listHeight} + suggestionsRef={suggestionsRef} + updateDraft={updateDraft} + measureParentContainer={measureContainer} /> diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index dba8ef2e11d0..4388e83ee728 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -136,6 +136,7 @@ function ReportActionsList({ loadOlderChats, onLayout, isComposerFullSize, + listHeight }) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -417,6 +418,7 @@ function ReportActionsList({ mostRecentIOUReportActionID={mostRecentIOUReportActionID} shouldHideThreadDividerLine={shouldHideThreadDividerLine} shouldDisplayNewMarker={shouldDisplayNewMarker(reportAction, index)} + listHeight={listHeight} /> ), [report, linkedReportActionID, sortedReportActions, mostRecentIOUReportActionID, shouldHideThreadDividerLine, shouldDisplayNewMarker], diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js index a9ae2b4c73b9..6ebbe22c76f0 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.js +++ b/src/pages/home/report/ReportActionsListItemRenderer.js @@ -49,6 +49,7 @@ function ReportActionsListItemRenderer({ shouldHideThreadDividerLine, shouldDisplayNewMarker, linkedReportActionID, + listHeight }) { const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && @@ -62,6 +63,7 @@ function ReportActionsListItemRenderer({ parentReportID={`${report.parentReportID}`} shouldDisplayNewMarker={shouldDisplayNewMarker} index={index} + listHeight={listHeight} /> ) : ( ); } diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 2758437a3962..490b86ada468 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -264,6 +264,7 @@ function ReportActionsView(props) { isLoadingOlderReportActions={props.isLoadingOlderReportActions} isLoadingNewerReportActions={props.isLoadingNewerReportActions} policy={props.policy} + listHeight={props.listHeight} /> From 5e5adb85b0a05d0d57d4fc3cbc7521d59edc0b59 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 24 Jan 2024 16:17:03 +0700 Subject: [PATCH 005/961] fix lint --- src/components/Image/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index edea86725ae7..1c7b239f6acc 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -1,12 +1,12 @@ import {Image as ImageComponent} from 'expo-image'; import lodashGet from 'lodash/get'; import React, {useEffect, useMemo, useState} from 'react'; +import {Image as RNImage} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; -import {Image as RNImage} from 'react-native'; const dimensionsCache = new Map(); From 2e260e62b64f2c0d8601b006a59037b9fbd12fcc Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 24 Jan 2024 17:09:22 +0700 Subject: [PATCH 006/961] fix lint --- src/components/Image/index.native.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index 1c7b239f6acc..f221a3a15019 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -41,7 +41,7 @@ function Image(props) { return {uri: `${source.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; } return source; - }, [source, isAuthTokenRequired]); + }, [source, isAuthTokenRequired, session]); useEffect(() => { if (props.onLoad == null) { @@ -54,7 +54,7 @@ function Image(props) { setAspectRatio(height ? width / height : 'auto'); } }); - }, [props.onLoad, newSource]); + }, [props.onLoad, newSource, props]); return ( Date: Thu, 25 Jan 2024 17:56:15 +0700 Subject: [PATCH 007/961] implement suggestion for edit composer --- src/components/Composer/types.ts | 4 ++- src/pages/home/ReportScreen.js | 1 - .../ComposerWithSuggestionsEdit.tsx | 36 ++++++------------- .../home/report/ReportActionCompose/types.ts | 14 ++++++++ src/pages/home/report/ReportActionItem.js | 1 - .../report/ReportActionItemMessageEdit.tsx | 24 ++++++------- src/pages/home/report/ReportActionsList.js | 2 -- .../report/ReportActionsListItemRenderer.js | 3 -- src/pages/home/report/ReportActionsView.js | 1 - 9 files changed, 38 insertions(+), 48 deletions(-) create mode 100644 src/pages/home/report/ReportActionCompose/types.ts diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts index d8d88970ea78..6d4689ab8150 100644 --- a/src/components/Composer/types.ts +++ b/src/components/Composer/types.ts @@ -1,4 +1,4 @@ -import type {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, TextInputKeyPressEventData, TextInputSelectionChangeEventData, TextStyle} from 'react-native'; +import type {LayoutChangeEvent, NativeSyntheticEvent, StyleProp, TextInputFocusEventData, TextInputKeyPressEventData, TextInputSelectionChangeEventData, TextStyle} from 'react-native'; type TextSelection = { start: number; @@ -80,6 +80,8 @@ type ComposerProps = { onBlur?: (event: NativeSyntheticEvent) => void; + onLayout?: (event: LayoutChangeEvent) => void; + /** Should make the input only scroll inside the element avoid scroll out to parent */ shouldContainScroll?: boolean; }; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index e5df63f0ff04..4d043f12351e 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -528,7 +528,6 @@ function ReportScreen({ isLoadingOlderReportActions={reportMetadata.isLoadingOlderReportActions} isComposerFullSize={isComposerFullSize} policy={policy} - listHeight={listHeight} /> )} diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 7acdb0c864ab..efc1370f7e23 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,12 +1,11 @@ -import { ComposerProps } from "@components/Composer/types"; +import type { ComposerProps } from "@components/Composer/types"; import Composer from "@components/Composer"; -import React, { Dispatch, ForwardedRef, MutableRefObject, SetStateAction, useRef, useState } from 'react'; -import { AnimatedProps } from "react-native-reanimated"; -import { TextInputProps } from "react-native"; -import Suggestions from '../Suggestions'; -import lodashGet from 'lodash/get'; -import * as SuggestionUtils from '@libs/SuggestionUtils'; -import useWindowDimensions from "@hooks/useWindowDimensions"; +import type { Dispatch, ForwardedRef, MutableRefObject, SetStateAction} from 'react'; +import React, { useState } from 'react'; +import type { AnimatedProps } from "react-native-reanimated"; +import type {TextInputProps} from "react-native"; +import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; +import type { SuggestionsRef } from "@pages/home/report/ReportActionCompose/types"; type ComposerWithSuggestionsEditProps = { setValue: Dispatch>, @@ -16,8 +15,7 @@ type ComposerWithSuggestionsEditProps = { }>>, resetKeyboardInput: () => void, isComposerFocused: boolean, - listHeight: number, - suggestionsRef: MutableRefObject, + suggestionsRef: MutableRefObject, updateDraft: (newValue: string) => void, measureParentContainer: (callback: () => void) => void } @@ -29,7 +27,6 @@ function ComposerWithSuggestionsEdit( maxLines = -1, onKeyPress = () => {}, style, - numberOfLines: numberOfLinesProp = 0, onSelectionChange = () => {}, selection = { start: 0, @@ -43,26 +40,14 @@ function ComposerWithSuggestionsEdit( resetKeyboardInput = () => {}, isComposerFocused, suggestionsRef, - listHeight, updateDraft, measureParentContainer, id = undefined }: ComposerWithSuggestionsEditProps & ComposerProps, ref: ForwardedRef>>, ) { - const {isSmallScreenWidth} = useWindowDimensions(); - - const suggestions = lodashGet(suggestionsRef, 'current.getSuggestions', () => [])(); - const [composerHeight, setComposerHeight] = useState(0); - const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions.length); - - console.log(hasEnoughSpaceForLargeSuggestion); - - const isAutoSuggestionPickerLarge = !isSmallScreenWidth || (isSmallScreenWidth && hasEnoughSpaceForLargeSuggestion); - - return ( <> - ) } diff --git a/src/pages/home/report/ReportActionCompose/types.ts b/src/pages/home/report/ReportActionCompose/types.ts new file mode 100644 index 000000000000..8630189d45ec --- /dev/null +++ b/src/pages/home/report/ReportActionCompose/types.ts @@ -0,0 +1,14 @@ +import type { NativeSyntheticEvent, TextInputKeyPressEventData, TextInputSelectionChangeEventData } from "react-native"; + +type SuggestionsRef = { + getSuggestions: () => void; + resetSuggestions: () => void; + triggerHotkeyActions: (event: NativeSyntheticEvent | KeyboardEvent) => void; + onSelectionChange: (event: NativeSyntheticEvent) => void; + updateShouldShowSuggestionMenuToFalse: () => void; + setShouldBlockSuggestionCalc: () => void; +} + + +// eslint-disable-next-line import/prefer-default-export +export type {SuggestionsRef}; \ No newline at end of file diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index f3a02cae06dd..80fb341a2cf8 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -514,7 +514,6 @@ function ReportActionItem(props) { shouldDisableEmojiPicker={ (ReportUtils.chatIncludesConcierge(props.report) && User.isBlockedFromConcierge(props.blockedFromConcierge)) || ReportUtils.isArchivedRoom(props.report) } - listHeight={props.listHeight} /> )} diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index ba247fc4be6d..7d13bd6335ca 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -6,7 +6,6 @@ import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} fr import {InteractionManager, Keyboard, NativeModules, View, findNodeHandle} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; -import Composer from '@components/Composer'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import ExceededCommentLength from '@components/ExceededCommentLength'; import Icon from '@components/Icon'; @@ -39,8 +38,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; -import convertToLTRForComposer from '@libs/convertToLTRForComposer'; -import getPlatform from '@libs/getPlatform'; +import type { SuggestionsRef } from './ReportActionCompose/types'; const {RNTextInputReset} = NativeModules; @@ -67,8 +65,6 @@ type ReportActionItemMessageEditProps = { /** Stores user's preferred skin tone */ preferredSkinTone?: number; - - listHeight: number; }; // native ids @@ -76,9 +72,8 @@ const emojiButtonID = 'emojiButton'; const messageEditInput = 'messageEditInput'; const isMobileSafari = Browser.isMobileSafari(); -const isIOSNative = getPlatform() === CONST.PLATFORM.IOS; function ReportActionItemMessageEdit( - {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, listHeight}: ReportActionItemMessageEditProps, + {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps, forwardedRef: ForwardedRef, ) { const theme = useTheme(); @@ -131,8 +126,8 @@ function ReportActionItemMessageEdit( const isFocusedRef = useRef(false); const insertedEmojis = useRef([]); const draftRef = useRef(draft); - const suggestionsRef = useRef(); - const containerRef = useRef(null); + const suggestionsRef = useRef(); + const containerRef = useRef(null); useEffect(() => { if (ReportActionsUtils.isDeletedAction(action) || (action.message && draftMessage === action.message[0].html)) { @@ -366,7 +361,9 @@ function ReportActionItemMessageEdit( */ const triggerSaveOrCancel = useCallback( (e: NativeSyntheticEvent | KeyboardEvent) => { - suggestionsRef.current.triggerHotkeyActions(e); + if (suggestionsRef.current) { + suggestionsRef.current.triggerHotkeyActions(e); + } if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { return; @@ -391,7 +388,7 @@ function ReportActionItemMessageEdit( }, [textInputRef]); const measureContainer = useCallback( - (callback) => { + (callback: () => void) => { if (!containerRef.current) { return; } @@ -488,14 +485,15 @@ function ReportActionItemMessageEdit( }} selection={selection} onSelectionChange={(e) => { - suggestionsRef.current.onSelectionChange(e) + if (suggestionsRef.current) { + suggestionsRef.current.onSelectionChange(e) + } setSelection(e.nativeEvent.selection) }} setValue={setDraft} setSelection={setSelection} isComposerFocused={!!textInputRef.current && textInputRef.current.isFocused()} resetKeyboardInput={resetKeyboardInput} - listHeight={listHeight} suggestionsRef={suggestionsRef} updateDraft={updateDraft} measureParentContainer={measureContainer} diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 322d9277af3f..ce8dcb10ef5f 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -136,7 +136,6 @@ function ReportActionsList({ loadOlderChats, onLayout, isComposerFullSize, - listHeight }) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -461,7 +460,6 @@ function ReportActionsList({ mostRecentIOUReportActionID={mostRecentIOUReportActionID} shouldHideThreadDividerLine={shouldHideThreadDividerLine} shouldDisplayNewMarker={shouldDisplayNewMarker(reportAction, index)} - listHeight={listHeight} /> ), [report, linkedReportActionID, sortedReportActions, mostRecentIOUReportActionID, shouldHideThreadDividerLine, shouldDisplayNewMarker], diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js index 6ebbe22c76f0..a9ae2b4c73b9 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.js +++ b/src/pages/home/report/ReportActionsListItemRenderer.js @@ -49,7 +49,6 @@ function ReportActionsListItemRenderer({ shouldHideThreadDividerLine, shouldDisplayNewMarker, linkedReportActionID, - listHeight }) { const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && @@ -63,7 +62,6 @@ function ReportActionsListItemRenderer({ parentReportID={`${report.parentReportID}`} shouldDisplayNewMarker={shouldDisplayNewMarker} index={index} - listHeight={listHeight} /> ) : ( ); } diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 12179b211e6c..4843a29dde60 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -263,7 +263,6 @@ function ReportActionsView(props) { isLoadingOlderReportActions={props.isLoadingOlderReportActions} isLoadingNewerReportActions={props.isLoadingNewerReportActions} policy={props.policy} - listHeight={props.listHeight} /> From 6939350d98ef2bb731b1973febd417b365a6fffb Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 11:35:09 +0700 Subject: [PATCH 008/961] fix crash bug --- src/components/Image/index.js | 4 ++-- src/components/Image/index.native.js | 26 ++++---------------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 58b7ec3383a4..f4962ad9b324 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -9,7 +9,7 @@ import RESIZE_MODES from './resizeModes'; function Image(props) { const {source: propsSource, isAuthTokenRequired, onLoad, session} = props; - const [aspectRatio, setAspectRatio] = useState(); + const [aspectRatio, setAspectRatio] = useState(null); /** * Check if the image source is a URL - if so the `encryptedAuthToken` is appended * to the source. @@ -54,7 +54,7 @@ function Image(props) { // eslint-disable-next-line react/jsx-props-no-spreading {...forwardedProps} source={source} - style={[forwardedProps.style, aspectRatio !== undefined && {aspectRatio, height: 'auto'}, props.objectPositionTop && !aspectRatio && {opacity: 0}]} + style={[forwardedProps.style, !!aspectRatio && {aspectRatio, height: 'auto'}, props.objectPositionTop && !aspectRatio && {opacity: 0}]} /> ); } diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index f221a3a15019..7da72c34a012 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -35,27 +35,6 @@ function Image(props) { }; } - const newSource = useMemo(() => { - if (isAuthTokenRequired) { - const authToken = lodashGet(session, 'encryptedAuthToken', null); - return {uri: `${source.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; - } - return source; - }, [source, isAuthTokenRequired, session]); - - useEffect(() => { - if (props.onLoad == null) { - return; - } - RNImage.getSize(newSource.uri, (width, height) => { - props.onLoad({nativeEvent: {width, height}}); - - if (props.objectPositionTop) { - setAspectRatio(height ? width / height : 'auto'); - } - }); - }, [props.onLoad, newSource, props]); - return ( ); } From 02732408714a4be850731bb3eb86f44960d58dbf Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 15:51:50 +0700 Subject: [PATCH 009/961] display suggestion below when composer at the top of the screen --- .../AutoCompleteSuggestions/index.tsx | 9 +++++-- .../AutoCompleteSuggestions/types.ts | 2 +- src/libs/SuggestionUtils.ts | 24 ++++++++++++++++++- src/styles/utils/index.ts | 3 ++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index baca4011a177..01aa6ea1fc17 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -6,6 +6,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; +import { measureHeightOfSuggestioContainer } from '@libs/SuggestionUtils'; /** * On the mobile-web platform, when long-pressing on auto-complete suggestions, @@ -18,6 +19,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} const StyleUtils = useStyleUtils(); const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); + const suggestionContainerHeight = measureHeightOfSuggestioContainer(props.suggestions.length, props.isSuggestionPickerLarge); const [{width, left, bottom}, setContainerState] = React.useState({ width: 0, left: 0, @@ -41,9 +43,12 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} if (!measureParentContainer) { return; } - measureParentContainer((x, y, w) => setContainerState({left: x, bottom: windowHeight - y, width: w})); - }, [measureParentContainer, windowHeight, windowWidth]); + measureParentContainer((x, y, w, h) => { + const currenBottom = y < suggestionContainerHeight ? windowHeight - y - suggestionContainerHeight - h : windowHeight - y; + setContainerState({left: x, bottom: currenBottom, width: w}) + }); + }, [measureParentContainer, windowHeight, windowWidth]); const componentToRender = ( // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index 61d614dcf2e4..20477af2e365 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -1,6 +1,6 @@ import type {ReactElement} from 'react'; -type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; +type MeasureParentContainerCallback = (x: number, y: number, width: number, height: number) => void; type RenderSuggestionMenuItemProps = { item: TSuggestion; diff --git a/src/libs/SuggestionUtils.ts b/src/libs/SuggestionUtils.ts index 96379ce49ef3..e920678b0ecc 100644 --- a/src/libs/SuggestionUtils.ts +++ b/src/libs/SuggestionUtils.ts @@ -20,4 +20,26 @@ function hasEnoughSpaceForLargeSuggestionMenu(listHeight: number, composerHeight return availableHeight > menuHeight; } -export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu}; +const measureHeightOfSuggestioContainer = (numRows: number, isSuggestionPickerLarge: boolean): number => { + const borderAndPadding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + 2; + let suggestionHeight = 0; + + if (isSuggestionPickerLarge) { + if (numRows > CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER) { + // On large screens, if there are more than 5 suggestions, we display a scrollable window with a height of 5 items, indicating that there are more items available + suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + } else { + suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + } + } else { + if (numRows > 2) { + // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible + suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + } else { + suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + } + } + return suggestionHeight + borderAndPadding; +}; + +export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestioContainer}; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index cdefc8bc1c91..289e3430ed40 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -782,7 +782,7 @@ type GetBaseAutoCompleteSuggestionContainerStyleParams = { */ function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: GetBaseAutoCompleteSuggestionContainerStyleParams): ViewStyle { return { - ...positioning.pFixed, + // ...positioning.pFixed, bottom, left, width, @@ -797,6 +797,7 @@ function getAutoCompleteSuggestionContainerStyle(itemsHeight: number): ViewStyle const borderWidth = 2; const height = itemsHeight + 2 * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING; + console.log(height); // The suggester is positioned absolutely within the component that includes the input and RecipientLocalTime view (for non-expanded mode only). To position it correctly, // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. From 31bf412c900678781408b37185939d8bf86de4c1 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 16:52:11 +0700 Subject: [PATCH 010/961] fix edge case --- .../AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx | 3 ++- src/components/AutoCompleteSuggestions/index.tsx | 3 +++ src/components/AutoCompleteSuggestions/types.ts | 3 +++ src/styles/utils/index.ts | 5 ++--- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index 5da9c6981603..f5e6cdc52574 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -39,6 +39,7 @@ function BaseAutoCompleteSuggestions( suggestions, isSuggestionPickerLarge, keyExtractor, + shouldBelowParentContainer = false }: AutoCompleteSuggestionsProps, ref: ForwardedRef, ) { @@ -67,7 +68,7 @@ function BaseAutoCompleteSuggestions( ); const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length; - const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value)); + const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBelowParentContainer)); const estimatedListSize = useMemo( () => ({ height: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length, diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index 01aa6ea1fc17..c6911366f102 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -25,6 +25,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} left: 0, bottom: 0, }); + const [shouldBelowContainer, setShouldBelowContainer] = React.useState(false); React.useEffect(() => { const container = containerRef.current; if (!container) { @@ -46,6 +47,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} measureParentContainer((x, y, w, h) => { const currenBottom = y < suggestionContainerHeight ? windowHeight - y - suggestionContainerHeight - h : windowHeight - y; + setShouldBelowContainer(y < suggestionContainerHeight); setContainerState({left: x, bottom: currenBottom, width: w}) }); }, [measureParentContainer, windowHeight, windowWidth]); @@ -53,6 +55,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} // eslint-disable-next-line react/jsx-props-no-spreading {...props} + shouldBelowParentContainer={shouldBelowContainer} ref={containerRef} /> ); diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index 20477af2e365..2f6f07d51be1 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -33,6 +33,9 @@ type AutoCompleteSuggestionsProps = { /** Meaures the parent container's position and dimensions. */ measureParentContainer?: (callback: MeasureParentContainerCallback) => void; + + /** Whether suggestion should be displayed below the parent container or not */ + shouldBelowParentContainer?: boolean }; export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps}; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 289e3430ed40..d8a616fa34c2 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -792,18 +792,17 @@ function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: GetB /** * Gets the correct position for auto complete suggestion container */ -function getAutoCompleteSuggestionContainerStyle(itemsHeight: number): ViewStyle { +function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBelowParentContainer: boolean): ViewStyle { 'worklet'; const borderWidth = 2; const height = itemsHeight + 2 * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING; - console.log(height); // The suggester is positioned absolutely within the component that includes the input and RecipientLocalTime view (for non-expanded mode only). To position it correctly, // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. return { overflow: 'hidden', - top: -(height + CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + borderWidth), + top: -(height + (shouldBelowParentContainer ? -2 : 1) * (CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + borderWidth)), height, minHeight: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, }; From 7c9c40ff933a7d2d47a39a5f391101863551aa00 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 17:01:55 +0700 Subject: [PATCH 011/961] fix lint --- src/components/Image/index.native.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index 7da72c34a012..862807b909bb 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -1,7 +1,6 @@ import {Image as ImageComponent} from 'expo-image'; import lodashGet from 'lodash/get'; -import React, {useEffect, useMemo, useState} from 'react'; -import {Image as RNImage} from 'react-native'; +import React, { useState } from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; From a153e8a46181c676adfd995ce00759e7b4e6b5cb Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 17:41:00 +0700 Subject: [PATCH 012/961] fix lint --- src/components/Image/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index 862807b909bb..2791ea40925d 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -1,6 +1,6 @@ import {Image as ImageComponent} from 'expo-image'; import lodashGet from 'lodash/get'; -import React, { useState } from 'react'; +import React, {useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; From ee43388d477ef88da7414c15c69ffb1b34221325 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 29 Jan 2024 17:31:42 +0700 Subject: [PATCH 013/961] create global ref --- .../BaseAutoCompleteSuggestions.tsx | 2 +- .../AutoCompleteSuggestions/index.tsx | 7 +-- .../AutoCompleteSuggestions/types.ts | 2 +- src/components/EmojiSuggestions.tsx | 2 +- src/libs/SuggestionUtils.ts | 10 ++-- src/libs/actions/SuggestionsAction.ts | 50 +++++++++++++++++++ .../ComposerWithSuggestionsEdit.tsx | 45 +++++++++-------- .../home/report/ReportActionCompose/types.ts | 14 ------ .../report/ReportActionItemMessageEdit.tsx | 28 ++++------- src/pages/home/report/ReportActionsView.js | 2 + 10 files changed, 97 insertions(+), 65 deletions(-) create mode 100644 src/libs/actions/SuggestionsAction.ts delete mode 100644 src/pages/home/report/ReportActionCompose/types.ts diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index f5e6cdc52574..2b7750f732f2 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -39,7 +39,7 @@ function BaseAutoCompleteSuggestions( suggestions, isSuggestionPickerLarge, keyExtractor, - shouldBelowParentContainer = false + shouldBelowParentContainer = false, }: AutoCompleteSuggestionsProps, ref: ForwardedRef, ) { diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index c6911366f102..bda8e0aa7711 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -4,9 +4,9 @@ import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {measureHeightOfSuggestioContainer} from '@libs/SuggestionUtils'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; -import { measureHeightOfSuggestioContainer } from '@libs/SuggestionUtils'; /** * On the mobile-web platform, when long-pressing on auto-complete suggestions, @@ -48,9 +48,10 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} measureParentContainer((x, y, w, h) => { const currenBottom = y < suggestionContainerHeight ? windowHeight - y - suggestionContainerHeight - h : windowHeight - y; setShouldBelowContainer(y < suggestionContainerHeight); - setContainerState({left: x, bottom: currenBottom, width: w}) + setContainerState({left: x, bottom: currenBottom, width: w}); }); - }, [measureParentContainer, windowHeight, windowWidth]); + }, [measureParentContainer, windowHeight, windowWidth, suggestionContainerHeight]); + const componentToRender = ( // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index 2f6f07d51be1..b837901026b2 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -35,7 +35,7 @@ type AutoCompleteSuggestionsProps = { measureParentContainer?: (callback: MeasureParentContainerCallback) => void; /** Whether suggestion should be displayed below the parent container or not */ - shouldBelowParentContainer?: boolean + shouldBelowParentContainer?: boolean; }; export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps}; diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx index 1c0306741048..c95288f43164 100644 --- a/src/components/EmojiSuggestions.tsx +++ b/src/components/EmojiSuggestions.tsx @@ -9,7 +9,7 @@ import getStyledTextArray from '@libs/GetStyledTextArray'; import AutoCompleteSuggestions from './AutoCompleteSuggestions'; import Text from './Text'; -type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; +type MeasureParentContainerCallback = (x: number, y: number, width: number, height: number) => void; type EmojiSuggestionsProps = { /** The index of the highlighted emoji */ diff --git a/src/libs/SuggestionUtils.ts b/src/libs/SuggestionUtils.ts index e920678b0ecc..00039592ba77 100644 --- a/src/libs/SuggestionUtils.ts +++ b/src/libs/SuggestionUtils.ts @@ -31,13 +31,11 @@ const measureHeightOfSuggestioContainer = (numRows: number, isSuggestionPickerLa } else { suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } + } else if (numRows > 2) { + // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible + suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } else { - if (numRows > 2) { - // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible - suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; - } else { - suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; - } + suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } return suggestionHeight + borderAndPadding; }; diff --git a/src/libs/actions/SuggestionsAction.ts b/src/libs/actions/SuggestionsAction.ts new file mode 100644 index 000000000000..9fecd18f7d10 --- /dev/null +++ b/src/libs/actions/SuggestionsAction.ts @@ -0,0 +1,50 @@ +import React from 'react'; +import type {NativeSyntheticEvent, TextInputKeyPressEventData, TextInputSelectionChangeEventData} from 'react-native'; + +type SuggestionsRef = { + getSuggestions: () => void; + resetSuggestions: () => void; + triggerHotkeyActions: (event: NativeSyntheticEvent | KeyboardEvent) => boolean; + onSelectionChange: (event: NativeSyntheticEvent) => void; + updateShouldShowSuggestionMenuToFalse: () => void; + setShouldBlockSuggestionCalc: () => void; +}; + +const suggestionsRef = React.createRef(); + +function resetSuggestions() { + if (!suggestionsRef.current) { + return; + } + + suggestionsRef.current.resetSuggestions(); +} + +function triggerHotkeyActions(event: NativeSyntheticEvent | KeyboardEvent): boolean { + if (!suggestionsRef.current) { + return false; + } + + return suggestionsRef.current.triggerHotkeyActions(event); +} + +function updateShouldShowSuggestionMenuToFalse() { + if (!suggestionsRef.current) { + return; + } + + suggestionsRef.current.updateShouldShowSuggestionMenuToFalse(); +} + +function onSelectionChange(event: NativeSyntheticEvent) { + if (!suggestionsRef.current) { + return; + } + + suggestionsRef.current.onSelectionChange(event); +} + +export {suggestionsRef, resetSuggestions, triggerHotkeyActions, onSelectionChange, updateShouldShowSuggestionMenuToFalse}; + +// eslint-disable-next-line import/prefer-default-export +export type {SuggestionsRef}; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index efc1370f7e23..927d160a8b7f 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,25 +1,26 @@ -import type { ComposerProps } from "@components/Composer/types"; -import Composer from "@components/Composer"; -import type { Dispatch, ForwardedRef, MutableRefObject, SetStateAction} from 'react'; -import React, { useState } from 'react'; -import type { AnimatedProps } from "react-native-reanimated"; -import type {TextInputProps} from "react-native"; +import type {Dispatch, ForwardedRef, RefObject, SetStateAction} from 'react'; +import React, {useState} from 'react'; +import type {TextInputProps} from 'react-native'; +import type {AnimatedProps} from 'react-native-reanimated'; +import Composer from '@components/Composer'; +import type {ComposerProps} from '@components/Composer/types'; +import type {SuggestionsRef} from '@libs/actions/SuggestionsAction'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; -import type { SuggestionsRef } from "@pages/home/report/ReportActionCompose/types"; type ComposerWithSuggestionsEditProps = { - setValue: Dispatch>, - setSelection: Dispatch>, - resetKeyboardInput: () => void, - isComposerFocused: boolean, - suggestionsRef: MutableRefObject, - updateDraft: (newValue: string) => void, - measureParentContainer: (callback: () => void) => void -} - + setValue: Dispatch>; + setSelection: Dispatch< + SetStateAction<{ + start: number; + end: number; + }> + >; + resetKeyboardInput: () => void; + isComposerFocused: boolean; + suggestionsRef: RefObject; + updateDraft: (newValue: string) => void; + measureParentContainer: (callback: () => void) => void; +}; function ComposerWithSuggestionsEdit( { @@ -42,7 +43,7 @@ function ComposerWithSuggestionsEdit( suggestionsRef, updateDraft, measureParentContainer, - id = undefined + id = undefined, }: ComposerWithSuggestionsEditProps & ComposerProps, ref: ForwardedRef>>, ) { @@ -88,7 +89,7 @@ function ComposerWithSuggestionsEdit( resetKeyboardInput={resetKeyboardInput} /> - ) + ); } -export default React.forwardRef(ComposerWithSuggestionsEdit); \ No newline at end of file +export default React.forwardRef(ComposerWithSuggestionsEdit); diff --git a/src/pages/home/report/ReportActionCompose/types.ts b/src/pages/home/report/ReportActionCompose/types.ts deleted file mode 100644 index 8630189d45ec..000000000000 --- a/src/pages/home/report/ReportActionCompose/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { NativeSyntheticEvent, TextInputKeyPressEventData, TextInputSelectionChangeEventData } from "react-native"; - -type SuggestionsRef = { - getSuggestions: () => void; - resetSuggestions: () => void; - triggerHotkeyActions: (event: NativeSyntheticEvent | KeyboardEvent) => void; - onSelectionChange: (event: NativeSyntheticEvent) => void; - updateShouldShowSuggestionMenuToFalse: () => void; - setShouldBlockSuggestionCalc: () => void; -} - - -// eslint-disable-next-line import/prefer-default-export -export type {SuggestionsRef}; \ No newline at end of file diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 7d13bd6335ca..a6535b83d0f7 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -3,7 +3,7 @@ import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {InteractionManager, Keyboard, NativeModules, View, findNodeHandle} from 'react-native'; +import {findNodeHandle, InteractionManager, Keyboard, NativeModules, View} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; @@ -20,6 +20,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as SuggestionsAction from '@libs/actions/SuggestionsAction'; import * as Browser from '@libs/Browser'; import * as ComposerUtils from '@libs/ComposerUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; @@ -38,11 +39,10 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; -import type { SuggestionsRef } from './ReportActionCompose/types'; +import { PortalHost } from '@gorhom/portal'; const {RNTextInputReset} = NativeModules; - type ReportActionItemMessageEditProps = { /** All the data of the action */ action: OnyxTypes.ReportAction; @@ -126,7 +126,6 @@ function ReportActionItemMessageEdit( const isFocusedRef = useRef(false); const insertedEmojis = useRef([]); const draftRef = useRef(draft); - const suggestionsRef = useRef(); const containerRef = useRef(null); useEffect(() => { @@ -182,7 +181,7 @@ function ReportActionItemMessageEdit( // and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style), // so we need to ensure that it is only updated after focus. if (isMobileSafari) { - setDraft((prevDraft) => { + setDraft((prevDraft: string) => { setSelection({ start: prevDraft.length, end: prevDraft.length, @@ -257,9 +256,7 @@ function ReportActionItemMessageEdit( if (emojis?.length > 0) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (newEmojis?.length > 0) { - if (suggestionsRef.current) { - suggestionsRef.current.resetSuggestions(); - } + SuggestionsAction.resetSuggestions(); insertedEmojis.current = [...insertedEmojis.current, ...newEmojis]; debouncedUpdateFrequentlyUsedEmojis(); } @@ -361,8 +358,8 @@ function ReportActionItemMessageEdit( */ const triggerSaveOrCancel = useCallback( (e: NativeSyntheticEvent | KeyboardEvent) => { - if (suggestionsRef.current) { - suggestionsRef.current.triggerHotkeyActions(e); + if (SuggestionsAction.triggerHotkeyActions(e)) { + return; } if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { @@ -408,8 +405,6 @@ function ReportActionItemMessageEdit( validateCommentMaxLength(draft); }, [draft, validateCommentMaxLength]); - - return ( <> @@ -423,6 +418,7 @@ function ReportActionItemMessageEdit( hasExceededMaxCommentLength && styles.borderColorDanger, ]} > + { - if (suggestionsRef.current) { - suggestionsRef.current.onSelectionChange(e) - } - setSelection(e.nativeEvent.selection) + SuggestionsAction.onSelectionChange(e); + setSelection(e.nativeEvent.selection); }} setValue={setDraft} setSelection={setSelection} isComposerFocused={!!textInputRef.current && textInputRef.current.isFocused()} resetKeyboardInput={resetKeyboardInput} - suggestionsRef={suggestionsRef} + suggestionsRef={SuggestionsAction.suggestionsRef} updateDraft={updateDraft} measureParentContainer={measureContainer} /> diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 4843a29dde60..1c0719645d2f 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -11,6 +11,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useInitialValue from '@hooks/useInitialValue'; import usePrevious from '@hooks/usePrevious'; +import * as SuggestionsAction from '@libs/actions/SuggestionsAction'; import compose from '@libs/compose'; import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; import Performance from '@libs/Performance'; @@ -255,6 +256,7 @@ function ReportActionsView(props) { Date: Tue, 30 Jan 2024 16:29:37 +0700 Subject: [PATCH 014/961] fix the case wide image --- src/components/Image/index.js | 4 ++++ src/components/Image/index.native.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/components/Image/index.js b/src/components/Image/index.js index f4962ad9b324..848e64f60350 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -41,6 +41,10 @@ function Image(props) { onLoad({nativeEvent: {width, height}}); if (props.objectPositionTop) { + if (width > height) { + setAspectRatio(1); + return; + } setAspectRatio(height ? width / height : 'auto'); } }); diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index 2791ea40925d..b6d5f5b2dcda 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -46,6 +46,10 @@ function Image(props) { props.onLoad({nativeEvent: {width, height}}); } if (props.objectPositionTop) { + if (width > height) { + setAspectRatio(1); + return; + } setAspectRatio(height ? width / height : 'auto'); } }} From 00ccc9bd4e75c8a480313f11ecf986725eb6a83e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 30 Jan 2024 16:34:02 +0700 Subject: [PATCH 015/961] add comment prop --- src/components/Image/imagePropTypes.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Image/imagePropTypes.js b/src/components/Image/imagePropTypes.js index 78bd48ba47ec..cc4c36e32387 100644 --- a/src/components/Image/imagePropTypes.js +++ b/src/components/Image/imagePropTypes.js @@ -34,6 +34,9 @@ const imagePropTypes = { /** Currently logged in user authToken */ authToken: PropTypes.string, }), + + /** Whether we should show the top of the image */ + objectPositionTop: PropTypes.string }; const defaultProps = { @@ -43,6 +46,7 @@ const defaultProps = { }, isAuthTokenRequired: false, resizeMode: RESIZE_MODES.cover, + objectPositionTop: false, onLoadStart: () => {}, onLoadEnd: () => {}, onLoad: () => {}, From c761e1c17e8795c514904e167f23e42c9fa7aa27 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 30 Jan 2024 18:08:45 +0700 Subject: [PATCH 016/961] merge main --- src/components/Image/imagePropTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Image/imagePropTypes.js b/src/components/Image/imagePropTypes.js index cc4c36e32387..5d28b1a9f3f4 100644 --- a/src/components/Image/imagePropTypes.js +++ b/src/components/Image/imagePropTypes.js @@ -36,7 +36,7 @@ const imagePropTypes = { }), /** Whether we should show the top of the image */ - objectPositionTop: PropTypes.string + objectPositionTop: PropTypes.string, }; const defaultProps = { From d09ee205845c7b47eb4b6cdc49c803dc8be43918 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 2 Feb 2024 10:41:02 +0700 Subject: [PATCH 017/961] centralize logic --- src/components/Image/BaseImage.native.tsx | 16 +++------------- src/components/Image/BaseImage.tsx | 16 +++------------- src/components/Image/index.js | 22 ++++++++++++++++++++-- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/components/Image/BaseImage.native.tsx b/src/components/Image/BaseImage.native.tsx index e912bdda9eba..c517efd04515 100644 --- a/src/components/Image/BaseImage.native.tsx +++ b/src/components/Image/BaseImage.native.tsx @@ -1,10 +1,9 @@ import {Image as ExpoImage} from 'expo-image'; import type {ImageProps as ExpoImageProps, ImageLoadEventData} from 'expo-image'; -import {useCallback, useState} from 'react'; +import {useCallback} from 'react'; import type {BaseImageProps} from './types'; -function BaseImage({onLoad, objectPositionTop = false, style, ...props}: ExpoImageProps & BaseImageProps) { - const [aspectRatio, setAspectRatio] = useState(null); +function BaseImage({onLoad, ...props}: ExpoImageProps & BaseImageProps) { const imageLoadedSuccessfully = useCallback( (event: ImageLoadEventData) => { if (!onLoad) { @@ -14,23 +13,14 @@ function BaseImage({onLoad, objectPositionTop = false, style, ...props}: ExpoIma // We override `onLoad`, so both web and native have the same signature const {width, height} = event.source; onLoad({nativeEvent: {width, height}}); - - if (objectPositionTop) { - if (width > height) { - setAspectRatio(1); - return; - } - setAspectRatio(height ? width / height : 'auto'); - } }, - [onLoad, objectPositionTop], + [onLoad], ); return ( diff --git a/src/components/Image/BaseImage.tsx b/src/components/Image/BaseImage.tsx index 9a3f4668647e..ebdd76840267 100644 --- a/src/components/Image/BaseImage.tsx +++ b/src/components/Image/BaseImage.tsx @@ -1,10 +1,9 @@ -import React, {useCallback, useState} from 'react'; +import React, {useCallback} from 'react'; import {Image as RNImage} from 'react-native'; import type {ImageLoadEventData, ImageProps as WebImageProps} from 'react-native'; import type {BaseImageProps} from './types'; -function BaseImage({onLoad, objectPositionTop = false, style, ...props}: WebImageProps & BaseImageProps) { - const [aspectRatio, setAspectRatio] = useState(null); +function BaseImage({onLoad, ...props}: WebImageProps & BaseImageProps) { const imageLoadedSuccessfully = useCallback( (event: {nativeEvent: ImageLoadEventData}) => { if (!onLoad) { @@ -14,23 +13,14 @@ function BaseImage({onLoad, objectPositionTop = false, style, ...props}: WebImag // We override `onLoad`, so both web and native have the same signature const {width, height} = event.nativeEvent.source; onLoad({nativeEvent: {width, height}}); - - if (objectPositionTop) { - if (width > height) { - setAspectRatio(1); - return; - } - setAspectRatio(height ? width / height : 'auto'); - } }, - [onLoad, objectPositionTop], + [onLoad], ); return ( diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 8cee1cf95e14..2788e85895f6 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,5 +1,5 @@ import lodashGet from 'lodash/get'; -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -7,7 +7,9 @@ import BaseImage from './BaseImage'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; -function Image({source: propsSource, isAuthTokenRequired, session, ...forwardedProps}) { +function Image({source: propsSource, isAuthTokenRequired, session, onLoad, ...forwardedProps}) { + const [aspectRatio, setAspectRatio] = useState(null); + // Update the source to include the auth token if required const source = useMemo(() => { if (typeof lodashGet(propsSource, 'uri') === 'number') { @@ -28,11 +30,27 @@ function Image({source: propsSource, isAuthTokenRequired, session, ...forwardedP // eslint-disable-next-line react-hooks/exhaustive-deps }, [propsSource, isAuthTokenRequired]); + const imageLoadedSuccessfully = useCallback((event) => { + const {width, height} = event.nativeEvent; + + onLoad(event); + + if (objectPositionTop) { + if (width > height) { + setAspectRatio(1); + return; + } + setAspectRatio(height ? width / height : 'auto'); + } + }) + return ( ); } From 7cf8fd81cf99cd3a8cfa869f33be36c271081e65 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 2 Feb 2024 10:56:55 +0700 Subject: [PATCH 018/961] fix lint --- src/components/Image/imagePropTypes.js | 2 +- src/components/Image/index.js | 29 ++++++++++++++------------ src/components/Image/types.ts | 3 --- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/components/Image/imagePropTypes.js b/src/components/Image/imagePropTypes.js index 5d28b1a9f3f4..deb7d75da5ae 100644 --- a/src/components/Image/imagePropTypes.js +++ b/src/components/Image/imagePropTypes.js @@ -36,7 +36,7 @@ const imagePropTypes = { }), /** Whether we should show the top of the image */ - objectPositionTop: PropTypes.string, + objectPositionTop: PropTypes.bool, }; const defaultProps = { diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 2788e85895f6..6851604be8b1 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,5 +1,5 @@ import lodashGet from 'lodash/get'; -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -7,8 +7,8 @@ import BaseImage from './BaseImage'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; -function Image({source: propsSource, isAuthTokenRequired, session, onLoad, ...forwardedProps}) { - const [aspectRatio, setAspectRatio] = useState(null); +function Image({source: propsSource, isAuthTokenRequired, session, onLoad, style, objectPositionTop, ...forwardedProps}) { + const [aspectRatio, setAspectRatio] = useState(null); // Update the source to include the auth token if required const source = useMemo(() => { @@ -30,19 +30,22 @@ function Image({source: propsSource, isAuthTokenRequired, session, onLoad, ...fo // eslint-disable-next-line react-hooks/exhaustive-deps }, [propsSource, isAuthTokenRequired]); - const imageLoadedSuccessfully = useCallback((event) => { - const {width, height} = event.nativeEvent; + const imageLoadedSuccessfully = useCallback( + (event) => { + const {width, height} = event.nativeEvent; - onLoad(event); + onLoad(event); - if (objectPositionTop) { - if (width > height) { - setAspectRatio(1); - return; + if (objectPositionTop) { + if (width > height) { + setAspectRatio(1); + return; + } + setAspectRatio(height ? width / height : 'auto'); } - setAspectRatio(height ? width / height : 'auto'); - } - }) + }, + [onLoad, objectPositionTop], + ); return ( void; - - /** Whether we should show the top of the image */ - objectPositionTop?: boolean; }; export type {BaseImageProps}; From 75656213396603f69e7d93ddc569cedb4ac71181 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 5 Feb 2024 20:29:51 +0530 Subject: [PATCH 019/961] Remove MoneyRequestParticipantsPage.js and copy any changes since Nov 27 into IOURequestStepParticipants.js. Signed-off-by: Krishna Gupta --- .../AppNavigator/ModalStackNavigators.tsx | 1 - src/libs/Navigation/linkingConfig/config.ts | 1 - src/libs/Navigation/types.ts | 4 - .../MoneyRequestParticipantsPage.js | 174 ------------------ 4 files changed, 180 deletions(-) delete mode 100644 src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 4606f867c3fc..a19ffb184cf1 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -95,7 +95,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/request/step/IOURequestStepWaypoint').default as React.ComponentType, [SCREENS.MONEY_REQUEST.ROOT]: () => require('../../../pages/iou/MoneyRequestSelectorPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.AMOUNT]: () => require('../../../pages/iou/steps/NewRequestAmountPage').default as React.ComponentType, - [SCREENS.MONEY_REQUEST.PARTICIPANTS]: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CONFIRMATION]: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType, [SCREENS.MONEY_REQUEST.DATE]: () => require('../../../pages/iou/MoneyRequestDatePage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index f1c9c316fe93..2bae3ec1f73f 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -400,7 +400,6 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.AMOUNT]: ROUTES.MONEY_REQUEST_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_RATE]: ROUTES.MONEY_REQUEST_STEP_TAX_RATE.route, - [SCREENS.MONEY_REQUEST.PARTICIPANTS]: ROUTES.MONEY_REQUEST_PARTICIPANTS.route, [SCREENS.MONEY_REQUEST.CONFIRMATION]: ROUTES.MONEY_REQUEST_CONFIRMATION.route, [SCREENS.MONEY_REQUEST.DATE]: ROUTES.MONEY_REQUEST_DATE.route, [SCREENS.MONEY_REQUEST.CURRENCY]: ROUTES.MONEY_REQUEST_CURRENCY.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3c4cf17853f1..c1b31cc3a864 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -195,10 +195,6 @@ type RoomInviteNavigatorParamList = { type MoneyRequestNavigatorParamList = { [SCREENS.MONEY_REQUEST.ROOT]: undefined; [SCREENS.MONEY_REQUEST.AMOUNT]: undefined; - [SCREENS.MONEY_REQUEST.PARTICIPANTS]: { - iouType: string; - reportID: string; - }; [SCREENS.MONEY_REQUEST.CONFIRMATION]: { iouType: string; reportID: string; diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js deleted file mode 100644 index 216154be9cd4..000000000000 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ /dev/null @@ -1,174 +0,0 @@ -import _ from 'lodash'; -import lodashGet from 'lodash/get'; -import lodashSize from 'lodash/size'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import transactionPropTypes from '@components/transactionPropTypes'; -import useInitialValue from '@hooks/useInitialValue'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import MoneyRequestParticipantsSelector from './MoneyRequestParticipantsSelector'; - -const propTypes = { - /** React Navigation route */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, - - /** The report ID of the IOU */ - reportID: PropTypes.string, - }), - }).isRequired, - - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, - - /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ - selectedTab: PropTypes.oneOf(_.values(CONST.TAB_REQUEST)), - - /** Transaction that stores the distance request data */ - transaction: transactionPropTypes, -}; - -const defaultProps = { - iou: iouDefaultProps, - transaction: {}, - selectedTab: undefined, -}; - -function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const prevMoneyRequestId = useRef(iou.id); - const iouType = useInitialValue(() => lodashGet(route, 'params.iouType', '')); - const reportID = useInitialValue(() => lodashGet(route, 'params.reportID', '')); - const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType, selectedTab); - const isSendRequest = iouType === CONST.IOU.TYPE.SEND; - const isScanRequest = MoneyRequestUtils.isScanRequest(selectedTab); - const isSplitRequest = iou.id === CONST.IOU.TYPE.SPLIT; - const waypoints = lodashGet(transaction, 'comment.waypoints', {}); - const validatedWaypoints = TransactionUtils.getValidWaypoints(waypoints); - const isInvalidWaypoint = lodashSize(validatedWaypoints) < 2; - const headerTitle = useMemo(() => { - if (isDistanceRequest) { - return translate('common.distance'); - } - - if (isSendRequest) { - return translate('common.send'); - } - - if (isScanRequest) { - return translate('tabSelector.scan'); - } - - if (iou.isSplitRequest) { - return translate('iou.split'); - } - - return translate('tabSelector.manual'); - }, [iou, isDistanceRequest, translate, isScanRequest, isSendRequest]); - - const navigateToConfirmationStep = (moneyRequestType) => { - IOU.setMoneyRequestId(moneyRequestType); - IOU.resetMoneyRequestCategory(); - IOU.resetMoneyRequestTag(); - Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(moneyRequestType, reportID)); - }; - - const navigateBack = useCallback((forceFallback = false) => { - Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID), forceFallback); - // eslint-disable-next-line react-hooks/exhaustive-deps -- no deps as we use only initial values - }, []); - - useEffect(() => { - const isInvalidDistanceRequest = !isDistanceRequest || isInvalidWaypoint; - - // ID in Onyx could change by initiating a new request in a separate browser tab or completing a request - if (prevMoneyRequestId.current !== iou.id) { - // The ID is cleared on completing a request. In that case, we will do nothing - if (iou.id && isInvalidDistanceRequest && !isSplitRequest) { - navigateBack(true); - } - return; - } - - // Reset the money request Onyx if the ID in Onyx does not match the ID from params - const moneyRequestId = `${iouType}${reportID}`; - const shouldReset = iou.id !== moneyRequestId && !_.isEmpty(reportID); - if (shouldReset) { - IOU.resetMoneyRequestInfo(moneyRequestId); - } - if (isInvalidDistanceRequest && ((iou.amount === 0 && !iou.receiptPath) || shouldReset)) { - navigateBack(true); - } - - return () => { - prevMoneyRequestId.current = iou.id; - }; - }, [iou.amount, iou.id, iou.receiptPath, isDistanceRequest, isSplitRequest, iouType, reportID, navigateBack, isInvalidWaypoint]); - - return ( - - {({safeAreaPaddingBottomStyle}) => ( - - - navigateToConfirmationStep(iouType)} - navigateToSplit={() => navigateToConfirmationStep(CONST.IOU.TYPE.SPLIT)} - safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} - iouType={iouType} - isDistanceRequest={isDistanceRequest} - isScanRequest={isScanRequest} - /> - - )} - - ); -} - -MoneyRequestParticipantsPage.displayName = 'MoneyRequestParticipantsPage'; -MoneyRequestParticipantsPage.propTypes = propTypes; -MoneyRequestParticipantsPage.defaultProps = defaultProps; - -export default compose( - withOnyx({ - iou: { - key: ONYXKEYS.IOU, - }, - selectedTab: { - key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({iou}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(iou, 'transactionID', 0)}`, - }, - }), -)(MoneyRequestParticipantsPage); From 86190f61d6f9f090ea0714939a15685c57cdc246 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 6 Feb 2024 01:01:01 +0700 Subject: [PATCH 020/961] revert hard code --- .../ReportActionItem/ReportActionItemImage.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index 7dc4cdf07031..ed46e00c7923 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -73,13 +73,11 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, tr ); } else { receiptImageComponent = ( - - - + ); } From db861f619867edcfad892e05d4c48507223097d1 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 12:08:45 +0530 Subject: [PATCH 021/961] remove MoneyRequestParticipantsPage.js. Signed-off-by: Krishna Gupta --- .../MoneyRequestParticipantsPage.js | 173 ------------------ 1 file changed, 173 deletions(-) delete mode 100644 src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js deleted file mode 100644 index ea57d88579ae..000000000000 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ /dev/null @@ -1,173 +0,0 @@ -import _ from 'lodash'; -import lodashGet from 'lodash/get'; -import lodashSize from 'lodash/size'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import transactionPropTypes from '@components/transactionPropTypes'; -import useInitialValue from '@hooks/useInitialValue'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import MoneyRequestParticipantsSelector from './MoneyRequestParticipantsSelector'; - -const propTypes = { - /** React Navigation route */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, - - /** The report ID of the IOU */ - reportID: PropTypes.string, - }), - }).isRequired, - - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, - - /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ - selectedTab: PropTypes.oneOf(_.values(CONST.TAB_REQUEST)), - - /** Transaction that stores the distance request data */ - transaction: transactionPropTypes, -}; - -const defaultProps = { - iou: iouDefaultProps, - transaction: {}, - selectedTab: undefined, -}; - -function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const prevMoneyRequestId = useRef(iou.id); - const iouType = useInitialValue(() => lodashGet(route, 'params.iouType', '')); - const reportID = useInitialValue(() => lodashGet(route, 'params.reportID', '')); - const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType, selectedTab); - const isSendRequest = iouType === CONST.IOU.TYPE.SEND; - const isScanRequest = MoneyRequestUtils.isScanRequest(selectedTab); - const isSplitRequest = iou.id === CONST.IOU.TYPE.SPLIT; - const waypoints = lodashGet(transaction, 'comment.waypoints', {}); - const validatedWaypoints = TransactionUtils.getValidWaypoints(waypoints); - const isInvalidWaypoint = lodashSize(validatedWaypoints) < 2; - const headerTitle = useMemo(() => { - if (isDistanceRequest) { - return translate('common.distance'); - } - - if (isSendRequest) { - return translate('common.send'); - } - - if (isScanRequest) { - return translate('tabSelector.scan'); - } - - if (iou.isSplitRequest) { - return translate('iou.split'); - } - - return translate('tabSelector.manual'); - }, [iou, isDistanceRequest, translate, isScanRequest, isSendRequest]); - - const navigateToConfirmationStep = (moneyRequestType) => { - IOU.setMoneyRequestId(moneyRequestType); - IOU.resetMoneyRequestCategory(); - Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(moneyRequestType, reportID)); - }; - - const navigateBack = useCallback((forceFallback = false) => { - Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID), forceFallback); - // eslint-disable-next-line react-hooks/exhaustive-deps -- no deps as we use only initial values - }, []); - - useEffect(() => { - const isInvalidDistanceRequest = !isDistanceRequest || isInvalidWaypoint; - - // ID in Onyx could change by initiating a new request in a separate browser tab or completing a request - if (prevMoneyRequestId.current !== iou.id) { - // The ID is cleared on completing a request. In that case, we will do nothing - if (iou.id && isInvalidDistanceRequest && !isSplitRequest) { - navigateBack(true); - } - return; - } - - // Reset the money request Onyx if the ID in Onyx does not match the ID from params - const moneyRequestId = `${iouType}${reportID}`; - const shouldReset = iou.id !== moneyRequestId && !_.isEmpty(reportID); - if (shouldReset) { - IOU.resetMoneyRequestInfo(moneyRequestId); - } - if (isInvalidDistanceRequest && ((iou.amount === 0 && !iou.receiptPath) || shouldReset)) { - navigateBack(true); - } - - return () => { - prevMoneyRequestId.current = iou.id; - }; - }, [iou.amount, iou.id, iou.receiptPath, isDistanceRequest, isSplitRequest, iouType, reportID, navigateBack, isInvalidWaypoint]); - - return ( - - {({safeAreaPaddingBottomStyle}) => ( - - - navigateToConfirmationStep(iouType)} - navigateToSplit={() => navigateToConfirmationStep(CONST.IOU.TYPE.SPLIT)} - safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} - iouType={iouType} - isDistanceRequest={isDistanceRequest} - isScanRequest={isScanRequest} - /> - - )} - - ); -} - -MoneyRequestParticipantsPage.displayName = 'MoneyRequestParticipantsPage'; -MoneyRequestParticipantsPage.propTypes = propTypes; -MoneyRequestParticipantsPage.defaultProps = defaultProps; - -export default compose( - withOnyx({ - iou: { - key: ONYXKEYS.IOU, - }, - selectedTab: { - key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({iou}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(iou, 'transactionID', 0)}`, - }, - }), -)(MoneyRequestParticipantsPage); From bd0ccb7e82426ce3ef30b1c8aa5ba6a569ec7eb5 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 6 Feb 2024 23:42:11 +0700 Subject: [PATCH 022/961] merge main --- .../ComposerWithSuggestionsEdit.tsx | 5 ++--- src/pages/home/report/ReportActionItemMessageEdit.tsx | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 927d160a8b7f..02a629c954e2 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,7 +1,6 @@ import type {Dispatch, ForwardedRef, RefObject, SetStateAction} from 'react'; import React, {useState} from 'react'; -import type {TextInputProps} from 'react-native'; -import type {AnimatedProps} from 'react-native-reanimated'; +import type {TextInput} from 'react-native'; import Composer from '@components/Composer'; import type {ComposerProps} from '@components/Composer/types'; import type {SuggestionsRef} from '@libs/actions/SuggestionsAction'; @@ -45,7 +44,7 @@ function ComposerWithSuggestionsEdit( measureParentContainer, id = undefined, }: ComposerWithSuggestionsEditProps & ComposerProps, - ref: ForwardedRef>>, + ref: ForwardedRef, ) { const [composerHeight, setComposerHeight] = useState(0); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 8dad9fce6003..a89b509a5f9a 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -1,3 +1,4 @@ +import {PortalHost} from '@gorhom/portal'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; @@ -39,7 +40,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; -import { PortalHost } from '@gorhom/portal'; const {RNTextInputReset} = NativeModules; From f11b8ed46d3c87315354c73370f5123763d2fcd8 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 19 Feb 2024 13:35:45 +0700 Subject: [PATCH 023/961] fix lint --- .../AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx | 4 ++-- src/components/AutoCompleteSuggestions/index.tsx | 6 +++--- src/components/AutoCompleteSuggestions/types.ts | 2 +- src/libs/SuggestionUtils.ts | 4 ++-- src/pages/home/report/ReportActionItemMessageEdit.tsx | 3 +-- src/styles/utils/index.ts | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index 2b7750f732f2..7b4d23d0f3e7 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -39,7 +39,7 @@ function BaseAutoCompleteSuggestions( suggestions, isSuggestionPickerLarge, keyExtractor, - shouldBelowParentContainer = false, + shouldBeDisplayedBelowParentContainer = false, }: AutoCompleteSuggestionsProps, ref: ForwardedRef, ) { @@ -68,7 +68,7 @@ function BaseAutoCompleteSuggestions( ); const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length; - const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBelowParentContainer)); + const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBeDisplayedBelowParentContainer)); const estimatedListSize = useMemo( () => ({ height: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length, diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index bda8e0aa7711..1f5d87b15962 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -4,7 +4,7 @@ import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import {measureHeightOfSuggestioContainer} from '@libs/SuggestionUtils'; +import {measureHeightOfSuggestionsContainer} from '@libs/SuggestionUtils'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; @@ -19,7 +19,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} const StyleUtils = useStyleUtils(); const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); - const suggestionContainerHeight = measureHeightOfSuggestioContainer(props.suggestions.length, props.isSuggestionPickerLarge); + const suggestionContainerHeight = measureHeightOfSuggestionsContainer(props.suggestions.length, props.isSuggestionPickerLarge); const [{width, left, bottom}, setContainerState] = React.useState({ width: 0, left: 0, @@ -56,7 +56,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} // eslint-disable-next-line react/jsx-props-no-spreading {...props} - shouldBelowParentContainer={shouldBelowContainer} + shouldBeDisplayedBelowParentContainer={shouldBelowContainer} ref={containerRef} /> ); diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index b837901026b2..d9824db1988d 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -35,7 +35,7 @@ type AutoCompleteSuggestionsProps = { measureParentContainer?: (callback: MeasureParentContainerCallback) => void; /** Whether suggestion should be displayed below the parent container or not */ - shouldBelowParentContainer?: boolean; + shouldBeDisplayedBelowParentContainer?: boolean; }; export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps}; diff --git a/src/libs/SuggestionUtils.ts b/src/libs/SuggestionUtils.ts index 00039592ba77..f61989133409 100644 --- a/src/libs/SuggestionUtils.ts +++ b/src/libs/SuggestionUtils.ts @@ -20,7 +20,7 @@ function hasEnoughSpaceForLargeSuggestionMenu(listHeight: number, composerHeight return availableHeight > menuHeight; } -const measureHeightOfSuggestioContainer = (numRows: number, isSuggestionPickerLarge: boolean): number => { +const measureHeightOfSuggestionsContainer = (numRows: number, isSuggestionPickerLarge: boolean): number => { const borderAndPadding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + 2; let suggestionHeight = 0; @@ -40,4 +40,4 @@ const measureHeightOfSuggestioContainer = (numRows: number, isSuggestionPickerLa return suggestionHeight + borderAndPadding; }; -export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestioContainer}; +export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestionsContainer}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 1ef9406c6327..bdd41a27baa6 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -4,8 +4,7 @@ import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {findNodeHandle, InteractionManager, Keyboard, NativeModules, View} from 'react-native'; -import {Keyboard, View} from 'react-native'; +import {findNodeHandle, Keyboard, NativeModules, View} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index ec3f088da537..8ca45907449d 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -793,7 +793,7 @@ function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: GetB /** * Gets the correct position for auto complete suggestion container */ -function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBelowParentContainer: boolean): ViewStyle { +function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBeDisplayedBelowParentContainer: boolean): ViewStyle { 'worklet'; const borderWidth = 2; @@ -803,7 +803,7 @@ function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBelo // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. return { overflow: 'hidden', - top: -(height + (shouldBelowParentContainer ? -2 : 1) * (CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + borderWidth)), + top: -(height + (shouldBeDisplayedBelowParentContainer ? -2 : 1) * (CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + borderWidth)), height, minHeight: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, }; From a7bcde62060bef47d9675cea3d300fb156fa170f Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 19 Feb 2024 13:37:25 +0700 Subject: [PATCH 024/961] update suggestion --- .../actions/{SuggestionsAction.ts => SuggestionsActions.ts} | 0 .../ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx | 2 +- src/pages/home/report/ReportActionItemMessageEdit.tsx | 2 +- src/pages/home/report/ReportActionsView.js | 2 +- src/styles/utils/index.ts | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/libs/actions/{SuggestionsAction.ts => SuggestionsActions.ts} (100%) diff --git a/src/libs/actions/SuggestionsAction.ts b/src/libs/actions/SuggestionsActions.ts similarity index 100% rename from src/libs/actions/SuggestionsAction.ts rename to src/libs/actions/SuggestionsActions.ts diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 02a629c954e2..379928e8f28f 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -3,7 +3,7 @@ import React, {useState} from 'react'; import type {TextInput} from 'react-native'; import Composer from '@components/Composer'; import type {ComposerProps} from '@components/Composer/types'; -import type {SuggestionsRef} from '@libs/actions/SuggestionsAction'; +import type {SuggestionsRef} from '@libs/actions/SuggestionsActions'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; type ComposerWithSuggestionsEditProps = { diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index bdd41a27baa6..226576e8b916 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -21,7 +21,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as SuggestionsAction from '@libs/actions/SuggestionsAction'; +import * as SuggestionsAction from '@libs/actions/SuggestionsActions'; import * as Browser from '@libs/Browser'; import * as ComposerUtils from '@libs/ComposerUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index a76814caf147..cf23fb76d052 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -11,7 +11,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useInitialValue from '@hooks/useInitialValue'; import usePrevious from '@hooks/usePrevious'; -import * as SuggestionsAction from '@libs/actions/SuggestionsAction'; +import * as SuggestionsAction from '@libs/actions/SuggestionsActions'; import compose from '@libs/compose'; import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; import Performance from '@libs/Performance'; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 8ca45907449d..61ef018350df 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -783,7 +783,7 @@ type GetBaseAutoCompleteSuggestionContainerStyleParams = { */ function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: GetBaseAutoCompleteSuggestionContainerStyleParams): ViewStyle { return { - // ...positioning.pFixed, + ...positioning.pFixed, bottom, left, width, From be5faa2661dced9170ec02ff85c302d1f23eb726 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 19 Feb 2024 13:39:21 +0700 Subject: [PATCH 025/961] rename variable --- src/libs/SuggestionUtils.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/SuggestionUtils.ts b/src/libs/SuggestionUtils.ts index f61989133409..414244d77e13 100644 --- a/src/libs/SuggestionUtils.ts +++ b/src/libs/SuggestionUtils.ts @@ -20,24 +20,24 @@ function hasEnoughSpaceForLargeSuggestionMenu(listHeight: number, composerHeight return availableHeight > menuHeight; } -const measureHeightOfSuggestionsContainer = (numRows: number, isSuggestionPickerLarge: boolean): number => { +const measureHeightOfSuggestionsContainer = (numRows: number, isSuggestionsPickerLarge: boolean): number => { const borderAndPadding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + 2; - let suggestionHeight = 0; + let suggestionsHeight = 0; - if (isSuggestionPickerLarge) { + if (isSuggestionsPickerLarge) { if (numRows > CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER) { // On large screens, if there are more than 5 suggestions, we display a scrollable window with a height of 5 items, indicating that there are more items available - suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + suggestionsHeight = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } else { - suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + suggestionsHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } } else if (numRows > 2) { // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible - suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + suggestionsHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } else { - suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + suggestionsHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } - return suggestionHeight + borderAndPadding; + return suggestionsHeight + borderAndPadding; }; export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestionsContainer}; From eb09849b6f8d18f52ab18e4942e5ed20b19118ae Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 26 Feb 2024 11:53:05 +0700 Subject: [PATCH 026/961] move portal of suggestion to the correct place --- src/pages/home/report/ReportActionItemMessageEdit.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 2c9190926d0d..6a182a6016c3 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -401,9 +401,12 @@ function ReportActionItemMessageEdit( return ( <> - + + - Date: Mon, 26 Feb 2024 17:14:29 +0100 Subject: [PATCH 027/961] feat: create new rate field --- .../MoneyRequestConfirmationList.js | 12 ++++++ ...oraryForRefactorRequestConfirmationList.js | 20 ++++++++++ src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/DistanceRequestUtils.ts | 39 ++++++++++++++++--- 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index df2781d3ea89..7bb4bb868c99 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -730,6 +730,18 @@ function MoneyRequestConfirmationList(props) { interactive={!props.isReadOnly} /> )} + {props.isDistanceRequest && ( + Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))} + disabled={didConfirm || !isTypeRequest} + interactive={!props.isReadOnly} + /> + )} {shouldShowMerchant && ( {}} + // onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm || !isTypeRequest} + interactive={!isReadOnly} + /> + ), + // TODO: hide when betas ready + shouldShow: isDistanceRequest, + isSupplementary: true, + }, { item: ( Date: Mon, 26 Feb 2024 19:20:47 +0100 Subject: [PATCH 028/961] feat: create rate selection page --- src/ROUTES.ts | 5 ++ src/SCREENS.ts | 1 + ...oraryForRefactorRequestConfirmationList.js | 23 +++---- .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + .../iou/request/step/IOURequestStepRate.tsx | 65 +++++++++++++++++++ 6 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 src/pages/iou/request/step/IOURequestStepRate.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a8786bda3ffb..c01fdac72aef 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -369,6 +369,11 @@ const ROUTES = { getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => getUrlWithBackToParam(`create/${iouType}/distance/${transactionID}/${reportID}`, backTo), }, + MONEY_REQUEST_STEP_RATE: { + route: ':action/:iouType/rate/:transactionID/:reportID', + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action}/${iouType}/rate/${transactionID}/${reportID}`, backTo), + }, MONEY_REQUEST_STEP_MERCHANT: { route: ':action/:iouType/merchant/:transactionID/:reportID', getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 520895c89c98..ddaf52a03a59 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -143,6 +143,7 @@ const SCREENS = { STEP_DATE: 'Money_Request_Step_Date', STEP_DESCRIPTION: 'Money_Request_Step_Description', STEP_DISTANCE: 'Money_Request_Step_Distance', + STEP_RATE: 'Money_Request_Step_Rate', STEP_MERCHANT: 'Money_Request_Step_Merchant', STEP_PARTICIPANTS: 'Money_Request_Step_Participants', STEP_SCAN: 'Money_Request_Step_Scan', diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 6fb0e8bc0f08..f2fcb3ac96eb 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -1,10 +1,10 @@ -import {useIsFocused} from '@react-navigation/native'; -import {format} from 'date-fns'; +import { useIsFocused } from '@react-navigation/native'; +import { format } from 'date-fns'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import { View } from 'react-native'; +import { withOnyx } from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; @@ -21,9 +21,9 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import playSound, {SOUNDS} from '@libs/Sound'; +import playSound, { SOUNDS } from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; -import {policyPropTypes} from '@pages/workspace/withPolicy'; +import { policyPropTypes } from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -44,7 +44,8 @@ import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails, { withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes } from './withCurrentUserPersonalDetails'; + const propTypes = { /** Callback to inform parent modal of success */ @@ -701,9 +702,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ description={translate('common.rate')} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - // TODO: Add the onPress function - onPress={() => {}} - // onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + onPress={() => { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} disabled={didConfirm || !isTypeRequest} interactive={!isReadOnly} /> diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 2be262aa5f0f..941900c3bf2c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -89,6 +89,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/request/step/IOURequestStepDate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: () => require('../../../pages/iou/request/step/IOURequestStepDescription').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: () => require('../../../pages/iou/request/step/IOURequestStepDistance').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.STEP_RATE]: () => require('../../../pages/iou/request/step/IOURequestStepRate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: () => require('../../../pages/iou/request/step/IOURequestStepMerchant').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: () => require('../../../pages/iou/request/step/IOURequestStepParticipants').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_SCAN]: () => require('../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 48d649cc4dd9..9163c0b9bfe5 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -416,6 +416,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_DATE]: ROUTES.MONEY_REQUEST_STEP_DATE.route, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.route, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: ROUTES.MONEY_REQUEST_STEP_DISTANCE.route, + [SCREENS.MONEY_REQUEST.STEP_RATE]: ROUTES.MONEY_REQUEST_STEP_RATE.route, [SCREENS.MONEY_REQUEST.HOLD]: ROUTES.MONEY_REQUEST_HOLD_REASON.route, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: ROUTES.MONEY_REQUEST_STEP_MERCHANT.route, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.route, diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx new file mode 100644 index 000000000000..347e627624e9 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; +import type {Route} from '@src/ROUTES'; + +type Props = { + // eslint-disable-next-line react/no-unused-prop-types + lastSelectedDistanceRate: string; + /** The route object passed to this screen */ + route: { + /** The params passed to this screen */ + params: { + /** The route to go back to */ + backTo: Route; + }; + }; +}; + +const mockRates = [ + {text: 'Default Rate', alternateText: '0.656/mile', keyForList: 'DefaultRate'}, + {text: 'Custom Rate', alternateText: '0.700/mile', keyForList: 'CustomRate'}, +]; + +function IOURequestStepRate({ + route: { + params: {backTo}, + }, +}: Props) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( + Navigation.goBack(backTo)} + shouldShowWrapper={Boolean(backTo)} + testID="rate" + > + {translate('themePage.chooseThemeBelowOrSync')} + + {}} + initiallyFocusedOptionKey="DefaultRate" + /> + + ); +} + +IOURequestStepRate.displayName = 'IOURequestStepRate'; + +// export default withOnyx({ +// lastSelectedDistanceRate: { +// key: 'ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATE', +// }, +// })(IOURequestStepRate); + +export default IOURequestStepRate; From b3e1f48577d3cd8e1b4c041ce661ae684507fdd0 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 29 Feb 2024 10:07:54 +0100 Subject: [PATCH 029/961] feat: add translation for the rate page --- src/languages/en.ts | 1 + src/languages/es.ts | 2 ++ src/pages/iou/request/step/IOURequestStepRate.tsx | 6 +++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 920705883b02..d904a52c1b7a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -696,6 +696,7 @@ export default { set: 'set', changed: 'changed', removed: 'removed', + chooseARate: 'Choose a rate to use below', }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index 1b858f707851..842f357639c8 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -691,6 +691,8 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', + // TODO: check if this is the correct translation + chooseARate: 'Elige una tarifa para utilizar a continuación', }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 347e627624e9..d82238f2c7ea 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -23,8 +23,8 @@ type Props = { }; const mockRates = [ - {text: 'Default Rate', alternateText: '0.656/mile', keyForList: 'DefaultRate'}, - {text: 'Custom Rate', alternateText: '0.700/mile', keyForList: 'CustomRate'}, + {text: 'Default Rate', alternateText: '$0.656 / mile', keyForList: 'DefaultRate'}, + {text: 'Custom Rate', alternateText: '$0.700 / mile', keyForList: 'CustomRate'}, ]; function IOURequestStepRate({ @@ -42,7 +42,7 @@ function IOURequestStepRate({ shouldShowWrapper={Boolean(backTo)} testID="rate" > - {translate('themePage.chooseThemeBelowOrSync')} + {translate('iou.chooseARate')} Date: Thu, 29 Feb 2024 17:59:45 +0100 Subject: [PATCH 030/961] feat: wip - display rates for policy --- ...oraryForRefactorRequestConfirmationList.js | 4 +- src/libs/DistanceRequestUtils.ts | 119 +++++++++++++++--- .../iou/request/step/IOURequestStepRate.tsx | 49 +++++--- src/types/onyx/Policy.ts | 4 +- 4 files changed, 139 insertions(+), 37 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 46e4cb3d6733..267662f07157 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -681,7 +681,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY, + callback: (policy, key) => { + if (!policy || !key || !policy.name) { + return; + } + + policies[key] = policy; + }, +}); + /** * Retrieves the default mileage rate based on a given policy. * @@ -67,22 +81,21 @@ function convertDistanceUnit(distanceInMeters: number, unit: Unit): number { */ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string { const convertedDistance = convertDistanceUnit(distanceInMeters, unit); - return convertedDistance.toFixed(2); + // TODO: add logic for currencies for which we need to round to 4 decimals + return convertedDistance.toFixed(3); } /** -* @param hasRoute Whether the route exists for the distance request -* @param distanceInMeters Distance traveled -* @param unit Unit that should be used to display the distance -* @param rate Expensable amount allowed per unit -* @param currency The currency associated with the rate -* @param translate Translate function -* @param toLocaleDigit Function to convert to localized digit -* @returns A string that describes the distance traveled and the rate used for expense calculation -*/ + * @param hasRoute Whether the route exists for the distance request + * @param unit Unit that should be used to display the distance + * @param rate Expensable amount allowed per unit + * @param currency The currency associated with the rate + * @param translate Translate function + * @param toLocaleDigit Function to convert to localized digit + * @returns A string that describes the distance traveled and the rate used for expense calculation + */ function getRateForDisplay( hasRoute: boolean, - distanceInMeters: number, unit: Unit, rate: number, currency: string, @@ -101,6 +114,28 @@ function getRateForDisplay( return `${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; } +// TODO: I wonder if it would be better to rfactor these functions to pass params in an object +/** + * @param hasRoute Whether the route exists for the distance request + * @param distanceInMeters Distance traveled + * @param unit Unit that should be used to display the distance + * @param rate Expensable amount allowed per unit + * @param translate Translate function + * @returns A string that describes the distance traveled + */ +function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, translate: LocaleContextProps['translate']): string { + if (!hasRoute) { + return translate('iou.routePending'); + } + + const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit); + const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); + const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); + const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; + + return `${distanceInUnits} ${unitString}`; +} + /** * @param hasRoute Whether the route exists for the distance request * @param distanceInMeters Distance traveled @@ -124,13 +159,52 @@ function getDistanceMerchant( return translate('iou.routePending'); } - const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit); - const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); - const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); - const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; - const ratePerUnit = getRateForDisplay(hasRoute, distanceInMeters, unit, rate, currency, translate, toLocaleDigit); + const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, translate); + const ratePerUnit = getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); + + return `${distanceInUnits} @ ${ratePerUnit}`; +} + +/** + * Retrieves the mileage rates for given policy. + * + * @param policyID - The policy ID from which to extract the mileage rates. + * + * @returns An array of mileage rates or an empty array if not found. + */ +function getMileageRates(policyID?: string): MileageRate[] | [] { + if (!policyID) { + return []; + } + + const mileageRates: MileageRate[] = []; + + const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? null; + + if (!policy || !policy?.customUnits) { + return mileageRates; + } + + const distanceUnit = Object.values(policy.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + if (!distanceUnit?.rates) { + return mileageRates; + } + + const rates = Object.values(distanceUnit.rates); + + for (const rate of rates) { + if (rate.enabled) { + mileageRates.push({ + rate: rate.rate ?? 0, + name: rate.name, + currency: rate.currency ?? 'USD', + unit: distanceUnit.attributes.unit, + customUnitRateID: rate.customUnitRateID, + }); + } + } - return `${distanceInUnits} ${unitString} @ ${ratePerUnit}`; + return mileageRates; } /** @@ -147,4 +221,11 @@ function getDistanceRequestAmount(distance: number, unit: Unit, rate: number): n return Math.round(roundedDistance * rate); } -export default {getDefaultMileageRate, getDistanceMerchant, getDistanceRequestAmount, getRateForDisplay}; +export default { + getDefaultMileageRate, + getDistanceMerchant, + getDistanceRequestAmount, + getRateForDisplay, + getMileageRates, + getDistanceForDisplay, +}; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index d82238f2c7ea..3b7889de9651 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -1,17 +1,27 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import compose from '@libs/compose'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; +import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; +import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; +import type {Policy} from '@src/types/onyx'; type Props = { // eslint-disable-next-line react/no-unused-prop-types lastSelectedDistanceRate: string; + + /** Policy details */ + policy: OnyxEntry; + /** The route object passed to this screen */ route: { /** The params passed to this screen */ @@ -22,18 +32,22 @@ type Props = { }; }; -const mockRates = [ - {text: 'Default Rate', alternateText: '$0.656 / mile', keyForList: 'DefaultRate'}, - {text: 'Custom Rate', alternateText: '$0.700 / mile', keyForList: 'CustomRate'}, -]; - function IOURequestStepRate({ + policy, route: { params: {backTo}, }, }: Props) { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {translate, toLocaleDigit} = useLocalize(); + const rates = DistanceRequestUtils.getMileageRates(policy?.id); + + const data = rates.map((rate) => ({ + text: rate.name ?? '', + alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), + keyForList: rate.name ?? '', + value: rate.customUnitRateID, + })); return ( {translate('iou.chooseARate')} {}} - initiallyFocusedOptionKey="DefaultRate" + // TODO: change for lastSelectedDistanceRates + initiallyFocusedOptionKey="Default Rate" /> ); @@ -56,10 +71,14 @@ function IOURequestStepRate({ IOURequestStepRate.displayName = 'IOURequestStepRate'; -// export default withOnyx({ -// lastSelectedDistanceRate: { -// key: 'ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATE', -// }, -// })(IOURequestStepRate); - -export default IOURequestStepRate; +export default compose( + withWritableReportOrNotFound, + withOnyx({ + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, + }, + // lastSelectedDistanceRates: { + // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + // }, + }), +)(IOURequestStepRate); diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 1eece2d3a1e0..8e408951a84f 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -19,8 +19,10 @@ type Attributes = { type MileageRate = { unit: Unit; - rate?: number; currency: string; + customUnitRateID?: string; + rate?: number; + name?: string; }; type CustomUnit = OnyxCommon.OnyxValueWithOfflineFeedback<{ From cde1d19d37cdd0203ca6023612dfe12ea0f46537 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 1 Mar 2024 16:12:00 +0100 Subject: [PATCH 031/961] feat: wip - get rate for p2p --- ...oraryForRefactorRequestConfirmationList.js | 52 ++++++++++++++++--- src/libs/DistanceRequestUtils.ts | 13 +++-- ...yForRefactorRequestParticipantsSelector.js | 4 +- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 267662f07157..5a7019e63428 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -239,6 +239,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, + lastSelectedDistanceRate = {}, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -250,6 +251,12 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSend = iouType === CONST.IOU.TYPE.SEND; const {unit, rate, currency} = mileageRate; + + // const rate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' + + // const {unit, rate, currency} = lastSelectedDistanceRate || mileageRate; + + const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; const taxRates = lodashGet(policy, 'taxRates', {}); @@ -266,6 +273,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; + console.log({transaction}); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field @@ -622,6 +631,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ); }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); + // TODO: change for a value from usePermissions [will be added in this PR https://github.com/Expensify/App/pull/37185] + // change for true for development + const canUseP2PDistanceRequests = true; + // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. const classifiedFields = [ @@ -681,7 +694,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ), - shouldShow: isDistanceRequest, + shouldShow: isDistanceRequest && !canUseP2PDistanceRequests, isSupplementary: true, }, { item: ( Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm || !isTypeRequest} + interactive={!isReadOnly} + /> + ), + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, + isSupplementary: true, + }, + { + item: ( + { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()), + ); }} disabled={didConfirm || !isTypeRequest} - interactive={!isReadOnly} + interactive={!isReadOnly && isPolicyExpenseChat} /> ), - // TODO: hide when betas ready - shouldShow: isDistanceRequest, + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, isSupplementary: true, }, { @@ -950,12 +982,16 @@ export default compose( policyTags: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, + // TODO: add NVP_LAST_SELECTED_DISTANCE_RATES + // lastSelectedDistanceRates: { + // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + // }, mileageRate: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - }, + } }), )(MoneyTemporaryForRefactorRequestConfirmationList); diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index a605ace8da87..191edd93bfb9 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -12,6 +12,8 @@ type DefaultMileageRate = { rate?: number; currency?: string; unit: Unit; + name?: string; + customUnitRateID?: string; }; const policies: OnyxCollection = {}; @@ -55,6 +57,8 @@ function getDefaultMileageRate(policy: OnyxEntry): DefaultMileageRate | rate: distanceRate.rate, currency: distanceRate.currency, unit: distanceUnit.attributes.unit, + name: distanceRate.name, + customUnitRateID: distanceRate.customUnitRateID, }; } @@ -91,6 +95,7 @@ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string return convertedDistance.toFixed(3); } +// TODO: I wonder if it would be better to refactor these functions to pass params in an object /** * @param hasRoute Whether the route exists for the distance request * @param unit Unit that should be used to display the distance @@ -120,7 +125,7 @@ function getRateForDisplay( return `${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; } -// TODO: I wonder if it would be better to rfactor these functions to pass params in an object +// TODO: this function will be added in https://github.com/Expensify/App/pull/37185, remove it to avoid conflicts /** * @param hasRoute Whether the route exists for the distance request * @param distanceInMeters Distance traveled @@ -129,8 +134,8 @@ function getRateForDisplay( * @param translate Translate function * @returns A string that describes the distance traveled */ -function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, translate: LocaleContextProps['translate']): string { - if (!hasRoute) { +function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, rate: number, translate: LocaleContextProps['translate']): string { + if (!hasRoute || !rate) { return translate('iou.routePending'); } @@ -165,7 +170,7 @@ function getDistanceMerchant( return translate('iou.routePending'); } - const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, translate); + const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, rate, translate); const ratePerUnit = getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); return `${distanceInUnits} @ ${ratePerUnit}`; diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 2865316b7fd5..b42b94afc686 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -121,7 +121,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType === CONST.IOU.TYPE.REQUEST, // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + true, false, {}, [], @@ -131,7 +131,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + true, false, ); From c3cb80bf507de6c4922b28e170893cdcaa126ebb Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 1 Mar 2024 17:38:59 +0100 Subject: [PATCH 032/961] feat: wip - new rate field logic --- src/CONST.ts | 7 ++++ ...oraryForRefactorRequestConfirmationList.js | 40 +++++++++---------- src/libs/DistanceRequestUtils.ts | 39 +++++++++--------- src/libs/actions/IOU.ts | 13 ++++++ .../iou/request/step/IOURequestStepRate.tsx | 12 +++++- 5 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8d4eaac44a38..2bd047f6bf51 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1398,6 +1398,13 @@ const CONST = { RATE_DECIMALS: 3, }, + // TODO: remove this mock when https://github.com/Expensify/App/issues/36982 is done + CURRENCY_TO_DEFAULT_MILEAGE_RATE: { + USD: { unit: "mile", rate: 0.5 }, + EUR: { unit: "kilometer", rate: 0.8 }, + GBP: { unit: "mile", rate: 0.45 }, + }, + TERMS: { CFPB_PREPAID: 'cfpb.gov/prepaid', CFPB_COMPLAINT: 'cfpb.gov/complaint', diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 5a7019e63428..9c2d77239015 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -1,10 +1,10 @@ -import { useIsFocused } from '@react-navigation/native'; -import { format } from 'date-fns'; +import {useIsFocused} from '@react-navigation/native'; +import {format} from 'date-fns'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; -import { View } from 'react-native'; -import { withOnyx } from 'react-native-onyx'; +import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; @@ -21,9 +21,9 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import playSound, { SOUNDS } from '@libs/Sound'; +import playSound, {SOUNDS} from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; -import { policyPropTypes } from '@pages/workspace/withPolicy'; +import {policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -44,8 +44,7 @@ import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; -import withCurrentUserPersonalDetails, { withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes } from './withCurrentUserPersonalDetails'; - +import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; const propTypes = { /** Callback to inform parent modal of success */ @@ -239,7 +238,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, - lastSelectedDistanceRate = {}, + mileageRates, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -250,12 +249,11 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; const isTypeSend = iouType === CONST.IOU.TYPE.SEND; + // TODO: uncomment after Splits and P2P are enabled https://github.com/Expensify/App/pull/37185, mileageRate prop should be be removed + // const mileageRate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' ? DistanceRequestUtils.getRateForP2P(policy.outputCurrency) : mileageRates[transaction.comment.customUnit.customUnitRateID]; const {unit, rate, currency} = mileageRate; - // const rate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' - - // const {unit, rate, currency} = lastSelectedDistanceRate || mileageRate; - + // will hardcoded rates will have a currency property? If not we have to get currency other way const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; @@ -273,8 +271,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; - console.log({transaction}); - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field @@ -633,7 +629,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // TODO: change for a value from usePermissions [will be added in this PR https://github.com/Expensify/App/pull/37185] // change for true for development - const canUseP2PDistanceRequests = true; + const canUseP2PDistanceRequests = false; // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. @@ -982,16 +978,16 @@ export default compose( policyTags: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, - // TODO: add NVP_LAST_SELECTED_DISTANCE_RATES - // lastSelectedDistanceRates: { - // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - // }, mileageRate: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, + mileageRates: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + selector: (policy) => DistanceRequestUtils.getMileageRates(policy ? policy.id : ''), + }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - } + }, }), )(MoneyTemporaryForRefactorRequestConfirmationList); diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 191edd93bfb9..dd95612af3e6 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,4 +1,4 @@ -import type {OnyxEntry, OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; @@ -183,13 +183,13 @@ function getDistanceMerchant( * * @returns An array of mileage rates or an empty array if not found. */ -function getMileageRates(policyID?: string): DefaultMileageRate[] | [] { +function getMileageRates(policyID?: string): Record { + const mileageRates = {}; + if (!policyID) { - return []; + return mileageRates; } - const mileageRates: DefaultMileageRate[] = []; - const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? null; if (!policy || !policy?.customUnits) { @@ -201,23 +201,25 @@ function getMileageRates(policyID?: string): DefaultMileageRate[] | [] { return mileageRates; } - const rates = Object.values(distanceUnit.rates); - - for (const rate of rates) { - if (rate.enabled) { - mileageRates.push({ - rate: rate.rate ?? 0, - name: rate.name, - currency: rate.currency ?? 'USD', - unit: distanceUnit.attributes.unit, - customUnitRateID: rate.customUnitRateID, - }); - } - } + Object.entries(distanceUnit.rates).forEach(([rateID, rate]) => { + // TODO: fix TS error + mileageRates[rateID] = { + rate: rate.rate, + currency: rate.currency, + unit: distanceUnit.attributes.unit, + name: rate.name, + customUnitRateID: rate.customUnitRateID, + }; + }); return mileageRates; } +// TODO: probably will need to be changed +function getRateForP2P(currency) { + return CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; +} + /** * Calculates the request amount based on distance, unit, and rate. * @@ -239,4 +241,5 @@ export default { getRateForDisplay, getMileageRates, getDistanceForDisplay, + getRateForP2P, }; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5f9657755b02..91dd95b12592 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -355,6 +355,17 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: }); } +/** Set the last selected distance rate for policy */ +// TODO: probably need to be changed +function setLastSelectedDistanceRates(policyID: string, rateID: string) { + Onyx.merge('lastSelectedDistanceRates', {[policyID]: rateID}); +} + +/** Update transaction distance rate */ +function updateDistanceRequestRate(transactionID: string, rateID: string) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {comment: {customUnit: {customUnitRateID: rateID}}}); +} + /** Reset money request info from the store with its initial value */ function resetMoneyRequestInfo(id = '') { // Disabling this line since currentDate can be an empty string @@ -4327,4 +4338,6 @@ export { cancelPayment, navigateToStartStepIfScanFileCannotBeRead, savePreferredPaymentMethod, + setLastSelectedDistanceRates, + updateDistanceRequestRate, }; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 3b7889de9651..a2cf7a0af1c8 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -9,6 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as IOU from '@libs/actions/IOU'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -42,13 +43,20 @@ function IOURequestStepRate({ const {translate, toLocaleDigit} = useLocalize(); const rates = DistanceRequestUtils.getMileageRates(policy?.id); - const data = rates.map((rate) => ({ + const data = Object.values(rates).map((rate) => ({ text: rate.name ?? '', alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), keyForList: rate.name ?? '', value: rate.customUnitRateID, })); + const selectDistanceRate = (customUnitRateID) => { + IOU.setLastSelectedDistanceRates(policy?.id ?? '', customUnitRateID); + // TODO: get a proper transaction ID + IOU.updateDistanceRequestRate('1', customUnitRateID); + Navigation.goBack(backTo); + } + return ( {}} + onSelectRow={({value}) => selectDistanceRate(value)} // TODO: change for lastSelectedDistanceRates initiallyFocusedOptionKey="Default Rate" /> From 5bf399af20bdb7816bedf1ed40ee37fa83e3fffa Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 1 Mar 2024 17:51:11 +0100 Subject: [PATCH 033/961] fix: revert filtering changes --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index b42b94afc686..2865316b7fd5 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -121,7 +121,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType === CONST.IOU.TYPE.REQUEST, // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - true, + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, false, {}, [], @@ -131,7 +131,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - true, + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, false, ); From 930eb47f5222088e8f687d1be63707d83ec239e2 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 4 Mar 2024 17:18:45 +0700 Subject: [PATCH 034/961] remove global ref and create suggestion context --- src/App.tsx | 2 + src/libs/actions/SuggestionsActions.ts | 50 ------------------- .../ComposerWithSuggestionsEdit.tsx | 25 +++++----- .../SuggestionsContext.tsx | 50 +++++++++++++++++++ .../report/ReportActionItemMessageEdit.tsx | 21 +++++--- src/pages/home/report/ReportActionsView.js | 10 +++- 6 files changed, 87 insertions(+), 71 deletions(-) delete mode 100644 src/libs/actions/SuggestionsActions.ts create mode 100644 src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx diff --git a/src/App.tsx b/src/App.tsx index cbe5948f8d4e..03dc3c328488 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,6 +31,7 @@ import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import InitialUrlContext from './libs/InitialUrlContext'; +import {SuggestionsContextProvider} from './pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; import type {Route} from './ROUTES'; @@ -76,6 +77,7 @@ function App({url}: AppProps) { ActiveElementRoleProvider, ActiveWorkspaceContextProvider, PlaybackContextProvider, + SuggestionsContextProvider, VolumeContextProvider, VideoPopoverMenuContextProvider, ]} diff --git a/src/libs/actions/SuggestionsActions.ts b/src/libs/actions/SuggestionsActions.ts deleted file mode 100644 index 9fecd18f7d10..000000000000 --- a/src/libs/actions/SuggestionsActions.ts +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import type {NativeSyntheticEvent, TextInputKeyPressEventData, TextInputSelectionChangeEventData} from 'react-native'; - -type SuggestionsRef = { - getSuggestions: () => void; - resetSuggestions: () => void; - triggerHotkeyActions: (event: NativeSyntheticEvent | KeyboardEvent) => boolean; - onSelectionChange: (event: NativeSyntheticEvent) => void; - updateShouldShowSuggestionMenuToFalse: () => void; - setShouldBlockSuggestionCalc: () => void; -}; - -const suggestionsRef = React.createRef(); - -function resetSuggestions() { - if (!suggestionsRef.current) { - return; - } - - suggestionsRef.current.resetSuggestions(); -} - -function triggerHotkeyActions(event: NativeSyntheticEvent | KeyboardEvent): boolean { - if (!suggestionsRef.current) { - return false; - } - - return suggestionsRef.current.triggerHotkeyActions(event); -} - -function updateShouldShowSuggestionMenuToFalse() { - if (!suggestionsRef.current) { - return; - } - - suggestionsRef.current.updateShouldShowSuggestionMenuToFalse(); -} - -function onSelectionChange(event: NativeSyntheticEvent) { - if (!suggestionsRef.current) { - return; - } - - suggestionsRef.current.onSelectionChange(event); -} - -export {suggestionsRef, resetSuggestions, triggerHotkeyActions, onSelectionChange, updateShouldShowSuggestionMenuToFalse}; - -// eslint-disable-next-line import/prefer-default-export -export type {SuggestionsRef}; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 379928e8f28f..ce8cfa1dc356 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,24 +1,26 @@ import type {Dispatch, ForwardedRef, RefObject, SetStateAction} from 'react'; import React, {useState} from 'react'; -import type {TextInput} from 'react-native'; +import type {MeasureInWindowOnSuccessCallback, TextInput} from 'react-native'; import Composer from '@components/Composer'; import type {ComposerProps} from '@components/Composer/types'; -import type {SuggestionsRef} from '@libs/actions/SuggestionsActions'; +import type {SuggestionsRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; -type ComposerWithSuggestionsEditProps = { +type Selection = { + start: number; + end: number; +}; + +type ComposerWithSuggestionsEditProps = ComposerProps & { setValue: Dispatch>; - setSelection: Dispatch< - SetStateAction<{ - start: number; - end: number; - }> - >; + setSelection: Dispatch>; resetKeyboardInput: () => void; isComposerFocused: boolean; suggestionsRef: RefObject; updateDraft: (newValue: string) => void; - measureParentContainer: (callback: () => void) => void; + measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void; + value: string; + selection: Selection; }; function ComposerWithSuggestionsEdit( @@ -43,7 +45,7 @@ function ComposerWithSuggestionsEdit( updateDraft, measureParentContainer, id = undefined, - }: ComposerWithSuggestionsEditProps & ComposerProps, + }: ComposerWithSuggestionsEditProps, ref: ForwardedRef, ) { const [composerHeight, setComposerHeight] = useState(0); @@ -74,7 +76,6 @@ function ComposerWithSuggestionsEdit( ; + updateCurrentActiveSuggestionsRef: (ref: SuggestionsRef | null, id: string) => void; + clearActiveSuggestionsRef: () => void; +}; + +const SuggestionsContext = createContext({ + currentActiveSuggestionsRef: {current: null}, + updateCurrentActiveSuggestionsRef: () => {}, + clearActiveSuggestionsRef: () => {}, +}); + +function SuggestionsContextProvider({children}: SuggestionsContextProviderProps) { + const currentActiveSuggestionsRef = useRef(null); + const [activeID, setActiveID] = useState(null); + + const updateCurrentActiveSuggestionsRef = useCallback((ref: SuggestionsRef | null, id: string) => { + currentActiveSuggestionsRef.current = ref; + setActiveID(id); + }, []); + + const clearActiveSuggestionsRef = useCallback(() => { + currentActiveSuggestionsRef.current = null; + setActiveID(null); + }, []); + + const contextValue = useMemo( + () => ({activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef}), + [activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef], + ); + + return {children}; +} + +function useSuggestionsContext() { + const context = useContext(SuggestionsContext); + return context; +} + +SuggestionsContextProvider.displayName = 'PlaybackContextProvider'; + +export {SuggestionsContextProvider, useSuggestionsContext}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 6a182a6016c3..ad516946fecf 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -5,7 +5,7 @@ import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {findNodeHandle, Keyboard, NativeModules, View} from 'react-native'; -import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; +import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import ExceededCommentLength from '@components/ExceededCommentLength'; @@ -21,7 +21,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as SuggestionsAction from '@libs/actions/SuggestionsActions'; import * as Browser from '@libs/Browser'; import * as ComposerUtils from '@libs/ComposerUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; @@ -42,6 +41,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; +import {useSuggestionsContext} from './ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; +import type {SuggestionsRef} from './ReportActionCompose/ReportActionCompose'; const {RNTextInputReset} = NativeModules; @@ -81,6 +82,7 @@ function ReportActionItemMessageEdit( const {translate, preferredLocale} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); const {isSmallScreenWidth} = useWindowDimensions(); + const {updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef} = useSuggestionsContext(); const getInitialDraft = () => { if (draftMessage === action?.message?.[0].html) { @@ -125,6 +127,7 @@ function ReportActionItemMessageEdit( const insertedEmojis = useRef([]); const draftRef = useRef(draft); const containerRef = useRef(null); + const suggestionsRef = useRef(null); useEffect(() => { if (ReportActionsUtils.isDeletedAction(action) || (action.message && draftMessage === action.message[0].html)) { @@ -257,7 +260,7 @@ function ReportActionItemMessageEdit( if (emojis?.length > 0) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (newEmojis?.length > 0) { - SuggestionsAction.resetSuggestions(); + suggestionsRef.current?.resetSuggestions(); insertedEmojis.current = [...insertedEmojis.current, ...newEmojis]; debouncedUpdateFrequentlyUsedEmojis(); } @@ -352,7 +355,7 @@ function ReportActionItemMessageEdit( */ const triggerSaveOrCancel = useCallback( (e: NativeSyntheticEvent | KeyboardEvent) => { - if (SuggestionsAction.triggerHotkeyActions(e)) { + if (suggestionsRef.current?.triggerHotkeyActions(e as KeyboardEvent)) { return; } @@ -379,7 +382,7 @@ function ReportActionItemMessageEdit( }, [textInputRef]); const measureContainer = useCallback( - (callback: () => void) => { + (callback: MeasureInWindowOnSuccessCallback) => { if (!containerRef.current) { return; } @@ -465,11 +468,15 @@ function ReportActionItemMessageEdit( if (!ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { ReportActionContextMenu.clearActiveReportAction(); } + + updateCurrentActiveSuggestionsRef(suggestionsRef.current, action.reportActionID); }} onBlur={(event: NativeSyntheticEvent) => { setIsFocused(false); // @ts-expect-error TODO: TextInputFocusEventData doesn't contain relatedTarget. const relatedTargetId = event.nativeEvent?.relatedTarget?.id; + suggestionsRef.current?.resetSuggestions(); + clearActiveSuggestionsRef(); if (relatedTargetId && [messageEditInput, emojiButtonID].includes(relatedTargetId)) { return; } @@ -477,14 +484,14 @@ function ReportActionItemMessageEdit( }} selection={selection} onSelectionChange={(e) => { - SuggestionsAction.onSelectionChange(e); + suggestionsRef.current?.onSelectionChange?.(e); setSelection(e.nativeEvent.selection); }} setValue={setDraft} setSelection={setSelection} isComposerFocused={!!textInputRef.current && textInputRef.current.isFocused()} resetKeyboardInput={resetKeyboardInput} - suggestionsRef={SuggestionsAction.suggestionsRef} + suggestionsRef={suggestionsRef} updateDraft={updateDraft} measureParentContainer={measureContainer} /> diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 5a167dbb376f..a7584a3570dd 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -11,7 +11,6 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useInitialValue from '@hooks/useInitialValue'; import usePrevious from '@hooks/usePrevious'; -import * as SuggestionsAction from '@libs/actions/SuggestionsActions'; import compose from '@libs/compose'; import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; import Performance from '@libs/Performance'; @@ -25,6 +24,7 @@ import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import PopoverReactionList from './ReactionList/PopoverReactionList'; +import {useSuggestionsContext} from './ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import reportActionPropTypes from './reportActionPropTypes'; import ReportActionsList from './ReportActionsList'; @@ -88,6 +88,7 @@ const defaultProps = { function ReportActionsView(props) { useCopySelectionHelper(); const reactionListRef = useContext(ReactionListContext); + const {currentActiveSuggestionsRef} = useSuggestionsContext(); const didLayout = useRef(false); const didSubscribeToReportTypingEvents = useRef(false); const isFirstRender = useRef(true); @@ -261,7 +262,12 @@ function ReportActionsView(props) { report={props.report} parentReportAction={props.parentReportAction} onLayout={recordTimeToMeasureItemLayout} - onScroll={SuggestionsAction.updateShouldShowSuggestionMenuToFalse} + onScroll={() => { + if (!currentActiveSuggestionsRef.current) { + return; + } + currentActiveSuggestionsRef.current.updateShouldShowSuggestionMenuToFalse(); + }} sortedReportActions={props.reportActions} mostRecentIOUReportActionID={mostRecentIOUReportActionID} loadOlderChats={loadOlderChats} From ef7b30e6a95bc95ed2ead63f27e25636398f7c6b Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 13 Mar 2024 10:19:52 +0100 Subject: [PATCH 035/961] refactor: use canUseP2PDistanceRequests from usePermissions --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 182aa5464772..7333d405363b 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -664,10 +664,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ); }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); - // TODO: change for a value from usePermissions [will be added in this PR https://github.com/Expensify/App/pull/37185] - // change for true for development - const canUseP2PDistanceRequests = false; - // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. const classifiedFields = [ From 6f3afddef1d036658a48b38ca8f0caf6c696c85f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 13 Mar 2024 17:28:24 +0100 Subject: [PATCH 036/961] fix: remove mock rates --- src/CONST.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 9b2502cb17d1..d4fbd0ff6ef3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1431,13 +1431,6 @@ const CONST = { FAKE_P2P_ID: '_FAKE_P2P_ID_', }, - // TODO: remove this mock when https://github.com/Expensify/App/issues/36982 is done - CURRENCY_TO_DEFAULT_MILEAGE_RATE: { - USD: { unit: "mile", rate: 0.5 }, - EUR: { unit: "kilometer", rate: 0.8 }, - GBP: { unit: "mile", rate: 0.45 }, - }, - TERMS: { CFPB_PREPAID: 'cfpb.gov/prepaid', CFPB_COMPLAINT: 'cfpb.gov/complaint', From 50174f38629f23ef3363e51613755b05e30ec3d0 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 13 Mar 2024 17:29:24 +0100 Subject: [PATCH 037/961] feat: create function for getting personal policy --- src/libs/PolicyUtils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 4689fd03ebd0..6a3829b10934 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1,5 +1,6 @@ import Str from 'expensify-common/lib/str'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -12,6 +13,14 @@ import Navigation from './Navigation/Navigation'; type MemberEmailsToAccountIDs = Record; type UnitRate = {rate: number}; +let allPolicies: OnyxCollection; + +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY, + waitForCollectionCallback: true, + callback: (value) => (allPolicies = value), +}); + /** * Filter out the active policies, which will exclude policies with pending deletion * These are policies that we can use to create reports with in NewDot. @@ -265,6 +274,10 @@ function goBackFromInvalidPolicy() { Navigation.navigateWithSwitchPolicyID({route: ROUTES.ALL_SETTINGS}); } +function getPersonalPolicy() { + return Object.values(allPolicies ?? {}).find((policy) => policy?.type === CONST.POLICY.TYPE.PERSONAL); +} + export { getActivePolicies, hasAccountingConnections, @@ -295,6 +308,7 @@ export { getPathWithoutPolicyID, getPolicyMembersByIdWithoutCurrentUser, goBackFromInvalidPolicy, + getPersonalPolicy }; export type {MemberEmailsToAccountIDs}; From eb3aff8ee97f1f055d22376ea7004ba575ff1cb9 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 14 Mar 2024 19:15:31 +0100 Subject: [PATCH 038/961] feat: add rate field to MoneyRequestView --- .../ReportActionItem/MoneyRequestView.tsx | 68 +++++++++++++++---- src/libs/DistanceRequestUtils.ts | 17 ++--- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 70c65d1d66ce..bcaa7f830925 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -21,6 +21,7 @@ import type {ViolationField} from '@hooks/useViolations'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CardUtils from '@libs/CardUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; @@ -89,8 +90,8 @@ function MoneyRequestView({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); - const {translate} = useLocalize(); - const {canUseViolations} = usePermissions(); + const {translate, toLocaleDigit} = useLocalize(); + const {canUseViolations, canUseP2PDistanceRequests} = usePermissions(); const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null; const moneyRequestReport = parentReport; const { @@ -151,6 +152,20 @@ function MoneyRequestView({ let amountDescription = `${translate('iou.amount')}`; + const hasRoute = TransactionUtils.hasRoute(transaction); + const distance = transaction?.routes?.route0?.distance ?? 0; + const rateID = transaction?.comment.customUnit?.customUnitRateID ?? '0'; + + const rates = DistanceRequestUtils.getMileageRates(policy?.id); + const {unit, currency, rate} = rates[rateID as string] ?? { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + currency: CONST.CURRENCY.USD, + rate: 0, + }; + + const rateToDisplay = DistanceRequestUtils.getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); + const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); + const saveBillable = useCallback( (newBillable: boolean) => { // If the value hasn't changed, don't request to save changes on the server and just close the modal @@ -236,6 +251,44 @@ function MoneyRequestView({ [transactionAmount, isSettled, isCancelled, isPolicyExpenseChat, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], ); + const distanceRequestFields = canUseP2PDistanceRequests ? ( + <> + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + + {}} + /> + + + ) : ( + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + ) + return ( @@ -320,16 +373,7 @@ function MoneyRequestView({ /> {isDistanceRequest ? ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} - /> - + distanceRequestFields ) : ( { - const mileageRates = {}; + const mileageRates: Record = {}; if (!policyID) { return mileageRates; @@ -202,7 +201,6 @@ function getMileageRates(policyID?: string): Record } Object.entries(distanceUnit.rates).forEach(([rateID, rate]) => { - // TODO: fix TS error mileageRates[rateID] = { rate: rate.rate, currency: rate.currency, @@ -215,8 +213,7 @@ function getMileageRates(policyID?: string): Record return mileageRates; } -// TODO: probably will need to be changed -function getRateForP2P(currency) { +function getRateForP2P(currency: string) { return CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; } From 8dcd362fdcb118b8e6a7108bbf26d722d55ca100 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 15 Mar 2024 16:39:49 +0700 Subject: [PATCH 039/961] fix the suggestion padding for edit composer --- src/CONST.ts | 1 + .../BaseAutoCompleteSuggestions.tsx | 4 +++- .../ComposerWithSuggestionsEdit/SuggestionsContext.tsx | 10 ++++++++-- src/pages/home/report/ReportActionItemMessageEdit.tsx | 5 ++++- src/styles/utils/index.ts | 5 +++-- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fa44cda20720..68fb72541845 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1065,6 +1065,7 @@ const CONST = { EMOJI_PICKER_HEADER_HEIGHT: 32, RECIPIENT_LOCAL_TIME_HEIGHT: 25, AUTO_COMPLETE_SUGGESTER: { + EDIT_SUGGESTER_PADDING: 8, SUGGESTER_PADDING: 6, SUGGESTER_INNER_PADDING: 8, SUGGESTION_ROW_HEIGHT: 40, diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index 6c65afa9c2a8..a11fb2a01cc8 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -10,6 +10,7 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {useSuggestionsContext} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import viewForwardedRef from '@src/types/utils/viewForwardedRef'; @@ -48,6 +49,7 @@ function BaseAutoCompleteSuggestions( const StyleUtils = useStyleUtils(); const rowHeight = useSharedValue(0); const scrollRef = useRef>(null); + const {activeID} = useSuggestionsContext(); /** * Render a suggestion menu item component. */ @@ -69,7 +71,7 @@ function BaseAutoCompleteSuggestions( ); const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length; - const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBeDisplayedBelowParentContainer)); + const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBeDisplayedBelowParentContainer, Boolean(activeID))); const estimatedListSize = useMemo( () => ({ height: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length, diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx index 9118e80ef230..36ed962010ab 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx @@ -7,15 +7,19 @@ type SuggestionsContextProviderProps = { }; type SuggestionsContextProps = { + activeID: string | null; currentActiveSuggestionsRef: MutableRefObject; updateCurrentActiveSuggestionsRef: (ref: SuggestionsRef | null, id: string) => void; clearActiveSuggestionsRef: () => void; + isActiveSuggestions: (id: string) => boolean; }; const SuggestionsContext = createContext({ + activeID: null, currentActiveSuggestionsRef: {current: null}, updateCurrentActiveSuggestionsRef: () => {}, clearActiveSuggestionsRef: () => {}, + isActiveSuggestions: () => false, }); function SuggestionsContextProvider({children}: SuggestionsContextProviderProps) { @@ -32,9 +36,11 @@ function SuggestionsContextProvider({children}: SuggestionsContextProviderProps) setActiveID(null); }, []); + const isActiveSuggestions = useCallback((id: string) => id === activeID, [activeID]); + const contextValue = useMemo( - () => ({activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef}), - [activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef], + () => ({activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef, isActiveSuggestions}), + [activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef, isActiveSuggestions], ); return {children}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 858f33a5a8dc..294f7e83d66e 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -83,7 +83,7 @@ function ReportActionItemMessageEdit( const {translate, preferredLocale} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); const {isSmallScreenWidth} = useWindowDimensions(); - const {updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef} = useSuggestionsContext(); + const {updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef, isActiveSuggestions} = useSuggestionsContext(); const getInitialDraft = () => { if (draftMessage === action?.message?.[0].html) { @@ -212,6 +212,9 @@ function ReportActionItemMessageEdit( if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { ReportActionContextMenu.clearActiveReportAction(); } + if (isActiveSuggestions(action.reportActionID)) { + clearActiveSuggestionsRef(); + } // Show the main composer when the focused message is deleted from another client // to prevent the main composer stays hidden until we swtich to another chat. diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 2a05278dec6c..1fde098c4aeb 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -844,17 +844,18 @@ const shouldPreventScroll = shouldPreventScrollOnAutoCompleteSuggestion(); /** * Gets the correct position for auto complete suggestion container */ -function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBeDisplayedBelowParentContainer: boolean): ViewStyle { +function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBeDisplayedBelowParentContainer: boolean, isEditComposer: boolean): ViewStyle { 'worklet'; const borderWidth = 2; const height = itemsHeight + 2 * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING + (shouldPreventScroll ? borderWidth : 0); + const suggestionsPadding = isEditComposer ? CONST.AUTO_COMPLETE_SUGGESTER.EDIT_SUGGESTER_PADDING : CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING; // The suggester is positioned absolutely within the component that includes the input and RecipientLocalTime view (for non-expanded mode only). To position it correctly, // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. return { overflow: 'hidden', - top: -(height + (shouldBeDisplayedBelowParentContainer ? -2 : 1) * (CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + (shouldPreventScroll ? 0 : borderWidth))), + top: -(height + (shouldBeDisplayedBelowParentContainer ? -2 : 1) * (suggestionsPadding + (shouldPreventScroll ? 0 : borderWidth))), height, minHeight: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, }; From 7dc206cf66ae8f96ddea9883b16334618b4a5379 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 18 Mar 2024 19:57:19 -0300 Subject: [PATCH 040/961] feat: change customRateID when participant changes, other improvements --- ...oraryForRefactorRequestConfirmationList.js | 25 ++++--- .../ReportActionItem/MoneyRequestView.tsx | 72 +++++++++---------- src/libs/DistanceRequestUtils.ts | 3 +- src/libs/TransactionUtils.ts | 9 +++ src/libs/actions/IOU.ts | 6 +- ...yForRefactorRequestParticipantsSelector.js | 2 +- .../step/IOURequestStepParticipants.js | 35 ++++++++- .../iou/request/step/IOURequestStepRate.tsx | 28 +++++--- 8 files changed, 116 insertions(+), 64 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 93831606416e..ce5668924271 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -224,7 +224,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ isReadOnly, isScanRequest, listStyles, - mileageRate, + mileageRates, onConfirm, onSelectParticipant, onSendMoney, @@ -242,7 +242,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, - mileageRates, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -254,14 +253,19 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSend = iouType === CONST.IOU.TYPE.SEND; const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isTypeSplit); - // TODO: uncomment after Splits and P2P are enabled https://github.com/Expensify/App/pull/37185, mileageRate prop should be be removed - // const mileageRate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' ? DistanceRequestUtils.getRateForP2P(policy.outputCurrency) : mileageRates[transaction.comment.customUnit.customUnitRateID]; - const {unit, rate, currency} = mileageRate; + const personalPolicy = policyID === CONST.POLICY.ID_FAKE ? PolicyUtils.getPersonalPolicy() : policy; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) + ? DistanceRequestUtils.getRateForP2P(personalPolicy.outputCurrency) + : mileageRates[transaction.comment.customUnit.customUnitRateID]; - // will hardcoded rates will have a currency property? If not we have to get currency other way + const {unit, rate} = mileageRate || { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + rate: CONST.CUSTOM_UNITS.MILEAGE_IRS_RATE * 100, + }; + + const currency = personalPolicy.outputCurrency; const distance = lodashGet(transaction, 'routes.route0.distance', 0); - const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; const taxRates = lodashGet(policy, 'taxRates', {}); // A flag for showing the categories field @@ -292,7 +296,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const formattedAmount = isDistanceRequestWithPendingRoute ? '' : CurrencyUtils.convertToDisplayString( - shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, + isDistanceRequest && iouAmount === 0 ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, isDistanceRequest ? currency : iouCurrencyCode, ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode); @@ -359,13 +363,13 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit, isMerchantRequired, merchantError]); useEffect(() => { - if (!shouldCalculateDistanceAmount) { + if (!isDistanceRequest) { return; } const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate); IOU.setMoneyRequestAmount_temporaryForRefactor(transaction.transactionID, amount, currency); - }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency]); + }, [isDistanceRequest, distance, rate, unit, transaction, currency]); /** * Returns the participants with amount @@ -757,7 +761,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ item: ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} - /> - - - {}} - /> - - - ) : ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} - /> - - ) + <> + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + + {}} + /> + + + ) : ( + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + ); return ( diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index e954ce4d266d..28714a92ed84 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -91,8 +91,7 @@ function convertDistanceUnit(distanceInMeters: number, unit: Unit): number { */ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string { const convertedDistance = convertDistanceUnit(distanceInMeters, unit); - // TODO: add logic for currencies for which we need to round to 4 decimals - return convertedDistance.toFixed(3); + return convertedDistance.toFixed(2); } // TODO: I wonder if it would be better to refactor these functions to pass params in an object diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index bc94c8fee8fc..f694199fbeb4 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -584,6 +584,14 @@ function getEnabledTaxRateCount(options: TaxRates) { return Object.values(options).filter((option: TaxRate) => !option.isDisabled).length; } +/** + * Check if the customUnitRateID has a value default for P2P distance requests + */ + +function isCustomUnitRateIDForP2P(transaction: Transaction): boolean { + return transaction?.comment?.customUnit?.customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID; +} + export { buildOptimisticTransaction, calculateTaxAmount, @@ -633,6 +641,7 @@ export { waypointHasValidAddress, getRecentTransactions, hasViolation, + isCustomUnitRateIDForP2P, }; export type {TransactionChanges}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 477fb99900b8..31cd49269e2c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -253,8 +253,9 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry, waypoint1: {}, }; const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; - if (ReportUtils.isPolicyExpenseChat(report)) { + if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; } comment.customUnit = {customUnitRateID}; @@ -373,9 +374,8 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: } /** Set the last selected distance rate for policy */ -// TODO: probably need to be changed function setLastSelectedDistanceRates(policyID: string, rateID: string) { - Onyx.merge('lastSelectedDistanceRates', {[policyID]: rateID}); + Onyx.merge(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, {[policyID]: rateID}); } /** Update transaction distance rate */ diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index cdc3e72f98f4..7bdcafef61e6 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -190,7 +190,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const addSingleParticipant = (option) => { onParticipantsAdded([ { - ..._.pick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText'), + ..._.pick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText', 'policyID'), selected: true, }, ]); diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 5ca465d8fb78..8c92961a28c4 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -1,16 +1,21 @@ import {useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; import StepScreenWrapper from './StepScreenWrapper'; @@ -24,6 +29,9 @@ const propTypes = { /* Onyx Props */ /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, + + // eslint-disable-next-line + lastSelectedDistanceRates: PropTypes.object, }; const defaultProps = { @@ -36,6 +44,7 @@ function IOURequestStepParticipants({ }, transaction, transaction: {participants = []}, + lastSelectedDistanceRates = {}, }) { const {translate} = useLocalize(); const navigation = useNavigation(); @@ -100,6 +109,22 @@ function IOURequestStepParticipants({ } IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); + + // change customUnitRateID when choosing + if (val[0].isPolicyExpenseChat && TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { + let customUnitRateID = ''; + if (val[0].policyID && lastSelectedDistanceRates[val[0].policyID]) { + customUnitRateID = lastSelectedDistanceRates[val[0].policyID]; + } else { + const policy = ReportUtils.getPolicy(val[0].policyID); + const defaultRate = DistanceRequestUtils.getDefaultMileageRate(policy); + customUnitRateID = defaultRate ? defaultRate.customUnitRateID : ''; + } + IOU.updateDistanceRequestRate(transactionID, customUnitRateID); + } else if (!TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { + IOU.updateDistanceRequestRate(transactionID, CONST.CUSTOM_UNITS.FAKE_P2P_ID); + } + numberOfParticipants.current = val.length; // When multiple participants are selected, the reportID is generated at the end of the confirmation step. @@ -163,4 +188,12 @@ IOURequestStepParticipants.displayName = 'IOURequestStepParticipants'; IOURequestStepParticipants.propTypes = propTypes; IOURequestStepParticipants.defaultProps = defaultProps; -export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepParticipants); +export default compose( + withWritableReportOrNotFound, + withFullTransactionOrNotFound, + withOnyx({ + lastSelectedDistanceRates: { + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + }, + }), +)(IOURequestStepParticipants); diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index a2cf7a0af1c8..10513f7a44ca 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -6,19 +6,21 @@ import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as IOU from '@libs/actions/IOU'; import compose from '@libs/compose'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as IOU from '@libs/actions/IOU'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; + type Props = { - // eslint-disable-next-line react/no-unused-prop-types - lastSelectedDistanceRate: string; + /** Object of last selected rates for the policies */ + lastSelectedDistanceRates: Record; /** Policy details */ policy: OnyxEntry; @@ -38,11 +40,14 @@ function IOURequestStepRate({ route: { params: {backTo}, }, + lastSelectedDistanceRates = {}, }: Props) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const rates = DistanceRequestUtils.getMileageRates(policy?.id); + const lastSelectedRate = lastSelectedDistanceRates[policy?.id ?? '0'] ?? '0'; + const data = Object.values(rates).map((rate) => ({ text: rate.name ?? '', alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), @@ -50,9 +55,10 @@ function IOURequestStepRate({ value: rate.customUnitRateID, })); - const selectDistanceRate = (customUnitRateID) => { + const initiallyFocusedOption = rates[lastSelectedRate]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; + + function selectDistanceRate(customUnitRateID = '0') { IOU.setLastSelectedDistanceRates(policy?.id ?? '', customUnitRateID); - // TODO: get a proper transaction ID IOU.updateDistanceRequestRate('1', customUnitRateID); Navigation.goBack(backTo); } @@ -70,8 +76,7 @@ function IOURequestStepRate({ sections={[{data}]} ListItem={RadioListItem} onSelectRow={({value}) => selectDistanceRate(value)} - // TODO: change for lastSelectedDistanceRates - initiallyFocusedOptionKey="Default Rate" + initiallyFocusedOptionKey={initiallyFocusedOption} /> ); @@ -82,11 +87,14 @@ IOURequestStepRate.displayName = 'IOURequestStepRate'; export default compose( withWritableReportOrNotFound, withOnyx({ + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS policy: { + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, - // lastSelectedDistanceRates: { - // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - // }, + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS + lastSelectedDistanceRates: { + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + }, }), )(IOURequestStepRate); From 07db2abc4a3a1d5e6d64ce12cc0ec21274946d96 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 19 Mar 2024 17:48:30 +0700 Subject: [PATCH 041/961] update variable name --- src/components/AutoCompleteSuggestions/index.tsx | 14 +++++++------- .../home/report/ReportActionItemMessageEdit.tsx | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index 1f5d87b15962..66ea0de6f9f3 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -19,13 +19,13 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} const StyleUtils = useStyleUtils(); const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); - const suggestionContainerHeight = measureHeightOfSuggestionsContainer(props.suggestions.length, props.isSuggestionPickerLarge); + const suggestionsContainerHeight = measureHeightOfSuggestionsContainer(props.suggestions.length, props.isSuggestionPickerLarge); const [{width, left, bottom}, setContainerState] = React.useState({ width: 0, left: 0, bottom: 0, }); - const [shouldBelowContainer, setShouldBelowContainer] = React.useState(false); + const [shouldShowBelowContainer, setShouldShowBelowContainer] = React.useState(false); React.useEffect(() => { const container = containerRef.current; if (!container) { @@ -46,17 +46,17 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} } measureParentContainer((x, y, w, h) => { - const currenBottom = y < suggestionContainerHeight ? windowHeight - y - suggestionContainerHeight - h : windowHeight - y; - setShouldBelowContainer(y < suggestionContainerHeight); - setContainerState({left: x, bottom: currenBottom, width: w}); + const currentBottom = y < suggestionsContainerHeight ? windowHeight - y - suggestionsContainerHeight - h : windowHeight - y; + setShouldShowBelowContainer(y < suggestionsContainerHeight); + setContainerState({left: x, bottom: currentBottom, width: w}); }); - }, [measureParentContainer, windowHeight, windowWidth, suggestionContainerHeight]); + }, [measureParentContainer, windowHeight, windowWidth, suggestionsContainerHeight]); const componentToRender = ( // eslint-disable-next-line react/jsx-props-no-spreading {...props} - shouldBeDisplayedBelowParentContainer={shouldBelowContainer} + shouldBeDisplayedBelowParentContainer={shouldShowBelowContainer} ref={containerRef} /> ); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 294f7e83d66e..85f206cb94b0 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -392,7 +392,6 @@ function ReportActionItemMessageEdit( } containerRef.current.measureInWindow(callback); }, - // We added isComposerFullSize in dependencies so that when this value changes, we recalculate the position of the popup // eslint-disable-next-line react-hooks/exhaustive-deps [], ); From a0227e07854c76bbc9039d5df2971de4c5294a5f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 19 Mar 2024 09:42:20 -0300 Subject: [PATCH 042/961] fix: run prettier --- src/pages/iou/request/step/IOURequestStepRate.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 10513f7a44ca..583fa8c055c7 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -17,7 +17,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; - type Props = { /** Object of last selected rates for the policies */ lastSelectedDistanceRates: Record; From eb350eec27ba9858929864cdc29aada4453b88e1 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 19 Mar 2024 09:53:29 -0300 Subject: [PATCH 043/961] fix: lint fix --- src/pages/iou/request/step/IOURequestStepParticipants.js | 2 +- src/pages/iou/request/step/IOURequestStepRate.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 8c92961a28c4..a66387709f61 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -137,7 +137,7 @@ function IOURequestStepParticipants({ // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. selectedReportID.current = lodashGet(val, '[0].reportID', reportID); }, - [reportID, transactionID, iouType, participants, updateRouteParams], + [iouType, participants, transactionID, transaction, reportID, updateRouteParams, lastSelectedDistanceRates], ); const goToNextStep = useCallback( diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 583fa8c055c7..de570ca3c67f 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -10,12 +10,12 @@ import * as IOU from '@libs/actions/IOU'; import compose from '@libs/compose'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; -import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; +import StepScreenWrapper from './StepScreenWrapper'; +import withWritableReportOrNotFound from './withWritableReportOrNotFound'; type Props = { /** Object of last selected rates for the policies */ From 54d0bb9d340926b8997c8b1d584321554d49ecec Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 20 Mar 2024 12:08:37 +0700 Subject: [PATCH 044/961] fix lint --- src/components/Image/index.tsx | 36 +++++++++++++++++++-- src/components/Image/types.ts | 7 ++-- src/components/ImageWithSizeCalculation.tsx | 5 +-- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index f4e5bf4834e0..c8c78b54de06 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -1,11 +1,39 @@ -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import BaseImage from './BaseImage'; -import type {ImageOnyxProps, ImageOwnProps, ImageProps} from './types'; +import type {ImageOnLoadEvent, ImageOnyxProps, ImageOwnProps, ImageProps} from './types'; -function Image({source: propsSource, isAuthTokenRequired = false, session, ...forwardedProps}: ImageProps) { +function Image({source: propsSource, isAuthTokenRequired = false, session, onLoad, objectPositionTop, style, ...forwardedProps}: ImageProps) { + const [aspectRatio, setAspectRatio] = useState(null); + + const updateAspectRatio = useCallback( + (width: number, height: number) => { + if (!objectPositionTop) { + return; + } + + if (width > height) { + setAspectRatio(1); + return; + } + + setAspectRatio(height ? width / height : 'auto'); + }, + [objectPositionTop], + ); + + const handleLoad = useCallback( + (event: ImageOnLoadEvent) => { + const {width, height} = event.nativeEvent; + + onLoad?.(event); + + updateAspectRatio(width, height); + }, + [onLoad, updateAspectRatio], + ); /** * Check if the image source is a URL - if so the `encryptedAuthToken` is appended * to the source. @@ -34,6 +62,8 @@ function Image({source: propsSource, isAuthTokenRequired = false, session, ...fo ); diff --git a/src/components/Image/types.ts b/src/components/Image/types.ts index 2a5fcbb19324..c2f73e74ee34 100644 --- a/src/components/Image/types.ts +++ b/src/components/Image/types.ts @@ -23,12 +23,12 @@ type BaseImageProps = { /** Event for when the image is fully loaded and returns the natural dimensions of the image */ onLoad?: (event: ImageOnLoadEvent) => void; -}; -type ImageOwnProps = BaseImageProps & { /** Styles for the Image */ style?: StyleProp; +}; +type ImageOwnProps = BaseImageProps & { /** Should an auth token be included in the image request */ isAuthTokenRequired?: boolean; @@ -46,6 +46,9 @@ type ImageOwnProps = BaseImageProps & { /** Progress events while the image is downloading */ onProgress?: () => void; + + /** Whether we should show the top of the image */ + objectPositionTop?: boolean; }; type ImageProps = ImageOnyxProps & ImageOwnProps; diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index 46e1308692ba..ffa9517299cc 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -33,7 +33,8 @@ type ImageWithSizeCalculationProps = { /** Whether the image requires an authToken */ isAuthTokenRequired: boolean; - objectPositionTop: boolean; + /** Whether we should show the top of the image */ + objectPositionTop?: boolean; }; /** @@ -42,7 +43,7 @@ type ImageWithSizeCalculationProps = { * performing some calculation on a network image after fetching dimensions so * it can be appropriately resized. */ -function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthTokenRequired, objectPositionTop}: ImageWithSizeCalculationProps) { +function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthTokenRequired, objectPositionTop = false}: ImageWithSizeCalculationProps) { const styles = useThemeStyles(); const isLoadedRef = useRef(null); const [isImageCached, setIsImageCached] = useState(true); From c093a99e4fcbf720603f836be8cadca5d71217c1 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 20 Mar 2024 14:54:49 +0700 Subject: [PATCH 045/961] fix portal on native --- src/components/AutoCompleteSuggestions/index.native.tsx | 4 +++- .../home/report/ReportActionCompose/ReportActionCompose.tsx | 2 +- src/pages/home/report/ReportActionItemMessageEdit.tsx | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/index.native.tsx b/src/components/AutoCompleteSuggestions/index.native.tsx index fbfa7d953581..526954d370e3 100644 --- a/src/components/AutoCompleteSuggestions/index.native.tsx +++ b/src/components/AutoCompleteSuggestions/index.native.tsx @@ -1,11 +1,13 @@ import {Portal} from '@gorhom/portal'; import React from 'react'; +import {useSuggestionsContext} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; function AutoCompleteSuggestions({measureParentContainer, ...props}: AutoCompleteSuggestionsProps) { + const {activeID} = useSuggestionsContext(); return ( - + {/* eslint-disable-next-line react/jsx-props-no-spreading */} {...props} /> diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 1e0e322be258..4c767eeb7966 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -364,7 +364,7 @@ function ReportActionCompose({ {shouldShowReportRecipientLocalTime && hasReportRecipient && } - + - + Date: Wed, 20 Mar 2024 15:02:00 +0700 Subject: [PATCH 046/961] rename --- .../ComposerWithSuggestionsEdit/SuggestionsContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx index 36ed962010ab..ceecb56af450 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx @@ -51,6 +51,6 @@ function useSuggestionsContext() { return context; } -SuggestionsContextProvider.displayName = 'PlaybackContextProvider'; +SuggestionsContextProvider.displayName = 'SuggestionsContextProvider'; export {SuggestionsContextProvider, useSuggestionsContext}; From 5b1c8835707f9c7c8daf6ed6283a2e97f6d75707 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 17:33:55 +0700 Subject: [PATCH 047/961] replace objectPositionTop with objectPosition --- src/CONST.ts | 5 +++++ src/components/DistanceEReceipt.tsx | 2 +- src/components/Image/index.tsx | 9 +++++---- src/components/Image/types.ts | 10 +++++++--- src/components/ImageWithSizeCalculation.tsx | 10 ++++++---- src/components/MoneyRequestConfirmationList.tsx | 2 +- ...MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- .../ReportActionItem/ReportActionItemImage.tsx | 4 ++-- src/components/ThumbnailImage.tsx | 10 +++++++--- 9 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index bb191ac5e028..3cd473f2f451 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1095,6 +1095,11 @@ const CONST = { JPEG: 'image/jpeg', }, + IMAGE_OBJECT_POSITION: { + TOP: 'top', + INITIAL: 'initial', + }, + FILE_TYPE_REGEX: { // Image MimeTypes allowed by iOS photos app. IMAGE: /\.(jpg|jpeg|png|webp|gif|tiff|bmp|heic|heif)$/, diff --git a/src/components/DistanceEReceipt.tsx b/src/components/DistanceEReceipt.tsx index 864dc831f74c..03ae1cdef5ad 100644 --- a/src/components/DistanceEReceipt.tsx +++ b/src/components/DistanceEReceipt.tsx @@ -63,7 +63,7 @@ function DistanceEReceipt({transaction}: DistanceEReceiptProps) { style={[styles.w100, styles.h100]} isAuthTokenRequired shouldDynamicallyResize={false} - objectPositionTop + objectPosition /> )} diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index c8c78b54de06..e6cecdc0d5ec 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -5,12 +5,13 @@ import ONYXKEYS from '@src/ONYXKEYS'; import BaseImage from './BaseImage'; import type {ImageOnLoadEvent, ImageOnyxProps, ImageOwnProps, ImageProps} from './types'; -function Image({source: propsSource, isAuthTokenRequired = false, session, onLoad, objectPositionTop, style, ...forwardedProps}: ImageProps) { +function Image({source: propsSource, isAuthTokenRequired = false, session, onLoad, objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, style, ...forwardedProps}: ImageProps) { const [aspectRatio, setAspectRatio] = useState(null); + const isObjectPositionTop = objectPosition === CONST.IMAGE_OBJECT_POSITION.TOP; const updateAspectRatio = useCallback( (width: number, height: number) => { - if (!objectPositionTop) { + if (!isObjectPositionTop) { return; } @@ -21,7 +22,7 @@ function Image({source: propsSource, isAuthTokenRequired = false, session, onLoa setAspectRatio(height ? width / height : 'auto'); }, - [objectPositionTop], + [isObjectPositionTop], ); const handleLoad = useCallback( @@ -63,7 +64,7 @@ function Image({source: propsSource, isAuthTokenRequired = false, session, onLoa // eslint-disable-next-line react/jsx-props-no-spreading {...forwardedProps} onLoad={handleLoad} - style={[style, aspectRatio ? {aspectRatio, height: 'auto'} : {}, objectPositionTop && !aspectRatio && {opacity: 0}]} + style={[style, aspectRatio ? {aspectRatio, height: 'auto'} : {}, isObjectPositionTop && !aspectRatio && {opacity: 0}]} source={source} /> ); diff --git a/src/components/Image/types.ts b/src/components/Image/types.ts index c2f73e74ee34..27964d8a6764 100644 --- a/src/components/Image/types.ts +++ b/src/components/Image/types.ts @@ -1,10 +1,14 @@ import type {ImageSource} from 'expo-image'; import type {ImageRequireSource, ImageResizeMode, ImageStyle, ImageURISource, StyleProp} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; import type {Session} from '@src/types/onyx'; type ExpoImageSource = ImageSource | number | ImageSource[]; +type ImageObjectPosition = ValueOf; + type ImageOnyxProps = { /** Session info for the currently logged in user. */ session: OnyxEntry; @@ -47,10 +51,10 @@ type ImageOwnProps = BaseImageProps & { /** Progress events while the image is downloading */ onProgress?: () => void; - /** Whether we should show the top of the image */ - objectPositionTop?: boolean; + /** The object position of image */ + objectPosition?: ImageObjectPosition; }; type ImageProps = ImageOnyxProps & ImageOwnProps; -export type {BaseImageProps, ImageOwnProps, ImageOnyxProps, ImageProps, ExpoImageSource, ImageOnLoadEvent}; +export type {BaseImageProps, ImageOwnProps, ImageOnyxProps, ImageProps, ExpoImageSource, ImageOnLoadEvent, ImageObjectPosition}; diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index ffa9517299cc..eac5c676370b 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -5,9 +5,11 @@ import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; +import CONST from '@src/CONST'; import FullscreenLoadingIndicator from './FullscreenLoadingIndicator'; import Image from './Image'; import RESIZE_MODES from './Image/resizeModes'; +import type {ImageObjectPosition} from './Image/types'; type OnMeasure = (args: {width: number; height: number}) => void; @@ -33,8 +35,8 @@ type ImageWithSizeCalculationProps = { /** Whether the image requires an authToken */ isAuthTokenRequired: boolean; - /** Whether we should show the top of the image */ - objectPositionTop?: boolean; + /** The object position of image */ + objectPosition?: ImageObjectPosition; }; /** @@ -43,7 +45,7 @@ type ImageWithSizeCalculationProps = { * performing some calculation on a network image after fetching dimensions so * it can be appropriately resized. */ -function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthTokenRequired, objectPositionTop = false}: ImageWithSizeCalculationProps) { +function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthTokenRequired, objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL}: ImageWithSizeCalculationProps) { const styles = useThemeStyles(); const isLoadedRef = useRef(null); const [isImageCached, setIsImageCached] = useState(true); @@ -104,7 +106,7 @@ function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthT }} onError={onError} onLoad={imageLoadedSuccessfully} - objectPositionTop={objectPositionTop} + objectPosition={objectPosition} /> {isLoading && !isImageCached && } diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index b2ea75cae8b0..75d156e58eaa 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -614,7 +614,7 @@ function MoneyRequestConfirmationList({ // but we don't need it to load the blob:// or file:// image when starting a money request / split bill // So if we have a thumbnail, it means we're retrieving the image from the server isAuthTokenRequired={!!receiptThumbnail} - objectPositionTop + objectPosition={CONST.IMAGE_OBJECT_POSITION.TOP} /> ) : ( // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate") diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 08957c96128a..c32241d92096 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -906,7 +906,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // but we don't need it to load the blob:// or file:// image when starting a money request / split bill // So if we have a thumbnail, it means we're retrieving the image from the server isAuthTokenRequired={!_.isEmpty(receiptThumbnail)} - objectPositionTop + objectPosition={CONST.IMAGE_OBJECT_POSITION.TOP} /> ), [receiptFilename, receiptImage, styles, receiptThumbnail, isLocalFile, isAttachmentInvalid], diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index b39cabf4e74e..3c6f6e7e2421 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -85,7 +85,7 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, tr fallbackIcon={Expensicons.Receipt} fallbackIconSize={isSingleImage ? variables.iconSizeSuperLarge : variables.iconSizeExtraLarge} shouldDynamicallyResize={false} - objectPositionTop + objectPosition={CONST.IMAGE_OBJECT_POSITION.TOP} /> ); } else if (isLocalFile && filename && Str.isPDF(filename) && typeof attachmentModalSource === 'string') { @@ -100,7 +100,7 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, tr ); } diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index d1bdfdf3ca9f..8b8da81c727a 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -6,9 +6,11 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useThumbnailDimensions from '@hooks/useThumbnailDimensions'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; +import type {ImageObjectPosition} from './Image/types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; type ThumbnailImageProps = { @@ -35,7 +37,9 @@ type ThumbnailImageProps = { /** Should the image be resized on load or just fit container */ shouldDynamicallyResize?: boolean; - objectPositionTop?: boolean; + + /** The object position of image */ + objectPosition?: ImageObjectPosition; }; type UpdateImageSizeParams = { @@ -52,7 +56,7 @@ function ThumbnailImage({ shouldDynamicallyResize = true, fallbackIcon = Expensicons.Gallery, fallbackIconSize = variables.iconSizeSuperLarge, - objectPositionTop = false, + objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, }: ThumbnailImageProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -104,7 +108,7 @@ function ThumbnailImage({ onMeasure={updateImageSize} onLoadFailure={() => setFailedToLoad(true)} isAuthTokenRequired={isAuthTokenRequired} - objectPositionTop={objectPositionTop} + objectPosition={objectPosition} /> From 115ec923108600f1ad167783d5acb23417effe5c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 17:37:50 +0700 Subject: [PATCH 048/961] update objectPosition prop --- src/components/DistanceEReceipt.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/DistanceEReceipt.tsx b/src/components/DistanceEReceipt.tsx index 03ae1cdef5ad..3d2a6a3e57b6 100644 --- a/src/components/DistanceEReceipt.tsx +++ b/src/components/DistanceEReceipt.tsx @@ -18,6 +18,7 @@ import PendingMapView from './MapView/PendingMapView'; import ScrollView from './ScrollView'; import Text from './Text'; import ThumbnailImage from './ThumbnailImage'; +import CONST from '@src/CONST'; type DistanceEReceiptProps = { /** The transaction for the distance request */ @@ -63,7 +64,7 @@ function DistanceEReceipt({transaction}: DistanceEReceiptProps) { style={[styles.w100, styles.h100]} isAuthTokenRequired shouldDynamicallyResize={false} - objectPosition + objectPosition={CONST.IMAGE_OBJECT_POSITION.TOP} /> )} From 1562a88172728730d3ad58cad1abc0e1529f7a28 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 18:05:42 +0700 Subject: [PATCH 049/961] fix order import --- src/components/DistanceEReceipt.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DistanceEReceipt.tsx b/src/components/DistanceEReceipt.tsx index 3d2a6a3e57b6..2d6e3e56210f 100644 --- a/src/components/DistanceEReceipt.tsx +++ b/src/components/DistanceEReceipt.tsx @@ -8,6 +8,7 @@ import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; +import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {Transaction} from '@src/types/onyx'; import type {WaypointCollection} from '@src/types/onyx/Transaction'; @@ -18,7 +19,6 @@ import PendingMapView from './MapView/PendingMapView'; import ScrollView from './ScrollView'; import Text from './Text'; import ThumbnailImage from './ThumbnailImage'; -import CONST from '@src/CONST'; type DistanceEReceiptProps = { /** The transaction for the distance request */ From d296c84255fe8a96ff0f43e8b22aff1d95608f08 Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Thu, 21 Mar 2024 18:12:58 +0100 Subject: [PATCH 050/961] Turn on Onboarding Flow Stage 1 --- src/ROUTES.ts | 4 - src/SCREENS.ts | 7 - src/languages/en.ts | 22 --- src/languages/es.ts | 22 --- .../AppNavigator/ModalStackNavigators.tsx | 8 - .../Navigators/RightModalNavigator.tsx | 4 - .../BottomTabBar.tsx | 12 +- src/libs/Navigation/linkingConfig/config.ts | 7 - src/libs/Navigation/types.ts | 8 - src/libs/actions/Welcome.ts | 6 +- .../ExpensifyClassicPage.tsx | 75 --------- .../ManageTeamsExpensesPage.tsx | 122 -------------- .../PurposeForUsingExpensifyPage.tsx | 155 ------------------ src/styles/theme/themes/dark.ts | 8 - src/styles/theme/themes/light.ts | 8 - 15 files changed, 2 insertions(+), 466 deletions(-) delete mode 100644 src/pages/OnboardEngagement/ExpensifyClassicPage.tsx delete mode 100644 src/pages/OnboardEngagement/ManageTeamsExpensesPage.tsx delete mode 100644 src/pages/OnboardEngagement/PurposeForUsingExpensifyPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c326b1e69e08..83aff058a41a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -420,10 +420,6 @@ const ROUTES = { NEW_TASK_TITLE: 'new/task/title', NEW_TASK_DESCRIPTION: 'new/task/description', - ONBOARD: 'onboard', - ONBOARD_MANAGE_EXPENSES: 'onboard/manage-expenses', - ONBOARD_EXPENSIFY_CLASSIC: 'onboard/expensify-classic', - TEACHERS_UNITE: 'teachersunite', I_KNOW_A_TEACHER: 'teachersunite/i-know-a-teacher', I_AM_A_TEACHER: 'teachersunite/i-am-a-teacher', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 3cf747aa3859..45d4c79ca764 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -109,7 +109,6 @@ const SCREENS = { PARTICIPANTS: 'Participants', MONEY_REQUEST: 'MoneyRequest', NEW_TASK: 'NewTask', - ONBOARD_ENGAGEMENT: 'Onboard_Engagement', TEACHERS_UNITE: 'TeachersUnite', TASK_DETAILS: 'Task_Details', ENABLE_PAYMENTS: 'EnablePayments', @@ -274,12 +273,6 @@ const SCREENS = { PURPOSE: 'Onboarding_Purpose', }, - ONBOARD_ENGAGEMENT: { - ROOT: 'Onboard_Engagement_Root', - MANAGE_TEAMS_EXPENSES: 'Manage_Teams_Expenses', - EXPENSIFY_CLASSIC: 'Expenisfy_Classic', - }, - WELCOME_VIDEO: { ROOT: 'Welcome_Video_Root', }, diff --git a/src/languages/en.ts b/src/languages/en.ts index 92522b51d1f6..37b4b26ea45b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2512,28 +2512,6 @@ export default { }, copyReferralLink: 'Copy invite link', }, - purposeForExpensify: { - [CONST.INTRO_CHOICES.TRACK]: 'Track business spend for taxes', - [CONST.INTRO_CHOICES.SUBMIT]: 'Get paid back by my employer', - [CONST.INTRO_CHOICES.MANAGE_TEAM]: "Manage my team's expenses", - [CONST.INTRO_CHOICES.CHAT_SPLIT]: 'Chat and split bills with friends', - welcomeMessage: 'Welcome to Expensify', - welcomeSubtitle: 'What would you like to do?', - }, - manageTeams: { - [CONST.MANAGE_TEAMS_CHOICE.MULTI_LEVEL]: 'Multi level approval', - [CONST.MANAGE_TEAMS_CHOICE.CUSTOM_EXPENSE]: 'Custom expense coding', - [CONST.MANAGE_TEAMS_CHOICE.CARD_TRACKING]: 'Company card tracking', - [CONST.MANAGE_TEAMS_CHOICE.ACCOUNTING]: 'Accounting integrations', - [CONST.MANAGE_TEAMS_CHOICE.RULE]: 'Rule enforcement', - title: 'Do you require any of the following features?', - }, - expensifyClassic: { - title: "Expensify Classic has everything you'll need", - firstDescription: "While we're busy working on New Expensify, it currently doesn't support some of the features you're looking for.", - secondDescription: "Don't worry, Expensify Classic has everything you need.", - buttonText: 'Take me to Expensify Classic', - }, violations: { allTagLevelsRequired: 'All tags required', autoReportedRejectedExpense: ({rejectReason, rejectedBy}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rejected this expense with the comment "${rejectReason}"`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 95a84bcf59a0..f4f7b02124eb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3004,28 +3004,6 @@ export default { }, copyReferralLink: 'Copiar enlace de invitación', }, - purposeForExpensify: { - [CONST.INTRO_CHOICES.TRACK]: 'Seguimiento de los gastos de empresa para fines fiscales', - [CONST.INTRO_CHOICES.SUBMIT]: 'Reclamar gastos a mi empleador', - [CONST.INTRO_CHOICES.MANAGE_TEAM]: 'Gestionar los gastos de mi equipo', - [CONST.INTRO_CHOICES.CHAT_SPLIT]: 'Chatea y divide gastos con tus amigos', - welcomeMessage: 'Bienvenido a Expensify', - welcomeSubtitle: '¿Qué te gustaría hacer?', - }, - manageTeams: { - [CONST.MANAGE_TEAMS_CHOICE.MULTI_LEVEL]: 'Aprobación multinivel', - [CONST.MANAGE_TEAMS_CHOICE.CUSTOM_EXPENSE]: 'Codificación personalizada de gastos', - [CONST.MANAGE_TEAMS_CHOICE.CARD_TRACKING]: 'Seguimiento de tarjetas corporativas', - [CONST.MANAGE_TEAMS_CHOICE.ACCOUNTING]: 'Integraciones de contaduría', - [CONST.MANAGE_TEAMS_CHOICE.RULE]: 'Aplicación de reglas', - title: '¿Necesitas alguna de las siguientes funciones?', - }, - expensifyClassic: { - title: 'Expensify Classic tiene todo lo que necesitas', - firstDescription: 'Aunque estamos ocupados trabajando en el Nuevo Expensify, actualmente no soporta algunas de las funciones que estás buscando.', - secondDescription: 'No te preocupes, Expensify Classic tiene todo lo que necesitas.', - buttonText: 'Llévame a Expensify Classic', - }, violations: { allTagLevelsRequired: 'Todas las etiquetas son obligatorias', autoReportedRejectedExpense: ({rejectedBy, rejectReason}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rechazó la solicitud y comentó "${rejectReason}"`, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index bd5bfc46134a..370324602a0a 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -12,7 +12,6 @@ import type { MoneyRequestNavigatorParamList, NewChatNavigatorParamList, NewTaskNavigatorParamList, - OnboardEngagementNavigatorParamList, ParticipantsNavigatorParamList, PrivateNotesNavigatorParamList, ProfileNavigatorParamList, @@ -172,12 +171,6 @@ const NewTaskModalStackNavigator = createModalStackNavigator require('../../../pages/tasks/NewTaskDescriptionPage').default as React.ComponentType, }); -const OnboardEngagementModalStackNavigator = createModalStackNavigator({ - [SCREENS.ONBOARD_ENGAGEMENT.ROOT]: () => require('../../../pages/OnboardEngagement/PurposeForUsingExpensifyPage').default as React.ComponentType, - [SCREENS.ONBOARD_ENGAGEMENT.MANAGE_TEAMS_EXPENSES]: () => require('../../../pages/OnboardEngagement/ManageTeamsExpensesPage').default as React.ComponentType, - [SCREENS.ONBOARD_ENGAGEMENT.EXPENSIFY_CLASSIC]: () => require('../../../pages/OnboardEngagement/ExpensifyClassicPage').default as React.ComponentType, -}); - const NewTeachersUniteNavigator = createModalStackNavigator({ [SCREENS.SAVE_THE_WORLD.ROOT]: () => require('../../../pages/TeachersUnite/SaveTheWorldPage').default as React.ComponentType, [SCREENS.I_KNOW_A_TEACHER]: () => require('../../../pages/TeachersUnite/KnowATeacherPage').default as React.ComponentType, @@ -336,7 +329,6 @@ const ProcessMoneyRequestHoldStackNavigator = createModalStackNavigator({ export { AddPersonalBankAccountModalStackNavigator, DetailsModalStackNavigator, - OnboardEngagementModalStackNavigator, EditRequestStackNavigator, EnablePaymentsStackNavigator, FlagCommentStackNavigator, diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index c421bdc82028..93d2f8fba989 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -85,10 +85,6 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.NEW_TASK} component={ModalStackNavigators.NewTaskModalStackNavigator} /> - Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS)}); - Welcome.isOnboardingFlowCompleted({ - onNotCompleted: () => - Navigation.navigate( - // Uncomment once Stage 1 Onboarding Flow is ready - // - // ROUTES.ONBOARDING_PERSONAL_DETAILS - // - ROUTES.ONBOARD, - ), - }); + Welcome.isOnboardingFlowCompleted({onNotCompleted: () => Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS)}); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoadingApp]); diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index dafce762116e..1982afaa149f 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -451,13 +451,6 @@ const config: LinkingOptions['config'] = { [SCREENS.NEW_TASK.DESCRIPTION]: ROUTES.NEW_TASK_DESCRIPTION, }, }, - [SCREENS.RIGHT_MODAL.ONBOARD_ENGAGEMENT]: { - screens: { - [SCREENS.ONBOARD_ENGAGEMENT.ROOT]: ROUTES.ONBOARD, - [SCREENS.ONBOARD_ENGAGEMENT.MANAGE_TEAMS_EXPENSES]: ROUTES.ONBOARD_MANAGE_EXPENSES, - [SCREENS.ONBOARD_ENGAGEMENT.EXPENSIFY_CLASSIC]: ROUTES.ONBOARD_EXPENSIFY_CLASSIC, - }, - }, [SCREENS.RIGHT_MODAL.TEACHERS_UNITE]: { screens: { [SCREENS.SAVE_THE_WORLD.ROOT]: ROUTES.TEACHERS_UNITE, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 41e4602ea2d9..7dc862ed437d 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -412,12 +412,6 @@ type NewTaskNavigatorParamList = { [SCREENS.NEW_TASK.DESCRIPTION]: undefined; }; -type OnboardEngagementNavigatorParamList = { - [SCREENS.ONBOARD_ENGAGEMENT.ROOT]: undefined; - [SCREENS.ONBOARD_ENGAGEMENT.MANAGE_TEAMS_EXPENSES]: undefined; - [SCREENS.ONBOARD_ENGAGEMENT.EXPENSIFY_CLASSIC]: undefined; -}; - type TeachersUniteNavigatorParamList = { [SCREENS.SAVE_THE_WORLD.ROOT]: undefined; [SCREENS.I_KNOW_A_TEACHER]: undefined; @@ -525,7 +519,6 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.ROOM_INVITE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.MONEY_REQUEST]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.NEW_TASK]: NavigatorScreenParams; - [SCREENS.RIGHT_MODAL.ONBOARD_ENGAGEMENT]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.TEACHERS_UNITE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.TASK_DETAILS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.ENABLE_PAYMENTS]: NavigatorScreenParams; @@ -737,7 +730,6 @@ export type { ReimbursementAccountNavigatorParamList, State, WorkspaceSwitcherNavigatorParamList, - OnboardEngagementNavigatorParamList, SwitchPolicyIDParams, FullScreenNavigatorParamList, WorkspacesCentralPaneNavigatorParamList, diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index a93da266e622..95b879fc7485 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -63,11 +63,7 @@ function isOnboardingFlowCompleted({onCompleted, onNotCompleted}: HasCompletedOn return; } - // Uncomment once Stage 1 Onboarding Flow is ready - // - // const onboardingFlowCompleted = hasProvidedPersonalDetails && hasSelectedPurpose; - // - const onboardingFlowCompleted = hasSelectedPurpose; + const onboardingFlowCompleted = hasProvidedPersonalDetails && hasSelectedPurpose; if (onboardingFlowCompleted) { onCompleted?.(); diff --git a/src/pages/OnboardEngagement/ExpensifyClassicPage.tsx b/src/pages/OnboardEngagement/ExpensifyClassicPage.tsx deleted file mode 100644 index 7b242dd4aefa..000000000000 --- a/src/pages/OnboardEngagement/ExpensifyClassicPage.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import Button from '@components/Button'; -import HeaderPageLayout from '@components/HeaderPageLayout'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import Navigation from '@libs/Navigation/Navigation'; -import variables from '@styles/variables'; -import * as Link from '@userActions/Link'; -import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; - -function ExpensifyClassicModal() { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const {isExtraSmallScreenHeight} = useWindowDimensions(); - const theme = useTheme(); - - const navigateBack = () => { - Navigation.goBack(ROUTES.ONBOARD_MANAGE_EXPENSES); - }; - - const navigateToOldDot = () => { - Link.openOldDotLink(`${CONST.OLDDOT_URLS.INBOX}${CONST.OLDDOT_URLS.DISMMISSED_REASON}`); - }; - - return ( - - } - footer={ -