From 0f63c8a1867407a9cc474866f6f18f06dd4328b1 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 28 Feb 2024 14:55:53 +0530 Subject: [PATCH 0001/1525] fix: IOU - Disabled tag is greyed in list but disabled category is shown bold in list. Signed-off-by: Krishna Gupta --- .../CategoryPicker/categoryPickerPropTypes.js | 3 +++ src/components/CategoryPicker/index.js | 21 ++++++++++++++----- src/components/TagPicker/index.js | 5 +++-- .../request/step/IOURequestStepCategory.js | 1 + 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/components/CategoryPicker/categoryPickerPropTypes.js b/src/components/CategoryPicker/categoryPickerPropTypes.js index 0bc116bf45cc..a1cbabd4be40 100644 --- a/src/components/CategoryPicker/categoryPickerPropTypes.js +++ b/src/components/CategoryPicker/categoryPickerPropTypes.js @@ -18,6 +18,9 @@ const propTypes = { /** Callback to fire when a category is pressed */ onSubmit: PropTypes.func.isRequired, + + /** Should show the selected option that is disabled? */ + shouldShowDisabledAndSelectedOption: PropTypes.bool, }; const defaultProps = { diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index 2374fc9e5d0c..1887d46dc505 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -11,7 +11,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './categoryPickerPropTypes'; -function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit}) { +function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit, shouldShowDisabledAndSelectedOption}) { const {translate} = useLocalize(); const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); @@ -20,15 +20,26 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC return []; } + const isSelectedCateoryEnabled = _.some(policyCategories, (category) => category.name === selectedCategory && category.enabled); + return [ { name: selectedCategory, - enabled: true, + enabled: isSelectedCateoryEnabled, accountID: null, isSelected: true, }, ]; - }, [selectedCategory]); + }, [selectedCategory, policyCategories]); + + const enabledCategories = useMemo(() => { + if (!shouldShowDisabledAndSelectedOption) { + return policyCategories; + } + const selectedNames = _.map(selectedOptions, (s) => s.name); + const catergories = [...selectedOptions, ..._.filter(policyCategories, (category) => category.enabled && !selectedNames.includes(category.name))]; + return catergories; + }, [selectedOptions, policyCategories, shouldShowDisabledAndSelectedOption]); const [sections, headerMessage, policyCategoriesCount, shouldShowTextInput] = useMemo(() => { const validPolicyRecentlyUsedCategories = _.filter(policyRecentlyUsedCategories, (p) => !_.isEmpty(p)); @@ -42,7 +53,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC false, false, true, - policyCategories, + enabledCategories, validPolicyRecentlyUsedCategories, false, ); @@ -53,7 +64,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC const showInput = !isCategoriesCountBelowThreshold; return [categoryOptions, header, policiesCount, showInput]; - }, [policyCategories, policyRecentlyUsedCategories, debouncedSearchValue, selectedOptions]); + }, [policyCategories, policyRecentlyUsedCategories, debouncedSearchValue, selectedOptions, enabledCategories]); const selectedOptionKey = useMemo( () => lodashGet(_.filter(lodashGet(sections, '[0].data', []), (category) => category.searchText === selectedCategory)[0], 'keyForList'), diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 341ea9cddae9..557a8ad918e1 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -29,15 +29,16 @@ function TagPicker({selectedTag, tag, tagIndex, policyTags, policyRecentlyUsedTa if (!selectedTag) { return []; } + const selectedTagInList = _.some(policyTagList.tags, (policyTag) => policyTag.name === selectedTag && policyTag.enabled); return [ { name: selectedTag, - enabled: true, + enabled: selectedTagInList, accountID: null, }, ]; - }, [selectedTag]); + }, [selectedTag, policyTagList.tags]); const enabledTags = useMemo(() => { if (!shouldShowDisabledAndSelectedOption) { diff --git a/src/pages/iou/request/step/IOURequestStepCategory.js b/src/pages/iou/request/step/IOURequestStepCategory.js index 3e0feec02854..0c79aa12896b 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.js +++ b/src/pages/iou/request/step/IOURequestStepCategory.js @@ -120,6 +120,7 @@ function IOURequestStepCategory({ selectedCategory={transactionCategory} policyID={report.policyID} onSubmit={updateCategory} + shouldShowDisabledAndSelectedOption={isEditing} /> ); From 22a6f208a0f080195191d85deb40f4e68c8e98e8 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 4 Mar 2024 09:23:22 +0530 Subject: [PATCH 0002/1525] resolve conflicts. Signed-off-by: Krishna Gupta --- src/components/CategoryPicker/categoryPickerPropTypes.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/components/CategoryPicker/categoryPickerPropTypes.js diff --git a/src/components/CategoryPicker/categoryPickerPropTypes.js b/src/components/CategoryPicker/categoryPickerPropTypes.js deleted file mode 100644 index e69de29bb2d1..000000000000 From 061cdd1771362037f8363050dd1a34545b38be4b Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 4 Mar 2024 12:29:41 +0530 Subject: [PATCH 0003/1525] apply changes to new CategoryPicker.tsx Signed-off-by: Krishna Gupta --- src/components/CategoryPicker.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/CategoryPicker.tsx b/src/components/CategoryPicker.tsx index 3033bf118e8f..9213fbdfe4b9 100644 --- a/src/components/CategoryPicker.tsx +++ b/src/components/CategoryPicker.tsx @@ -34,15 +34,17 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC return []; } + const selectedCategoryInList = Object.values(policyCategories ?? {}).some((category) => category.name === selectedCategory && category.enabled); + return [ { name: selectedCategory, - enabled: true, + enabled: selectedCategoryInList, accountID: null, isSelected: true, }, ]; - }, [selectedCategory]); + }, [selectedCategory, policyCategories]); const [sections, headerMessage, shouldShowTextInput] = useMemo(() => { const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter((p) => !isEmptyObject(p)); From ffb21f156543aa780d9ab15585952a916aea2c9d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:25:09 +0530 Subject: [PATCH 0004/1525] add POC for fallingback to default attachment view when pdf fails to load --- .../BaseAttachmentViewPdf.js | 4 + .../DefaultAttachmentView.tsx | 59 +++++++++++++ .../AttachmentView/AttachmentViewPdf/index.js | 3 +- .../AttachmentViewPdf/propTypes.js | 8 ++ .../Attachments/AttachmentView/index.js | 86 ++++++++++++------- .../InvertedFlatList/BaseInvertedFlatList.tsx | 2 +- src/components/PDFView/WebPDFDocument.js | 4 +- src/components/PDFView/index.js | 1 + src/components/PDFView/index.native.js | 59 ++++++++++--- src/styles/index.ts | 14 +++ 10 files changed, 192 insertions(+), 48 deletions(-) create mode 100644 src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js index 2f16b63aacc6..3f3b995c02ba 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js @@ -28,6 +28,8 @@ function BaseAttachmentViewPdf({ onLoadComplete, errorLabelStyles, style, + isUsedAsChatAttachment, + onError, }) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled; @@ -89,6 +91,8 @@ function BaseAttachmentViewPdf({ onScaleChanged={onScaleChanged} onLoadComplete={onLoadComplete} errorLabelStyles={errorLabelStyles} + isUsedAsChatAttachment={isUsedAsChatAttachment} + onError={onError} /> ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx new file mode 100644 index 000000000000..ad102fd5caad --- /dev/null +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import {ActivityIndicator, View} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import _ from 'underscore'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import Tooltip from '@components/Tooltip'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type DefaultAttachmentViewProps = { + file: File; + shouldShowLoadingSpinnerIcon: boolean; + shouldShowDownloadIcon: boolean; + containerStyles: StyleProp[]; +}; + +function DefaultAttachmentView({file, shouldShowLoadingSpinnerIcon, shouldShowDownloadIcon, containerStyles}: DefaultAttachmentViewProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( + + + + + + {file && file.name} + {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( + + + + + + )} + {shouldShowLoadingSpinnerIcon && ( + + + + + + )} + + ); +} + +export default DefaultAttachmentView; diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index d6a402613c34..de740f4fdd1f 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, isUsedAsChatAttachment, onError}) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js index a34010f0ba8b..bcb0fabde352 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js @@ -15,6 +15,12 @@ const attachmentViewPdfPropTypes = { /** Styles for the error label */ errorLabelStyles: stylePropTypes, + + /** Callback when the pdf fails to load */ + onError: PropTypes.func, + + /** Whether the attachment is used as a chat attachment */ + isUsedAsChatAttachment: PropTypes.bool, }; const attachmentViewPdfDefaultProps = { @@ -23,6 +29,8 @@ const attachmentViewPdfDefaultProps = { }, style: [], errorLabelStyles: [], + onError: () => {}, + isUsedAsChatAttachment: false, }; export {attachmentViewPdfPropTypes, attachmentViewPdfDefaultProps}; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index f6a56dc73088..76e546be8930 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -1,16 +1,13 @@ import Str from 'expensify-common/lib/str'; import PropTypes from 'prop-types'; import React, {memo, useEffect, useState} from 'react'; -import {ActivityIndicator, ScrollView, View} from 'react-native'; +import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import * as AttachmentsPropTypes from '@components/Attachments/propTypes'; import DistanceEReceipt from '@components/DistanceEReceipt'; import EReceipt from '@components/EReceipt'; import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -25,6 +22,7 @@ import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; +import DefaultAttachmentView from './AttachmentViewPdf/DefaultAttachmentView'; import AttachmentViewVideo from './AttachmentViewVideo'; import {attachmentViewDefaultProps, attachmentViewPropTypes} from './propTypes'; @@ -107,6 +105,7 @@ function AttachmentView({ const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); + const [isPdfFailedToLoad, setIsPdfFailedToLoad] = useState(false); useEffect(() => { if (!isFocused && !(file && isUsedInAttachmentModal)) { @@ -169,6 +168,17 @@ function AttachmentView({ } }; + if (isPdfFailedToLoad) { + return ( + + ); + } + // We need the following View component on android native // So that the event will propagate properly and // the Password protected preview will be shown for pdf attachement we are about to send. @@ -186,6 +196,10 @@ function AttachmentView({ errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} + isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} + onError={() => { + setIsPdfFailedToLoad(true); + }} /> ); @@ -229,36 +243,42 @@ function AttachmentView({ } return ( - - - - + + // + // + // + // - {file && file.name} - {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - - - - - - )} - {shouldShowLoadingSpinnerIcon && ( - - - - - - )} - + // {file && file.name} + // {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( + // + // + // + // + // + // )} + // {shouldShowLoadingSpinnerIcon && ( + // + // + // + // + // + // )} + // ); } diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx index e28400505280..d0a4322300c3 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx +++ b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx @@ -4,7 +4,7 @@ import type {FlatListProps} from 'react-native'; import FlatList from '@components/FlatList'; const WINDOW_SIZE = 15; -const AUTOSCROLL_TO_TOP_THRESHOLD = 128; +const AUTOSCROLL_TO_TOP_THRESHOLD = 250; function BaseInvertedFlatList(props: FlatListProps, ref: ForwardedRef) { return ( diff --git a/src/components/PDFView/WebPDFDocument.js b/src/components/PDFView/WebPDFDocument.js index dd9d1e066b19..d2e93e3ba506 100644 --- a/src/components/PDFView/WebPDFDocument.js +++ b/src/components/PDFView/WebPDFDocument.js @@ -48,6 +48,8 @@ const propTypes = { * - `undefined` if password isn't needed to view the PDF file * - `null` if the password is required but hasn't been provided yet */ password: PropTypes.string, + /** Callback invoked when the PDF document fails to load */ + onError: PropTypes.func.isRequired, }; const defaultProps = { @@ -95,7 +97,7 @@ const WebPDFDocument = memo( return ( } - error={{translate('attachmentView.failedToLoadPDF')}} + onLoadError={onError} file={sourceURL} options={{ cMapUrl: 'cmaps/', diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 9706f8e06cc1..2e39594fc210 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -270,6 +270,7 @@ class PDFView extends Component { pageWidth={pageWidth} password={this.state.password} initiatePasswordChallenge={this.initiatePasswordChallenge} + onError={this.props.onError} /> {(this.state.password === PDFViewConstants.REQUIRED_PASSWORD_MISSING || this.state.isCheckingPassword) && ( diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 558f6636a325..6c0f5acd1081 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -2,17 +2,22 @@ import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import PDF from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import Text from '@components/Text'; import useKeyboardState from '@hooks/useKeyboardState'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; +import Text from '@components/Text'; const propTypes = { ...pdfViewPropTypes, @@ -33,7 +38,10 @@ const propTypes = { * is (temporarily) rendered. */ -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles}) { +const THUMBNAIL_HEIGHT = 250; +const THUMBNAIL_WIDTH = 250; + +function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, onError, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -46,6 +54,8 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused const themeStyles = useThemeStyles(); const {isKeyboardShown} = useKeyboardState(); const StyleUtils = useStyleUtils(); + const {isOffline} = useNetwork(); + const theme = useTheme(); useEffect(() => { onToggleKeyboard(isKeyboardShown); @@ -84,6 +94,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setShouldShowLoadingIndicator(false); setShouldRequestPassword(false); setShouldAttemptPDFLoad(false); + onError(error); }; /** @@ -113,9 +124,33 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setSuccessToLoadPDF(true); onLoadComplete(path); }; + const rendeFailedToLoadPDF = () => { + if (!isUsedAsChatAttachment) { + return ( + + {translate('attachmentView.failedToLoadPDF')} + + ); + } + + return ( + + + + + + ); + }; function renderPDFView() { - const pdfStyles = [themeStyles.imageModalPDF, StyleUtils.getWidthAndHeightStyle(windowWidth, windowHeight)]; + const pdfHeight = isUsedAsChatAttachment ? THUMBNAIL_HEIGHT : windowHeight; + const pdfWeight = isUsedAsChatAttachment ? THUMBNAIL_WIDTH : windowWidth; + const pdfStyles = [StyleUtils.getWidthAndHeightStyle(pdfWeight, pdfHeight), themeStyles.imageModalPDF]; // If we haven't yet successfully validated the password and loaded the PDF, // then we need to hide the react-native-pdf/PDF component so that PDFPasswordForm @@ -124,22 +159,18 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused if (shouldRequestPassword) { pdfStyles.push(themeStyles.invisible); } - - const containerStyles = shouldRequestPassword && isSmallScreenWidth ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; + const containerStyles = + (shouldRequestPassword && isSmallScreenWidth) || isUsedAsChatAttachment ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; + const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)] : []; return ( - {failedToLoadPDF && ( - - {translate('attachmentView.failedToLoadPDF')} - - )} {shouldAttemptPDFLoad && ( } - source={{uri: sourceURL, cache: true, expiration: 864000}} + renderActivityIndicator={() => } + source={{uri: sourceURL}} style={pdfStyles} onError={handleFailureToLoadPDF} password={password} @@ -162,6 +193,10 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused ); } + // // uncomment this code to see failedToLoadPDF + // if (failedToLoadPDF) { + // return rendeFailedToLoadPDF(); + // } return onPress && !successToLoadPDF ? ( borderRadius: variables.buttonBorderRadius, }, + componentBorderRadiusNormal: { + borderRadius: variables.componentBorderRadiusNormal, + }, + bottomTabBarContainer: { flexDirection: 'row', height: variables.bottomTabHeight, @@ -2111,6 +2115,16 @@ const styles = (theme: ThemeColors) => width: 200, }, + chatItemPDFAttachmentLoading: { + backgroundColor: 'transparent', + borderColor: theme.border, + borderWidth: 1, + borderRadius: variables.componentBorderRadiusNormal, + textAlign: 'center', + verticalAlign: 'middle', + opacity: 1, + }, + sidebarVisible: { borderRightWidth: 1, }, From bde48faf072512f5d79d29ce293ae83b44241171 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:26:09 +0530 Subject: [PATCH 0005/1525] adds chnages for attachement view --- .../Attachments/AttachmentView/index.js | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 76e546be8930..f15b235144cb 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -12,7 +12,6 @@ import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContex import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CachedPDFPaths from '@libs/actions/CachedPDFPaths'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; @@ -100,7 +99,6 @@ function AttachmentView({ optionalVideoDuration, }) { const {updateCurrentlyPlayingURL} = usePlaybackContext(); - const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); @@ -155,7 +153,7 @@ function AttachmentView({ // Check both source and file.name since PDFs dragged into the text field // will appear with a source that is a blob - if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { + if (!isPdfFailedToLoad && ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename'))))) { const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; const onPDFLoadComplete = (path) => { @@ -168,17 +166,6 @@ function AttachmentView({ } }; - if (isPdfFailedToLoad) { - return ( - - ); - } - // We need the following View component on android native // So that the event will propagate properly and // the Password protected preview will be shown for pdf attachement we are about to send. @@ -249,36 +236,6 @@ function AttachmentView({ shouldShowLoadingSpinnerIcon={shouldShowLoadingSpinnerIcon} containerStyles={containerStyles} /> - // - // - // - // - - // {file && file.name} - // {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - // - // - // - // - // - // )} - // {shouldShowLoadingSpinnerIcon && ( - // - // - // - // - // - // )} - // ); } From 757661bf2c41ad8f7bc0abeed6102202890c7679 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:32:34 +0530 Subject: [PATCH 0006/1525] formatting --- src/components/PDFView/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 6c0f5acd1081..492a12e1dc66 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -6,6 +6,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import Text from '@components/Text'; import useKeyboardState from '@hooks/useKeyboardState'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -17,7 +18,6 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; -import Text from '@components/Text'; const propTypes = { ...pdfViewPropTypes, From 26c1f13c9fd6b7ede36ecf38c1d17e0ebb69fd7b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:41:08 +0530 Subject: [PATCH 0007/1525] fixes conflicts --- .../Attachments/AttachmentView/index.js | 435 ++++++++++-------- 1 file changed, 247 insertions(+), 188 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 492a12e1dc66..461548f0d2b1 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -1,219 +1,278 @@ -import React, {useCallback, useEffect, useState} from 'react'; -import {View} from 'react-native'; -import PDF from 'react-native-pdf'; -import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import Str from 'expensify-common/lib/str'; +import PropTypes from 'prop-types'; +import React, {memo, useEffect, useState} from 'react'; +import {ActivityIndicator, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; +import * as AttachmentsPropTypes from '@components/Attachments/propTypes'; +import DistanceEReceipt from '@components/DistanceEReceipt'; +import EReceipt from '@components/EReceipt'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; -import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; -import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; -import useKeyboardState from '@hooks/useKeyboardState'; -import useLocalize from '@hooks/useLocalize'; +import Tooltip from '@components/Tooltip'; +import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as CachedPDFPaths from '@libs/actions/CachedPDFPaths'; +import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; +import compose from '@libs/compose'; +import * as TransactionUtils from '@libs/TransactionUtils'; import variables from '@styles/variables'; -import CONST from '@src/CONST'; -import PDFPasswordForm from './PDFPasswordForm'; -import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; +import ONYXKEYS from '@src/ONYXKEYS'; +import AttachmentViewImage from './AttachmentViewImage'; +import AttachmentViewPdf from './AttachmentViewPdf'; +import AttachmentViewVideo from './AttachmentViewVideo'; +import {attachmentViewDefaultProps, attachmentViewPropTypes} from './propTypes'; const propTypes = { - ...pdfViewPropTypes, + ...attachmentViewPropTypes, + ...withLocalizePropTypes, + + /** URL to full-sized attachment, SVG function, or numeric static image on native platforms */ + source: AttachmentsPropTypes.attachmentSourcePropType.isRequired, + + /** Flag to show/hide download icon */ + shouldShowDownloadIcon: PropTypes.bool, + + /** Flag to show the loading indicator */ + shouldShowLoadingSpinnerIcon: PropTypes.bool, + + /** Notify parent that the UI should be modified to accommodate keyboard */ + onToggleKeyboard: PropTypes.func, + + /** Extra styles to pass to View wrapper */ + // eslint-disable-next-line react/forbid-prop-types + containerStyles: PropTypes.arrayOf(PropTypes.object), + + /** Denotes whether it is a workspace avatar or not */ + isWorkspaceAvatar: PropTypes.bool, + + /** Denotes whether it is an icon (ex: SVG) */ + maybeIcon: PropTypes.bool, + + /** The id of the transaction related to the attachment */ + // eslint-disable-next-line react/no-unused-prop-types + transactionID: PropTypes.string, + + /** The id of the report action related to the attachment */ + reportActionID: PropTypes.string, + + isHovered: PropTypes.bool, + + optionalVideoDuration: PropTypes.number, }; -/** - * On the native layer, we use react-native-pdf/PDF to display PDFs. If a PDF is - * password-protected we render a PDFPasswordForm to request a password - * from the user. - * - * In order to render things nicely during a password challenge we need - * to keep track of additional state. In particular, the - * react-native-pdf/PDF component is both conditionally rendered and hidden - * depending upon the situation. It needs to be rerendered on each password - * submission because it doesn't dynamically handle updates to its - * password property. And we need to hide it during password challenges - * so that PDFPasswordForm doesn't bounce when react-native-pdf/PDF - * is (temporarily) rendered. - */ - -const THUMBNAIL_HEIGHT = 250; -const THUMBNAIL_WIDTH = 250; - -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, onError, isUsedAsChatAttachment}) { - const [shouldRequestPassword, setShouldRequestPassword] = useState(false); - const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); - const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); - const [isPasswordInvalid, setIsPasswordInvalid] = useState(false); - const [failedToLoadPDF, setFailedToLoadPDF] = useState(false); - const [successToLoadPDF, setSuccessToLoadPDF] = useState(false); - const [password, setPassword] = useState(''); - const {windowWidth, windowHeight, isSmallScreenWidth} = useWindowDimensions(); - const {translate} = useLocalize(); - const themeStyles = useThemeStyles(); - const {isKeyboardShown} = useKeyboardState(); - const StyleUtils = useStyleUtils(); - const {isOffline} = useNetwork(); +const defaultProps = { + ...attachmentViewDefaultProps, + shouldShowDownloadIcon: false, + shouldShowLoadingSpinnerIcon: false, + onToggleKeyboard: () => {}, + containerStyles: [], + isWorkspaceAvatar: false, + maybeIcon: false, + transactionID: '', + reportActionID: '', + isHovered: false, + optionalVideoDuration: 0, +}; + +function AttachmentView({ + source, + file, + isAuthTokenRequired, + onPress, + shouldShowLoadingSpinnerIcon, + shouldShowDownloadIcon, + containerStyles, + onToggleKeyboard, + translate, + isFocused, + isUsedInCarousel, + isUsedInAttachmentModal, + isWorkspaceAvatar, + maybeIcon, + fallbackSource, + transaction, + reportActionID, + isHovered, + optionalVideoDuration, +}) { + const {updateCurrentlyPlayingURL} = usePlaybackContext(); const theme = useTheme(); + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const [loadComplete, setLoadComplete] = useState(false); + const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); useEffect(() => { - onToggleKeyboard(isKeyboardShown); - }); - - /** - * Initiate password challenge if message received from react-native-pdf/PDF - * indicates that a password is required or invalid. - * - * For a password challenge the message is "Password required or incorrect password." - * Note that the message doesn't specify whether the password is simply empty or - * invalid. - */ - - const initiatePasswordChallenge = useCallback(() => { - setShouldShowLoadingIndicator(false); - - // Render password form, and don't render PDF and loading indicator. - setShouldRequestPassword(true); - setShouldAttemptPDFLoad(false); - - // The message provided by react-native-pdf doesn't indicate whether this - // is an initial password request or if the password is invalid. So we just assume - // that if a password was already entered then it's an invalid password error. - if (password) { - setIsPasswordInvalid(true); - } - }, [password]); - - const handleFailureToLoadPDF = (error) => { - if (error.message.match(/password/i)) { - initiatePasswordChallenge(); + if (!isFocused && !(file && isUsedInAttachmentModal)) { return; } - setFailedToLoadPDF(true); - setShouldShowLoadingIndicator(false); - setShouldRequestPassword(false); - setShouldAttemptPDFLoad(false); - onError(error); - }; - - /** - * When the password is submitted via PDFPasswordForm, save the password - * in state and attempt to load the PDF. Also show the loading indicator - * since react-native-pdf/PDF will need to reload the PDF. - * - * @param {String} pdfPassword Password submitted via PDFPasswordForm - */ - const attemptPDFLoadWithPassword = (pdfPassword) => { - // Render react-native-pdf/PDF so that it can validate the password. - // Note that at this point in the password challenge, shouldRequestPassword is true. - // Thus react-native-pdf/PDF will be rendered - but not visible. - setPassword(pdfPassword); - setShouldAttemptPDFLoad(true); - setShouldShowLoadingIndicator(true); - }; - /** - * After the PDF is successfully loaded hide PDFPasswordForm and the loading - * indicator. - * @param {Number} numberOfPages - * @param {Number} path - Path to cache location - */ - const finishPDFLoad = (numberOfPages, path) => { - setShouldRequestPassword(false); - setShouldShowLoadingIndicator(false); - setSuccessToLoadPDF(true); - onLoadComplete(path); - }; - const rendeFailedToLoadPDF = () => { - if (!isUsedAsChatAttachment) { - return ( - - {translate('attachmentView.failedToLoadPDF')} - - ); + updateCurrentlyPlayingURL(isVideo ? source : null); + }, [isFocused, isVideo, source, updateCurrentlyPlayingURL, file, isUsedInAttachmentModal]); + + const [imageError, setImageError] = useState(false); + + useNetwork({onReconnect: () => setImageError(false)}); + + // Handles case where source is a component (ex: SVG) or a number + // Number may represent a SVG or an image + if ((maybeIcon && typeof source === 'number') || _.isFunction(source)) { + let iconFillColor = ''; + let additionalStyles = []; + if (isWorkspaceAvatar) { + const defaultWorkspaceAvatarColor = StyleUtils.getDefaultWorkspaceAvatarColor(file.name); + iconFillColor = defaultWorkspaceAvatarColor.fill; + additionalStyles = [defaultWorkspaceAvatarColor]; } return ( - - - - + + ); + } + + if (TransactionUtils.hasEReceipt(transaction)) { + return ( + + + + ); - }; - - function renderPDFView() { - const pdfHeight = isUsedAsChatAttachment ? THUMBNAIL_HEIGHT : windowHeight; - const pdfWeight = isUsedAsChatAttachment ? THUMBNAIL_WIDTH : windowWidth; - const pdfStyles = [StyleUtils.getWidthAndHeightStyle(pdfWeight, pdfHeight), themeStyles.imageModalPDF]; - - // If we haven't yet successfully validated the password and loaded the PDF, - // then we need to hide the react-native-pdf/PDF component so that PDFPasswordForm - // is positioned nicely. We're specifically hiding it because we still need to render - // the PDF component so that it can validate the password. - if (shouldRequestPassword) { - pdfStyles.push(themeStyles.invisible); - } - const containerStyles = - (shouldRequestPassword && isSmallScreenWidth) || isUsedAsChatAttachment ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; - const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)] : []; + } + + // Check both source and file.name since PDFs dragged into the text field + // will appear with a source that is a blob + if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { + const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; + + const onPDFLoadComplete = (path) => { + const id = (transaction && transaction.transactionID) || reportActionID; + if (path && id) { + CachedPDFPaths.add(id, path); + } + if (!loadComplete) { + setLoadComplete(true); + } + }; + // We need the following View component on android native + // So that the event will propagate properly and + // the Password protected preview will be shown for pdf attachement we are about to send. return ( - - {shouldAttemptPDFLoad && ( - } - source={{uri: sourceURL}} - style={pdfStyles} - onError={handleFailureToLoadPDF} - password={password} - onLoadComplete={finishPDFLoad} - onPageSingleTap={onPress} - onScaleChanged={onScaleChanged} - /> - )} - {shouldRequestPassword && ( - - setIsPasswordInvalid(false)} - isPasswordInvalid={isPasswordInvalid} - shouldShowLoadingIndicator={shouldShowLoadingIndicator} - /> - - )} + + ); } - // // uncomment this code to see failedToLoadPDF - // if (failedToLoadPDF) { - // return rendeFailedToLoadPDF(); - // } - - return onPress && !successToLoadPDF ? ( - - {renderPDFView()} - - ) : ( - renderPDFView() + + if (TransactionUtils.isDistanceRequest(transaction)) { + return ; + } + + // For this check we use both source and file.name since temporary file source is a blob + // both PDFs and images will appear as images when pasted into the text field. + // We also check for numeric source since this is how static images (used for preview) are represented in RN. + const isImage = typeof source === 'number' || Str.isImage(source); + if (isImage || (file && Str.isImage(file.name))) { + return ( + { + setImageError(true); + }} + /> + ); + } + + if (isVideo) { + return ( + + ); + } + + return ( + + + + + + {file && file.name} + {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( + + + + + + )} + {shouldShowLoadingSpinnerIcon && ( + + + + + + )} + ); } -PDFView.displayName = 'PDFView'; -PDFView.propTypes = propTypes; -PDFView.defaultProps = defaultProps; +AttachmentView.propTypes = propTypes; +AttachmentView.defaultProps = defaultProps; +AttachmentView.displayName = 'AttachmentView'; -export default PDFView; +export default compose( + memo, + withLocalize, + withOnyx({ + transaction: { + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + }, + }), +)(AttachmentView); From 36b581464aa1c9f6fbbb08dbc70f939b55017756 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:57:23 +0530 Subject: [PATCH 0008/1525] fix conflict after merge --- .../DefaultAttachmentView.tsx | 59 ------------------- .../Attachments/AttachmentView/index.js | 7 ++- src/components/PDFView/index.native.js | 1 + 3 files changed, 7 insertions(+), 60 deletions(-) delete mode 100644 src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx deleted file mode 100644 index ad102fd5caad..000000000000 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import {ActivityIndicator, View} from 'react-native'; -import type {StyleProp, ViewStyle} from 'react-native'; -import _ from 'underscore'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; -import useLocalize from '@hooks/useLocalize'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; - -type DefaultAttachmentViewProps = { - file: File; - shouldShowLoadingSpinnerIcon: boolean; - shouldShowDownloadIcon: boolean; - containerStyles: StyleProp[]; -}; - -function DefaultAttachmentView({file, shouldShowLoadingSpinnerIcon, shouldShowDownloadIcon, containerStyles}: DefaultAttachmentViewProps) { - const theme = useTheme(); - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - return ( - - - - - - {file && file.name} - {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - - - - - - )} - {shouldShowLoadingSpinnerIcon && ( - - - - - - )} - - ); -} - -export default DefaultAttachmentView; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 461548f0d2b1..fc11ccfd8fac 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -107,6 +107,7 @@ function AttachmentView({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); + const [isPdfFailedToLoad, setIsPdfFailedToLoad] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); useEffect(() => { @@ -157,7 +158,7 @@ function AttachmentView({ // Check both source and file.name since PDFs dragged into the text field // will appear with a source that is a blob - if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { + if (!isPdfFailedToLoad && ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename'))))) { const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; const onPDFLoadComplete = (path) => { @@ -187,6 +188,10 @@ function AttachmentView({ errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} + isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} + onError={() => { + setIsPdfFailedToLoad(true); + }} /> ); diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 492a12e1dc66..f7901b035d8b 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -124,6 +124,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setSuccessToLoadPDF(true); onLoadComplete(path); }; + // eslint-disable-next-line no-unused-vars const rendeFailedToLoadPDF = () => { if (!isUsedAsChatAttachment) { return ( From d3df9e9f91f11687af6c8a8dc3edcfa8c453ac7d Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 9 Mar 2024 13:58:36 +0530 Subject: [PATCH 0009/1525] fix: disabled seleted category not shown in list when searching. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index fd803a508b4a..8176bb81dd88 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -956,10 +956,11 @@ function getCategoryListSections( maxRecentReportsToShow: number, ): CategoryTreeSection[] { const sortedCategories = sortCategories(categories); - const enabledCategories = Object.values(sortedCategories).filter((category) => category.enabled); + const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); + const enabledCategories = [...selectedOptions, ...Object.values(sortedCategories).filter((category) => category.enabled && !selectedOptionNames.includes(category.name))]; + const numberOfCategories = enabledCategories.length; const categorySections: CategoryTreeSection[] = []; - const numberOfCategories = enabledCategories.length; let indexOffset = 0; @@ -989,17 +990,13 @@ function getCategoryListSections( return categorySections; } - const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); - const enabledAndSelectedCategories = [...selectedOptions, ...sortedCategories.filter((category) => category.enabled && !selectedOptionNames.includes(category.name))]; - const numberOfVisibleCategories = enabledAndSelectedCategories.length; - - if (numberOfVisibleCategories < CONST.CATEGORY_LIST_THRESHOLD) { + if (numberOfCategories < CONST.CATEGORY_LIST_THRESHOLD) { categorySections.push({ // "All" section when items amount less than the threshold title: '', shouldShow: false, indexOffset, - data: getCategoryOptionTree(enabledAndSelectedCategories), + data: getCategoryOptionTree(enabledCategories), }); return categorySections; From d78eebd99a6bfbd512cf84ec2a69c7e5c46ecb83 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 9 Mar 2024 14:00:27 +0530 Subject: [PATCH 0010/1525] revert all previous changes. Signed-off-by: Krishna Gupta --- src/components/CategoryPicker.tsx | 6 ++---- src/components/TagPicker/index.js | 6 ++---- src/pages/iou/request/step/IOURequestStepCategory.js | 1 - 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/components/CategoryPicker.tsx b/src/components/CategoryPicker.tsx index 9213fbdfe4b9..3033bf118e8f 100644 --- a/src/components/CategoryPicker.tsx +++ b/src/components/CategoryPicker.tsx @@ -34,17 +34,15 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC return []; } - const selectedCategoryInList = Object.values(policyCategories ?? {}).some((category) => category.name === selectedCategory && category.enabled); - return [ { name: selectedCategory, - enabled: selectedCategoryInList, + enabled: true, accountID: null, isSelected: true, }, ]; - }, [selectedCategory, policyCategories]); + }, [selectedCategory]); const [sections, headerMessage, shouldShowTextInput] = useMemo(() => { const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter((p) => !isEmptyObject(p)); diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 557a8ad918e1..38e863730353 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -29,16 +29,14 @@ function TagPicker({selectedTag, tag, tagIndex, policyTags, policyRecentlyUsedTa if (!selectedTag) { return []; } - const selectedTagInList = _.some(policyTagList.tags, (policyTag) => policyTag.name === selectedTag && policyTag.enabled); - return [ { name: selectedTag, - enabled: selectedTagInList, + enabled: true, accountID: null, }, ]; - }, [selectedTag, policyTagList.tags]); + }, [selectedTag]); const enabledTags = useMemo(() => { if (!shouldShowDisabledAndSelectedOption) { diff --git a/src/pages/iou/request/step/IOURequestStepCategory.js b/src/pages/iou/request/step/IOURequestStepCategory.js index 3a85c65f3441..1945edbc24c4 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.js +++ b/src/pages/iou/request/step/IOURequestStepCategory.js @@ -121,7 +121,6 @@ function IOURequestStepCategory({ selectedCategory={transactionCategory} policyID={report.policyID} onSubmit={updateCategory} - shouldShowDisabledAndSelectedOption={isEditing} /> ); From 0a3cd609e8b87742aa73ca953de849c4391c8521 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 9 Mar 2024 14:00:58 +0530 Subject: [PATCH 0011/1525] minor spacing fix Signed-off-by: Krishna Gupta --- src/components/TagPicker/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 38e863730353..341ea9cddae9 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -29,6 +29,7 @@ function TagPicker({selectedTag, tag, tagIndex, policyTags, policyRecentlyUsedTa if (!selectedTag) { return []; } + return [ { name: selectedTag, From a4485ff37edf58c018765deb76cf1b85214f74ac Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 11 Mar 2024 15:57:16 +0530 Subject: [PATCH 0012/1525] fix jest tests fails. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.ts | 14 +++++++++++--- tests/unit/OptionsListUtilsTest.js | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 03aa8f952065..a56e4afae124 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -956,21 +956,29 @@ function getCategoryListSections( maxRecentReportsToShow: number, ): CategoryTreeSection[] { const sortedCategories = sortCategories(categories); + const numberOfCategories = sortedCategories.length; const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); const enabledCategories = [...selectedOptions, ...Object.values(sortedCategories).filter((category) => category.enabled && !selectedOptionNames.includes(category.name))]; - const numberOfCategories = enabledCategories.length; + const enabledAndSelectedCategoriesLength = enabledCategories.length; const categorySections: CategoryTreeSection[] = []; let indexOffset = 0; if (numberOfCategories === 0 && selectedOptions.length > 0) { + const selectedTagOptions = selectedOptions.map((option) => ({ + name: option.name, + // Should be marked as enabled to be able to be de-selected + enabled: true, + isSelected: true, + })); + categorySections.push({ // "Selected" section title: '', shouldShow: false, indexOffset, - data: getCategoryOptionTree(selectedOptions, true), + data: getCategoryOptionTree(selectedTagOptions, true), }); return categorySections; @@ -990,7 +998,7 @@ function getCategoryListSections( return categorySections; } - if (numberOfCategories < CONST.CATEGORY_LIST_THRESHOLD) { + if (enabledAndSelectedCategoriesLength < CONST.CATEGORY_LIST_THRESHOLD) { categorySections.push({ // "All" section when items amount less than the threshold title: '', diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 7244b7830a29..1eed8d922036 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -1014,7 +1014,7 @@ describe('OptionsListUtils', () => { searchText: 'Medical', tooltipText: 'Medical', isDisabled: false, - isSelected: false, + isSelected: true, }, ], }, From e0918c80720e91af5a8cdacd659f57f3c5cc0ed7 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 11 Mar 2024 16:01:56 +0530 Subject: [PATCH 0013/1525] fix const names. Signed-off-by: Krishna Gupta --- Gemfile.lock | 16 +++++++++++----- src/libs/OptionsListUtils.ts | 24 +++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index beb2c1762936..7cc425fe6323 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,11 +3,12 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.0.8) + activesupport (6.1.7.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + zeitwerk (~> 2.3) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) @@ -80,7 +81,8 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20240107) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) escape (0.0.4) @@ -187,11 +189,11 @@ GEM google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.3.1) - google-cloud-storage (1.47.0) + google-cloud-storage (1.37.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.31.0) + google-apis-storage_v1 (~> 0.1) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) @@ -260,6 +262,9 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9.1) unicode-display_width (2.5.0) word_wrap (1.0.0) xcodeproj (1.23.0) @@ -273,6 +278,7 @@ GEM rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) + zeitwerk (2.6.13) PLATFORMS arm64-darwin-21 @@ -292,4 +298,4 @@ RUBY VERSION ruby 2.6.10p210 BUNDLED WITH - 2.4.19 + 2.4.22 diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index a56e4afae124..042be402678a 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -956,36 +956,30 @@ function getCategoryListSections( maxRecentReportsToShow: number, ): CategoryTreeSection[] { const sortedCategories = sortCategories(categories); - const numberOfCategories = sortedCategories.length; + const enabledCategoriesLength = Object.values(sortedCategories).filter((category) => category.enabled).length; + const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); - const enabledCategories = [...selectedOptions, ...Object.values(sortedCategories).filter((category) => category.enabled && !selectedOptionNames.includes(category.name))]; - const enabledAndSelectedCategoriesLength = enabledCategories.length; + const enabledAndSelectedCategories = [...selectedOptions, ...Object.values(sortedCategories).filter((category) => category.enabled && !selectedOptionNames.includes(category.name))]; + const enabledAndSelectedCategoriesLength = enabledAndSelectedCategories.length; const categorySections: CategoryTreeSection[] = []; let indexOffset = 0; - if (numberOfCategories === 0 && selectedOptions.length > 0) { - const selectedTagOptions = selectedOptions.map((option) => ({ - name: option.name, - // Should be marked as enabled to be able to be de-selected - enabled: true, - isSelected: true, - })); - + if (enabledCategoriesLength === 0 && selectedOptions.length > 0) { categorySections.push({ // "Selected" section title: '', shouldShow: false, indexOffset, - data: getCategoryOptionTree(selectedTagOptions, true), + data: getCategoryOptionTree(enabledAndSelectedCategories, true), }); return categorySections; } if (searchInputValue) { - const searchCategories = enabledCategories.filter((category) => category.name.toLowerCase().includes(searchInputValue.toLowerCase())); + const searchCategories = enabledAndSelectedCategories.filter((category) => category.name.toLowerCase().includes(searchInputValue.toLowerCase())); categorySections.push({ // "Search" section @@ -1004,7 +998,7 @@ function getCategoryListSections( title: '', shouldShow: false, indexOffset, - data: getCategoryOptionTree(enabledCategories), + data: getCategoryOptionTree(enabledAndSelectedCategories), }); return categorySections; @@ -1043,7 +1037,7 @@ function getCategoryListSections( indexOffset += filteredRecentlyUsedCategories.length; } - const filteredCategories = enabledCategories.filter((category) => !selectedOptionNames.includes(category.name)); + const filteredCategories = enabledAndSelectedCategories.filter((category) => !selectedOptionNames.includes(category.name)); categorySections.push({ // "All" section when items amount more than the threshold From dd517366675222f699f879e630ea1fec82fb2707 Mon Sep 17 00:00:00 2001 From: Krishna Date: Mon, 11 Mar 2024 16:04:02 +0530 Subject: [PATCH 0014/1525] Update Gemfile.lock --- Gemfile.lock | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7cc425fe6323..e276bcacbbd7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,11 @@ -GEM - remote: https://rubygems.org/ specs: CFPropertyList (3.0.6) rexml - activesupport (6.1.7.7) + activesupport (7.0.8) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) @@ -81,8 +78,7 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) escape (0.0.4) @@ -189,11 +185,11 @@ GEM google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.3.1) - google-cloud-storage (1.37.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) @@ -262,9 +258,6 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.9.1) unicode-display_width (2.5.0) word_wrap (1.0.0) xcodeproj (1.23.0) @@ -278,7 +271,6 @@ GEM rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) - zeitwerk (2.6.13) PLATFORMS arm64-darwin-21 @@ -286,16 +278,14 @@ PLATFORMS universal-darwin-20 x86_64-darwin-19 x86_64-linux - DEPENDENCIES activesupport (>= 6.1.7.3, < 7.1.0) cocoapods (~> 1.13) fastlane (~> 2) fastlane-plugin-aws_s3 xcpretty (~> 0) - RUBY VERSION ruby 2.6.10p210 BUNDLED WITH - 2.4.22 + 2.4.19 From d8f81d81c9dc9f15a78a675664799b90a4021f87 Mon Sep 17 00:00:00 2001 From: Krishna Date: Mon, 11 Mar 2024 16:05:19 +0530 Subject: [PATCH 0015/1525] Update Gemfile.lock --- Gemfile.lock | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e276bcacbbd7..bf34eda0dac4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,4 +1,6 @@ - specs: +GEM + remote: https://rubygems.org/ +specs: CFPropertyList (3.0.6) rexml activesupport (7.0.8) @@ -278,12 +280,14 @@ PLATFORMS universal-darwin-20 x86_64-darwin-19 x86_64-linux + DEPENDENCIES activesupport (>= 6.1.7.3, < 7.1.0) cocoapods (~> 1.13) fastlane (~> 2) fastlane-plugin-aws_s3 xcpretty (~> 0) + RUBY VERSION ruby 2.6.10p210 From 9f9669d52286eefb4f815b7ad56c7a01b65442ad Mon Sep 17 00:00:00 2001 From: Krishna Date: Mon, 11 Mar 2024 16:06:13 +0530 Subject: [PATCH 0016/1525] Update Gemfile.lock --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bf34eda0dac4..beb2c1762936 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ -GEM +GEM remote: https://rubygems.org/ -specs: + specs: CFPropertyList (3.0.6) rexml activesupport (7.0.8) From cdd87ddb1642d60c2cf75e4241b3c3ffb423df8d Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 11 Mar 2024 16:13:14 +0530 Subject: [PATCH 0017/1525] fix: jest tests. Signed-off-by: Krishna Gupta --- tests/unit/OptionsListUtilsTest.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 1eed8d922036..c3c84cdc2c83 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -689,6 +689,7 @@ describe('OptionsListUtils', () => { { name: 'Medical', enabled: true, + isSelected: true, }, ]; const smallCategoriesList = { @@ -845,7 +846,7 @@ describe('OptionsListUtils', () => { searchText: 'Medical', tooltipText: 'Medical', isDisabled: false, - isSelected: false, + isSelected: true, }, ], }, From 75a733c60b7f82a3221bb24a10b744da622978db Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 14 Mar 2024 01:32:31 +0530 Subject: [PATCH 0018/1525] clean up and lint fix --- .../AttachmentViewPdf/BaseAttachmentViewPdf.js | 2 -- .../AttachmentView/AttachmentViewPdf/index.js | 4 ++-- .../AttachmentView/AttachmentViewPdf/propTypes.js | 3 --- src/components/Attachments/AttachmentView/index.js | 6 +----- src/components/PDFView/index.native.js | 11 +++++------ 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js index 3f3b995c02ba..289909a66dab 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js @@ -29,7 +29,6 @@ function BaseAttachmentViewPdf({ errorLabelStyles, style, isUsedAsChatAttachment, - onError, }) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled; @@ -92,7 +91,6 @@ function BaseAttachmentViewPdf({ onLoadComplete={onLoadComplete} errorLabelStyles={errorLabelStyles} isUsedAsChatAttachment={isUsedAsChatAttachment} - onError={onError} /> ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index de740f4fdd1f..af665d73a3ba 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, isUsedAsChatAttachment, onError}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, isUsedAsChatAttachment}) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js index bcb0fabde352..b6ad86124f63 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js @@ -16,9 +16,6 @@ const attachmentViewPdfPropTypes = { /** Styles for the error label */ errorLabelStyles: stylePropTypes, - /** Callback when the pdf fails to load */ - onError: PropTypes.func, - /** Whether the attachment is used as a chat attachment */ isUsedAsChatAttachment: PropTypes.bool, }; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index fc11ccfd8fac..6ba3035afc86 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -107,7 +107,6 @@ function AttachmentView({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); - const [isPdfFailedToLoad, setIsPdfFailedToLoad] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); useEffect(() => { @@ -158,7 +157,7 @@ function AttachmentView({ // Check both source and file.name since PDFs dragged into the text field // will appear with a source that is a blob - if (!isPdfFailedToLoad && ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename'))))) { + if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; const onPDFLoadComplete = (path) => { @@ -189,9 +188,6 @@ function AttachmentView({ style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} - onError={() => { - setIsPdfFailedToLoad(true); - }} /> ); diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index f7901b035d8b..addbcec9b3bf 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -41,7 +41,7 @@ const propTypes = { const THUMBNAIL_HEIGHT = 250; const THUMBNAIL_WIDTH = 250; -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, onError, isUsedAsChatAttachment}) { +function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -94,7 +94,6 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setShouldShowLoadingIndicator(false); setShouldRequestPassword(false); setShouldAttemptPDFLoad(false); - onError(error); }; /** @@ -194,10 +193,10 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused ); } - // // uncomment this code to see failedToLoadPDF - // if (failedToLoadPDF) { - // return rendeFailedToLoadPDF(); - // } + + if (failedToLoadPDF) { + return rendeFailedToLoadPDF(); + } return onPress && !successToLoadPDF ? ( Date: Fri, 15 Mar 2024 15:07:54 +0700 Subject: [PATCH 0019/1525] display backend unreachability message --- src/CONST.ts | 3 +- src/Expensify.tsx | 4 +- src/components/OfflineIndicator.tsx | 27 ++++++++++++-- src/hooks/useNetwork.ts | 6 +-- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/NetworkConnection.ts | 57 +++++++++++++++++------------ src/libs/actions/Network.ts | 6 ++- src/types/onyx/Network.ts | 2 + 9 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fa44cda20720..472c980ebaca 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -507,6 +507,7 @@ const CONST = { EMPTY_ARRAY, EMPTY_OBJECT, USE_EXPENSIFY_URL, + STATUS_EXPENSIFY_URL: 'https://status.expensify.com/', GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com', GOOGLE_DOC_IMAGE_LINK_MATCH: 'googleusercontent.com', IMAGE_BASE64_MATCH: 'base64', @@ -917,7 +918,7 @@ const CONST = { DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, DEFAULT_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, DEFAULT_CLOSE_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, - DEFAULT_NETWORK_DATA: {isOffline: false}, + DEFAULT_NETWORK_DATA: {isOffline: false, isBackendReachable: true}, FORMS: { LOGIN_FORM: 'LoginForm', VALIDATE_CODE_FORM: 'ValidateCodeForm', diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 5681be838ca8..7a2a2189b0fa 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -139,7 +139,9 @@ function Expensify({ ActiveClientManager.init(); // Used for the offline indicator appearing when someone is offline - NetworkConnection.subscribeToNetInfo(); + const unsubscribeNetInfo = NetworkConnection.subscribeToNetInfo(); + + return () => unsubscribeNetInfo(); }, []); useEffect(() => { diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index 1a61b6622783..a550a2a6a563 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -7,9 +7,11 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; +import TextLink from './TextLink'; type OfflineIndicatorProps = { /** Optional styles for container element that will override the default styling for the offline indicator */ @@ -23,7 +25,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {isOffline} = useNetwork(); + const {isOffline, isBackendReachable} = useNetwork(); const {isSmallScreenWidth} = useWindowDimensions(); const computedStyles = useMemo((): StyleProp => { @@ -34,7 +36,8 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); - if (!isOffline) { + // Truthy isBackendReachable implies both online and normal backend reachability + if (isBackendReachable) { return null; } @@ -46,7 +49,25 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { width={variables.iconSizeSmall} height={variables.iconSizeSmall} /> - {translate('common.youAppearToBeOffline')} + + { + // Do not reorder the condition, we only show unreachability message when online and backend is unreachable + isOffline ? ( + translate('common.youAppearToBeOffline') + ) : ( + <> + {translate('common.weMightHaveProblem')} + + {CONST.STATUS_EXPENSIFY_URL} + + . + + ) + } + ); } diff --git a/src/hooks/useNetwork.ts b/src/hooks/useNetwork.ts index 9d5e1e75d7c8..4dfa3a322df8 100644 --- a/src/hooks/useNetwork.ts +++ b/src/hooks/useNetwork.ts @@ -6,13 +6,13 @@ type UseNetworkProps = { onReconnect?: () => void; }; -type UseNetwork = {isOffline: boolean}; +type UseNetwork = {isOffline: boolean; isBackendReachable: boolean}; export default function useNetwork({onReconnect = () => {}}: UseNetworkProps = {}): UseNetwork { const callback = useRef(onReconnect); callback.current = onReconnect; - const {isOffline} = useContext(NetworkContext) ?? CONST.DEFAULT_NETWORK_DATA; + const {isOffline, isBackendReachable} = useContext(NetworkContext) ?? CONST.DEFAULT_NETWORK_DATA; const prevOfflineStatusRef = useRef(isOffline); useEffect(() => { // If we were offline before and now we are not offline then we just reconnected @@ -29,5 +29,5 @@ export default function useNetwork({onReconnect = () => {}}: UseNetworkProps = { prevOfflineStatusRef.current = isOffline; }, [isOffline]); - return {isOffline: isOffline ?? false}; + return {isOffline: isOffline ?? false, isBackendReachable: isBackendReachable ?? true}; } diff --git a/src/languages/en.ts b/src/languages/en.ts index eecd81c54123..da67933011a5 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -266,6 +266,7 @@ export default { conciergeHelp: 'Please reach out to Concierge for help.', maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `You've selected the maximum number (${count}) of participants.`, youAppearToBeOffline: 'You appear to be offline.', + weMightHaveProblem: 'We might have a problem. Check out ', thisFeatureRequiresInternet: 'This feature requires an active internet connection to be used.', areYouSure: 'Are you sure?', verify: 'Verify', diff --git a/src/languages/es.ts b/src/languages/es.ts index cd36f9071de6..3e3b6be636eb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -256,6 +256,7 @@ export default { conciergeHelp: 'Por favor, contacta con Concierge para obtener ayuda.', maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', + weMightHaveProblem: 'We might have a problem. Check out', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', areYouSure: '¿Estás seguro?', verify: 'Verifique', diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index f5c391aad09c..0a3987e97152 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -2,7 +2,6 @@ import NetInfo from '@react-native-community/netinfo'; import throttle from 'lodash/throttle'; import Onyx from 'react-native-onyx'; import CONFIG from '@src/CONFIG'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as NetworkActions from './actions/Network'; import AppStateMonitor from './AppStateMonitor'; @@ -39,6 +38,7 @@ function setOfflineStatus(isCurrentlyOffline: boolean): void { // When reconnecting, ie, going from offline to online, all the reconnection callbacks // are triggered (this is usually Actions that need to re-download data from the server) if (isOffline && !isCurrentlyOffline) { + NetworkActions.setIsBackendReachable(true); triggerReconnectionCallbacks('offline status changed'); } @@ -72,35 +72,41 @@ Onyx.connect({ * internet connectivity or not. This is more reliable than the Pusher * `disconnected` event which takes about 10-15 seconds to emit. */ -function subscribeToNetInfo(): void { - // Note: We are disabling the configuration for NetInfo when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". +function subscribeToNetInfo() { + let backendReachabilityCheckInterval: NodeJS.Timeout; + // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. if (!CONFIG.IS_USING_LOCAL_WEB) { - // Calling NetInfo.configure (re)checks current state. We use it to force a recheck whenever we (re)subscribe - NetInfo.configure({ - // By default, NetInfo uses `/` for `reachabilityUrl` - // When App is served locally (or from Electron) this address is always reachable - even offline + // Set interval to (re)checks current state every 15 seconds + const BACKEND_REACHABILITY_CHECK_INTERVAL = 15000; + + backendReachabilityCheckInterval = setInterval(() => { + // Offline status also implies backend unreachability + if (isOffline) { + return; + } + // When App is served locally (or from Electron) is always reachable - even offline // Using the API url ensures reachability is tested over internet - reachabilityUrl: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=Ping`, - reachabilityMethod: 'GET', - reachabilityTest: (response) => { - if (!response.ok) { - return Promise.resolve(false); - } - return response - .json() - .then((json) => Promise.resolve(json.jsonCode === 200)) - .catch(() => Promise.resolve(false)); - }, - - // If a check is taking longer than this time we're considered offline - reachabilityRequestTimeout: CONST.NETWORK.MAX_PENDING_TIME_MS, - }); + fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=Ping`, { + method: 'GET', + cache: 'no-cache', + }) + .then((response) => { + if (!response.ok) { + return Promise.resolve(false); + } + return response + .json() + .then((json) => Promise.resolve(json.jsonCode === 200)) + .catch(() => Promise.resolve(false)); + }) + .then(NetworkActions.setIsBackendReachable); + }, BACKEND_REACHABILITY_CHECK_INTERVAL); } // Subscribe to the state change event via NetInfo so we can update // whether a user has internet connectivity or not. - NetInfo.addEventListener((state) => { + const unsubscribeNetInfo = NetInfo.addEventListener((state) => { Log.info('[NetworkConnection] NetInfo state change', false, {...state}); if (shouldForceOffline) { Log.info('[NetworkConnection] Not setting offline status because shouldForceOffline = true'); @@ -108,6 +114,11 @@ function subscribeToNetInfo(): void { } setOfflineStatus(state.isInternetReachable === false); }); + + return () => { + clearInterval(backendReachabilityCheckInterval); + unsubscribeNetInfo(); + }; } function listenForReconnect() { diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index e71094eded05..72a6bfcb3beb 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -1,6 +1,10 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; +function setIsBackendReachable(isBackendReachable: boolean) { + Onyx.merge(ONYXKEYS.NETWORK, {isBackendReachable}); +} + function setIsOffline(isOffline: boolean) { Onyx.merge(ONYXKEYS.NETWORK, {isOffline}); } @@ -20,4 +24,4 @@ function setShouldFailAllRequests(shouldFailAllRequests: boolean) { Onyx.merge(ONYXKEYS.NETWORK, {shouldFailAllRequests}); } -export {setIsOffline, setShouldForceOffline, setShouldFailAllRequests, setTimeSkew}; +export {setIsBackendReachable, setIsOffline, setShouldForceOffline, setShouldFailAllRequests, setTimeSkew}; diff --git a/src/types/onyx/Network.ts b/src/types/onyx/Network.ts index 173ca486b53c..d4162d1ffdb7 100644 --- a/src/types/onyx/Network.ts +++ b/src/types/onyx/Network.ts @@ -2,6 +2,8 @@ type Network = { /** Is the network currently offline or not */ isOffline: boolean; + isBackendReachable: boolean; + /** Should the network be forced offline */ shouldForceOffline?: boolean; From 4b24bd98d2e9fbefb68b2615ea0d86b84074a3aa Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 15 Mar 2024 15:20:31 +0700 Subject: [PATCH 0020/1525] show status page hostname only --- src/CONST.ts | 2 +- src/components/OfflineIndicator.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 472c980ebaca..515054f3132b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -507,7 +507,7 @@ const CONST = { EMPTY_ARRAY, EMPTY_OBJECT, USE_EXPENSIFY_URL, - STATUS_EXPENSIFY_URL: 'https://status.expensify.com/', + STATUS_EXPENSIFY_URL: 'https://status.expensify.com', GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com', GOOGLE_DOC_IMAGE_LINK_MATCH: 'googleusercontent.com', IMAGE_BASE64_MATCH: 'base64', diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index a550a2a6a563..bd57c176db65 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -61,7 +61,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { href={CONST.STATUS_EXPENSIFY_URL} style={styles.link} > - {CONST.STATUS_EXPENSIFY_URL} + {new URL(CONST.STATUS_EXPENSIFY_URL).host} . From 7e8cf7fce86c9a782f164e99a2157beb0b79535a Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 15 Mar 2024 18:27:44 +0700 Subject: [PATCH 0021/1525] adjust polling timeout and status page link style --- src/CONST.ts | 1 + src/components/OfflineIndicator.tsx | 5 ++--- src/libs/NetworkConnection.ts | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 515054f3132b..9ab45c6537c2 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -912,6 +912,7 @@ const CONST = { MAX_RETRY_WAIT_TIME_MS: 10 * 1000, PROCESS_REQUEST_DELAY_MS: 1000, MAX_PENDING_TIME_MS: 10 * 1000, + REACHABILITY_TIMEOUT_MS: 60 * 1000, MAX_REQUEST_RETRIES: 10, }, WEEK_STARTS_ON: 1, // Monday diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index bd57c176db65..4ed01a531cd2 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -36,8 +36,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); - // Truthy isBackendReachable implies both online and normal backend reachability - if (isBackendReachable) { + if (!isOffline && isBackendReachable) { return null; } @@ -59,7 +58,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { {translate('common.weMightHaveProblem')} {new URL(CONST.STATUS_EXPENSIFY_URL).host} diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 0a3987e97152..56f6f7e3c786 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -2,6 +2,7 @@ import NetInfo from '@react-native-community/netinfo'; import throttle from 'lodash/throttle'; import Onyx from 'react-native-onyx'; import CONFIG from '@src/CONFIG'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as NetworkActions from './actions/Network'; import AppStateMonitor from './AppStateMonitor'; @@ -77,15 +78,12 @@ function subscribeToNetInfo() { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. if (!CONFIG.IS_USING_LOCAL_WEB) { - // Set interval to (re)checks current state every 15 seconds - const BACKEND_REACHABILITY_CHECK_INTERVAL = 15000; - + // Set interval to (re)check current state every 60 seconds backendReachabilityCheckInterval = setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { return; } - // When App is served locally (or from Electron) is always reachable - even offline // Using the API url ensures reachability is tested over internet fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=Ping`, { method: 'GET', @@ -100,8 +98,9 @@ function subscribeToNetInfo() { .then((json) => Promise.resolve(json.jsonCode === 200)) .catch(() => Promise.resolve(false)); }) - .then(NetworkActions.setIsBackendReachable); - }, BACKEND_REACHABILITY_CHECK_INTERVAL); + .then(NetworkActions.setIsBackendReachable) + .catch(() => NetworkActions.setIsBackendReachable(false)); + }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); } // Subscribe to the state change event via NetInfo so we can update From c7b627d62df4769eb6eb3bed1bc4076b3023b05f Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 15 Mar 2024 18:46:37 +0700 Subject: [PATCH 0022/1525] update comment --- src/Expensify.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 7a2a2189b0fa..08fb4f269501 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -138,7 +138,7 @@ function Expensify({ // Initialize this client as being an active client ActiveClientManager.init(); - // Used for the offline indicator appearing when someone is offline + // Used for the offline indicator appearing when someone is offline or backend is unreachable const unsubscribeNetInfo = NetworkConnection.subscribeToNetInfo(); return () => unsubscribeNetInfo(); From e2b9966bd89653e470773a6eb2a2c663cfcb4bc3 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:25:09 +0530 Subject: [PATCH 0023/1525] add POC for fallingback to default attachment view when pdf fails to load --- .../BaseAttachmentViewPdf.js | 2 + .../AttachmentView/AttachmentViewPdf/index.js | 4 +- .../AttachmentViewPdf/propTypes.js | 3 ++ .../Attachments/AttachmentView/index.js | 16 ++++++++ src/components/PDFView/index.native.js | 38 +------------------ 5 files changed, 25 insertions(+), 38 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js index 289909a66dab..3f3b995c02ba 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js @@ -29,6 +29,7 @@ function BaseAttachmentViewPdf({ errorLabelStyles, style, isUsedAsChatAttachment, + onError, }) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled; @@ -91,6 +92,7 @@ function BaseAttachmentViewPdf({ onLoadComplete={onLoadComplete} errorLabelStyles={errorLabelStyles} isUsedAsChatAttachment={isUsedAsChatAttachment} + onError={onError} /> ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index af665d73a3ba..6377bc6e585a 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, isUsedAsChatAttachment}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, onError}) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js index b6ad86124f63..bcb0fabde352 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js @@ -16,6 +16,9 @@ const attachmentViewPdfPropTypes = { /** Styles for the error label */ errorLabelStyles: stylePropTypes, + /** Callback when the pdf fails to load */ + onError: PropTypes.func, + /** Whether the attachment is used as a chat attachment */ isUsedAsChatAttachment: PropTypes.bool, }; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 6ba3035afc86..863f37733972 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -26,6 +26,7 @@ import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; +import DefaultAttachmentView from './AttachmentViewPdf/DefaultAttachmentView'; import AttachmentViewVideo from './AttachmentViewVideo'; import {attachmentViewDefaultProps, attachmentViewPropTypes} from './propTypes'; @@ -108,6 +109,7 @@ function AttachmentView({ const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); + const [isPdfFailedToLoad, setIsPdfFailedToLoad] = useState(false); useEffect(() => { if (!isFocused && !(file && isUsedInAttachmentModal)) { @@ -170,6 +172,17 @@ function AttachmentView({ } }; + if (isPdfFailedToLoad) { + return ( + + ); + } + // We need the following View component on android native // So that the event will propagate properly and // the Password protected preview will be shown for pdf attachement we are about to send. @@ -188,6 +201,9 @@ function AttachmentView({ style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} + onError={() => { + setIsPdfFailedToLoad(true); + }} /> ); diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index addbcec9b3bf..2f9bb1a40573 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -2,19 +2,13 @@ import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import PDF from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import Text from '@components/Text'; import useKeyboardState from '@hooks/useKeyboardState'; import useLocalize from '@hooks/useLocalize'; -import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; @@ -41,7 +35,7 @@ const propTypes = { const THUMBNAIL_HEIGHT = 250; const THUMBNAIL_WIDTH = 250; -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, isUsedAsChatAttachment}) { +function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -54,8 +48,6 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused const themeStyles = useThemeStyles(); const {isKeyboardShown} = useKeyboardState(); const StyleUtils = useStyleUtils(); - const {isOffline} = useNetwork(); - const theme = useTheme(); useEffect(() => { onToggleKeyboard(isKeyboardShown); @@ -94,6 +86,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setShouldShowLoadingIndicator(false); setShouldRequestPassword(false); setShouldAttemptPDFLoad(false); + onError(error); }; /** @@ -123,29 +116,6 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setSuccessToLoadPDF(true); onLoadComplete(path); }; - // eslint-disable-next-line no-unused-vars - const rendeFailedToLoadPDF = () => { - if (!isUsedAsChatAttachment) { - return ( - - {translate('attachmentView.failedToLoadPDF')} - - ); - } - - return ( - - - - - - ); - }; function renderPDFView() { const pdfHeight = isUsedAsChatAttachment ? THUMBNAIL_HEIGHT : windowHeight; @@ -194,10 +164,6 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused ); } - if (failedToLoadPDF) { - return rendeFailedToLoadPDF(); - } - return onPress && !successToLoadPDF ? ( Date: Sat, 16 Mar 2024 03:17:27 +0530 Subject: [PATCH 0024/1525] fixes missing prop --- .../Attachments/AttachmentView/AttachmentViewPdf/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index 6377bc6e585a..f151bf84b7a8 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, onError}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, onError, isUsedAsChatAttachment}) { return ( ); } From b4ac9a1cb3518627a16a401654a80ef47ef6625d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 03:20:24 +0530 Subject: [PATCH 0025/1525] remove unused import --- src/components/Attachments/AttachmentView/index.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 863f37733972..56ebad34e963 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -26,7 +26,6 @@ import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; -import DefaultAttachmentView from './AttachmentViewPdf/DefaultAttachmentView'; import AttachmentViewVideo from './AttachmentViewVideo'; import {attachmentViewDefaultProps, attachmentViewPropTypes} from './propTypes'; @@ -159,7 +158,7 @@ function AttachmentView({ // Check both source and file.name since PDFs dragged into the text field // will appear with a source that is a blob - if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { + if (!isPdfFailedToLoad && ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename'))))) { const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; const onPDFLoadComplete = (path) => { @@ -172,17 +171,6 @@ function AttachmentView({ } }; - if (isPdfFailedToLoad) { - return ( - - ); - } - // We need the following View component on android native // So that the event will propagate properly and // the Password protected preview will be shown for pdf attachement we are about to send. From 03726012f10ad31c29f082ba614a9175d0110b62 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 03:23:31 +0530 Subject: [PATCH 0026/1525] change variable name --- src/components/PDFView/index.native.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 2f9bb1a40573..c0f36f310346 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -32,8 +32,8 @@ const propTypes = { * is (temporarily) rendered. */ -const THUMBNAIL_HEIGHT = 250; -const THUMBNAIL_WIDTH = 250; +const LOADING_THUMBNAIL_HEIGHT = 250; +const LOADING_THUMBNAIL_WIDTH = 250; function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); @@ -118,8 +118,8 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused }; function renderPDFView() { - const pdfHeight = isUsedAsChatAttachment ? THUMBNAIL_HEIGHT : windowHeight; - const pdfWeight = isUsedAsChatAttachment ? THUMBNAIL_WIDTH : windowWidth; + const pdfHeight = isUsedAsChatAttachment ? LOADING_THUMBNAIL_HEIGHT : windowHeight; + const pdfWeight = isUsedAsChatAttachment ? LOADING_THUMBNAIL_WIDTH : windowWidth; const pdfStyles = [StyleUtils.getWidthAndHeightStyle(pdfWeight, pdfHeight), themeStyles.imageModalPDF]; // If we haven't yet successfully validated the password and loaded the PDF, @@ -131,7 +131,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused } const containerStyles = (shouldRequestPassword && isSmallScreenWidth) || isUsedAsChatAttachment ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; - const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)] : []; + const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] : []; return ( From a20811ae16bab37acca9005f8b481a52f5be6d31 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 03:57:40 +0530 Subject: [PATCH 0027/1525] fixes lint --- .../AttachmentCarousel/CarouselItem.js | 1 + .../Attachments/AttachmentView/index.js | 2 +- src/components/PDFView/WebPDFDocument.js | 9 +-------- src/components/PDFView/index.native.js | 18 +++++++++--------- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index b2c9fed64467..e924cb8c13e9 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -109,6 +109,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) { isHovered={isModalHovered} isFocused={isFocused} optionalVideoDuration={item.duration} + isUsedInCarousel /> diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 56ebad34e963..4a167ba35c5d 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -188,7 +188,7 @@ function AttachmentView({ errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} - isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} + isUsedAsChatAttachment={!isUsedInAttachmentModal && !isUsedInCarousel} onError={() => { setIsPdfFailedToLoad(true); }} diff --git a/src/components/PDFView/WebPDFDocument.js b/src/components/PDFView/WebPDFDocument.js index d2e93e3ba506..3b1b3ba7c765 100644 --- a/src/components/PDFView/WebPDFDocument.js +++ b/src/components/PDFView/WebPDFDocument.js @@ -5,16 +5,11 @@ import {Document} from 'react-pdf'; import {VariableSizeList as List} from 'react-window'; import _ from 'underscore'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import Text from '@components/Text'; import stylePropTypes from '@styles/stylePropTypes'; import CONST from '@src/CONST'; import PageRenderer from './WebPDFPageRenderer'; const propTypes = { - /** Index of the PDF page to be displayed passed by VariableSizeList */ - errorLabelStyles: stylePropTypes, - /** Returns translated string for given locale and phrase */ - translate: PropTypes.func.isRequired, /** The source URL from which to load PDF file to be displayed */ sourceURL: PropTypes.string.isRequired, /** Callback invoked when the PDF document is loaded successfully */ @@ -53,7 +48,6 @@ const propTypes = { }; const defaultProps = { - errorLabelStyles: [], numPages: null, listStyle: undefined, password: undefined, @@ -61,8 +55,6 @@ const defaultProps = { const WebPDFDocument = memo( ({ - errorLabelStyles, - translate, sourceURL, onDocumentLoadSuccess, pageViewportsLength, @@ -78,6 +70,7 @@ const WebPDFDocument = memo( listStyle, initiatePasswordChallenge, password, + onError, }) => { const onPassword = useCallback( (callback, reason) => { diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index c0f36f310346..87262a566bc7 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -1,5 +1,5 @@ -import React, {useCallback, useEffect, useState} from 'react'; -import {View} from 'react-native'; +import React, { useCallback, useEffect, useState } from 'react'; +import { View } from 'react-native'; import PDF from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; @@ -11,7 +11,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; -import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; +import { defaultProps, propTypes as pdfViewPropTypes } from './pdfViewPropTypes'; const propTypes = { ...pdfViewPropTypes, @@ -35,7 +35,7 @@ const propTypes = { const LOADING_THUMBNAIL_HEIGHT = 250; const LOADING_THUMBNAIL_WIDTH = 250; -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment}) { +function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment }) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -43,10 +43,10 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused const [failedToLoadPDF, setFailedToLoadPDF] = useState(false); const [successToLoadPDF, setSuccessToLoadPDF] = useState(false); const [password, setPassword] = useState(''); - const {windowWidth, windowHeight, isSmallScreenWidth} = useWindowDimensions(); - const {translate} = useLocalize(); + const { windowWidth, windowHeight, isSmallScreenWidth } = useWindowDimensions(); + const { translate } = useLocalize(); const themeStyles = useThemeStyles(); - const {isKeyboardShown} = useKeyboardState(); + const { isKeyboardShown } = useKeyboardState(); const StyleUtils = useStyleUtils(); useEffect(() => { @@ -130,7 +130,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused pdfStyles.push(themeStyles.invisible); } const containerStyles = - (shouldRequestPassword && isSmallScreenWidth) || isUsedAsChatAttachment ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; + isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] : []; return ( @@ -140,7 +140,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused fitPolicy={0} trustAllCerts={false} renderActivityIndicator={() => } - source={{uri: sourceURL}} + source={{ uri: sourceURL, cache: true, expiration: 864000 }} style={pdfStyles} onError={handleFailureToLoadPDF} password={password} From 3ace4d305de71b924bd53a82d9e54f864fc07461 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 04:00:16 +0530 Subject: [PATCH 0028/1525] use correct conditional and use js docs for props --- .../BaseAnchorForAttachmentsOnly.tsx | 1 + src/components/Attachments/AttachmentView/index.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index d389ac4b92f0..3e7189ce0304 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -64,6 +64,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow file={{name: displayName}} shouldShowDownloadIcon={!isOffline} shouldShowLoadingSpinnerIcon={isDownloading} + isUsedAsChatAttachment /> )} diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 4a167ba35c5d..0d25de1bd799 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -62,9 +62,14 @@ const propTypes = { /** The id of the report action related to the attachment */ reportActionID: PropTypes.string, + /** Whether the attachment is currently being hovered over */ isHovered: PropTypes.bool, + /** The duration of the video */ optionalVideoDuration: PropTypes.number, + + /** Whether the attachment is being used as a chat attachment */ + isUsedAsChatAttachment: PropTypes.bool, }; const defaultProps = { @@ -79,6 +84,7 @@ const defaultProps = { reportActionID: '', isHovered: false, optionalVideoDuration: 0, + isUsedAsChatAttachment: false, }; function AttachmentView({ @@ -101,6 +107,7 @@ function AttachmentView({ reportActionID, isHovered, optionalVideoDuration, + isUsedAsChatAttachment, }) { const {updateCurrentlyPlayingURL} = usePlaybackContext(); const theme = useTheme(); @@ -188,7 +195,7 @@ function AttachmentView({ errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} - isUsedAsChatAttachment={!isUsedInAttachmentModal && !isUsedInCarousel} + isUsedAsChatAttachment={isUsedAsChatAttachment} onError={() => { setIsPdfFailedToLoad(true); }} From 830a392f5097a91c9d3689c7f500bb54ec0c28e2 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 04:06:08 +0530 Subject: [PATCH 0029/1525] prettier diffs --- src/components/PDFView/index.native.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 87262a566bc7..1c3cd02308fb 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -1,5 +1,5 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { View } from 'react-native'; +import React, {useCallback, useEffect, useState} from 'react'; +import {View} from 'react-native'; import PDF from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; @@ -11,7 +11,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; -import { defaultProps, propTypes as pdfViewPropTypes } from './pdfViewPropTypes'; +import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; const propTypes = { ...pdfViewPropTypes, @@ -35,7 +35,7 @@ const propTypes = { const LOADING_THUMBNAIL_HEIGHT = 250; const LOADING_THUMBNAIL_WIDTH = 250; -function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment }) { +function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -43,10 +43,10 @@ function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocuse const [failedToLoadPDF, setFailedToLoadPDF] = useState(false); const [successToLoadPDF, setSuccessToLoadPDF] = useState(false); const [password, setPassword] = useState(''); - const { windowWidth, windowHeight, isSmallScreenWidth } = useWindowDimensions(); - const { translate } = useLocalize(); + const {windowWidth, windowHeight, isSmallScreenWidth} = useWindowDimensions(); + const {translate} = useLocalize(); const themeStyles = useThemeStyles(); - const { isKeyboardShown } = useKeyboardState(); + const {isKeyboardShown} = useKeyboardState(); const StyleUtils = useStyleUtils(); useEffect(() => { @@ -131,7 +131,9 @@ function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocuse } const containerStyles = isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; - const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] : []; + const loadingIndicatorStyles = isUsedAsChatAttachment + ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] + : []; return ( @@ -140,7 +142,7 @@ function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocuse fitPolicy={0} trustAllCerts={false} renderActivityIndicator={() => } - source={{ uri: sourceURL, cache: true, expiration: 864000 }} + source={{uri: sourceURL, cache: true, expiration: 864000}} style={pdfStyles} onError={handleFailureToLoadPDF} password={password} From 3813026c23140b62b6b566ec587f4d1da9bdc15c Mon Sep 17 00:00:00 2001 From: Ishpaul Singh <104348397+ishpaul777@users.noreply.github.com> Date: Sat, 16 Mar 2024 04:10:54 +0530 Subject: [PATCH 0030/1525] Update src/components/Attachments/AttachmentCarousel/CarouselItem.js --- src/components/Attachments/AttachmentCarousel/CarouselItem.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index e924cb8c13e9..b2c9fed64467 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -109,7 +109,6 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) { isHovered={isModalHovered} isFocused={isFocused} optionalVideoDuration={item.duration} - isUsedInCarousel /> From a8cfafea38386727c62d8cdb4081fae9d0b0d62a Mon Sep 17 00:00:00 2001 From: Ishpaul Singh <104348397+ishpaul777@users.noreply.github.com> Date: Sat, 16 Mar 2024 04:21:58 +0530 Subject: [PATCH 0031/1525] remove unused styles --- src/styles/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index ffd05b97473d..2efb29a95ea5 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -516,10 +516,6 @@ const styles = (theme: ThemeColors) => borderRadius: variables.buttonBorderRadius, }, - componentBorderRadiusNormal: { - borderRadius: variables.componentBorderRadiusNormal, - }, - bottomTabBarContainer: { flexDirection: 'row', height: variables.bottomTabHeight, From f3be10027c4d786662253794c2cc7831170baf2a Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 18 Mar 2024 18:19:33 +0700 Subject: [PATCH 0032/1525] update spanish copy --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 9ac6e22648c0..9e5e4e0b6064 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -256,7 +256,7 @@ export default { conciergeHelp: 'Por favor, contacta con Concierge para obtener ayuda.', maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', - weMightHaveProblem: 'We might have a problem. Check out', + weMightHaveProblem: 'We might have a problem. Check out ', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', areYouSure: '¿Estás seguro?', verify: 'Verifique', From d3faf922c7f9784c5b51edbdb208fa3126d6768d Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 18 Mar 2024 18:51:52 +0700 Subject: [PATCH 0033/1525] Update Espanol copy --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 9e5e4e0b6064..3ccf191221fb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -256,7 +256,7 @@ export default { conciergeHelp: 'Por favor, contacta con Concierge para obtener ayuda.', maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', - weMightHaveProblem: 'We might have a problem. Check out ', + weMightHaveProblem: 'Peude que te tengamos un problema. Echa un vistazo a ', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', areYouSure: '¿Estás seguro?', verify: 'Verifique', From a45f6f0ba20490a03bbc5c74d77097eb30a3cd1b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 19 Mar 2024 13:37:32 +0530 Subject: [PATCH 0034/1525] remove error label styles --- .../AttachmentViewPdf/BaseAttachmentViewPdf.js | 2 -- .../Attachments/AttachmentView/AttachmentViewPdf/index.js | 3 +-- .../AttachmentView/AttachmentViewPdf/propTypes.js | 4 ---- src/components/Attachments/AttachmentView/index.js | 1 - src/components/PDFView/index.js | 1 - src/components/PDFView/pdfViewPropTypes.js | 8 +++++--- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js index 3f3b995c02ba..8ae5a483c9fa 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js @@ -26,7 +26,6 @@ function BaseAttachmentViewPdf({ onScaleChanged: onScaleChangedProp, onToggleKeyboard, onLoadComplete, - errorLabelStyles, style, isUsedAsChatAttachment, onError, @@ -90,7 +89,6 @@ function BaseAttachmentViewPdf({ onToggleKeyboard={onToggleKeyboard} onScaleChanged={onScaleChanged} onLoadComplete={onLoadComplete} - errorLabelStyles={errorLabelStyles} isUsedAsChatAttachment={isUsedAsChatAttachment} onError={onError} /> diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index f151bf84b7a8..1d5ffbccf40c 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, onError, isUsedAsChatAttachment}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, onError, isUsedAsChatAttachment}) { return ( diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js index bcb0fabde352..75984e6ac031 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js @@ -13,9 +13,6 @@ const attachmentViewPdfPropTypes = { /** Additional style props */ style: stylePropTypes, - /** Styles for the error label */ - errorLabelStyles: stylePropTypes, - /** Callback when the pdf fails to load */ onError: PropTypes.func, @@ -28,7 +25,6 @@ const attachmentViewPdfDefaultProps = { name: '', }, style: [], - errorLabelStyles: [], onError: () => {}, isUsedAsChatAttachment: false, }; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 0d25de1bd799..701f6343eb25 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -192,7 +192,6 @@ function AttachmentView({ onPress={onPress} onToggleKeyboard={onToggleKeyboard} onLoadComplete={onPDFLoadComplete} - errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} isUsedAsChatAttachment={isUsedAsChatAttachment} diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 2e39594fc210..838fd843b089 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -254,7 +254,6 @@ class PDFView extends Component { > {}, onLoadComplete: () => {}, isFocused: false, - errorLabelStyles: [], }; export {propTypes, defaultProps}; From aae3f37b09dbb9ee0dd95d7d69464f7ff9a1de7c Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 20 Mar 2024 01:07:35 +0700 Subject: [PATCH 0035/1525] update comments --- src/components/OfflineIndicator.tsx | 2 +- src/libs/NetworkConnection.ts | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index 4ed01a531cd2..bb497fad5b4e 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -50,7 +50,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { /> { - // Do not reorder the condition, we only show unreachability message when online and backend is unreachable + // If we reversed the ternary, unreachability message would always show even when offline isOffline ? ( translate('common.youAppearToBeOffline') ) : ( diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index cbfb4f9bfba3..96acc4c7d336 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -69,16 +69,14 @@ Onyx.connect({ }); /** - * Set up the event listener for NetInfo to tell whether the user has - * internet connectivity or not. This is more reliable than the Pusher - * `disconnected` event which takes about 10-15 seconds to emit. + * Monitor internet connectivity and perform periodic backend reachability checks */ function subscribeToNetInfo() { let backendReachabilityCheckInterval: NodeJS.Timeout; // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. if (!CONFIG.IS_USING_LOCAL_WEB) { - // Set interval to (re)check current state every 60 seconds + // Set interval to periodically (re)check backend status backendReachabilityCheckInterval = setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { @@ -103,8 +101,11 @@ function subscribeToNetInfo() { }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); } - // Subscribe to the state change event via NetInfo so we can update - // whether a user has internet connectivity or not. + /** + * Set up the event listener for NetInfo to tell whether the user has + * internet connectivity or not. This is more reliable than the Pusher + * `disconnected` event which takes about 10-15 seconds to emit. + */ const unsubscribeNetInfo = NetInfo.addEventListener((state) => { Log.info('[NetworkConnection] NetInfo state change', false, {...state}); if (shouldForceOffline) { From 4c84f89b10cda8b663c5dd7168ba6447cac0d9cb Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 20 Mar 2024 17:45:51 +0700 Subject: [PATCH 0036/1525] refactor: extract subscribeToBackendReachability and modify minor comments --- src/Expensify.tsx | 4 +-- src/libs/NetworkConnection.ts | 68 ++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 08fb4f269501..4f4a9e53bf5a 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -139,9 +139,9 @@ function Expensify({ ActiveClientManager.init(); // Used for the offline indicator appearing when someone is offline or backend is unreachable - const unsubscribeNetInfo = NetworkConnection.subscribeToNetInfo(); + const unsubscribeNetworkStatus = NetworkConnection.subscribeToNetworkStatus(); - return () => unsubscribeNetInfo(); + return () => unsubscribeNetworkStatus(); }, []); useEffect(() => { diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 10703eade109..511ecdd5614a 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -68,44 +68,46 @@ Onyx.connect({ }, }); +/** + * Set interval to periodically (re)check backend status + * @returns for use with clearInterval + */ +function subscribeToBackendReachability() { + return setInterval(() => { + // Offline status also implies backend unreachability + if (isOffline) { + return; + } + // Using the API url ensures reachability is tested over internet + fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/Ping`, { + method: 'GET', + cache: 'no-cache', + }) + .then((response) => { + if (!response.ok) { + return Promise.resolve(false); + } + return response + .json() + .then((json) => Promise.resolve(json.jsonCode === 200)) + .catch(() => Promise.resolve(false)); + }) + .then(NetworkActions.setIsBackendReachable) + .catch(() => NetworkActions.setIsBackendReachable(false)); + }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); +} + /** * Monitor internet connectivity and perform periodic backend reachability checks */ -function subscribeToNetInfo() { - let backendReachabilityCheckInterval: NodeJS.Timeout; +function subscribeToNetworkStatus() { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. - if (!CONFIG.IS_USING_LOCAL_WEB) { - // Set interval to periodically (re)check backend status - backendReachabilityCheckInterval = setInterval(() => { - // Offline status also implies backend unreachability - if (isOffline) { - return; - } - // Using the API url ensures reachability is tested over internet - fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/Ping`, { - method: 'GET', - cache: 'no-cache', - }) - .then((response) => { - if (!response.ok) { - return Promise.resolve(false); - } - return response - .json() - .then((json) => Promise.resolve(json.jsonCode === 200)) - .catch(() => Promise.resolve(false)); - }) - .then(NetworkActions.setIsBackendReachable) - .catch(() => NetworkActions.setIsBackendReachable(false)); - }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); - } + const backendReachabilityCheckInterval = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; - /** - * Set up the event listener for NetInfo to tell whether the user has - * internet connectivity or not. This is more reliable than the Pusher - * `disconnected` event which takes about 10-15 seconds to emit. - */ + // Set up the event listener for NetInfo to tell whether the user has + // internet connectivity or not. This is more reliable than the Pusher + // `disconnected` event which takes about 10-15 seconds to emit. const unsubscribeNetInfo = NetInfo.addEventListener((state) => { Log.info('[NetworkConnection] NetInfo state change', false, {...state}); if (shouldForceOffline) { @@ -167,5 +169,5 @@ export default { onReconnect, triggerReconnectionCallbacks, recheckNetworkConnection, - subscribeToNetInfo, + subscribeToNetworkStatus, }; From 0b5dec8bc4cf9b28cf7c4b05be55189b23f5474a Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 20 Mar 2024 17:49:38 +0700 Subject: [PATCH 0037/1525] refactor: add returns doc --- src/libs/NetworkConnection.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 511ecdd5614a..eb87da2b0684 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -72,7 +72,7 @@ Onyx.connect({ * Set interval to periodically (re)check backend status * @returns for use with clearInterval */ -function subscribeToBackendReachability() { +function subscribeToBackendReachability(): NodeJS.Timeout { return setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { @@ -99,8 +99,9 @@ function subscribeToBackendReachability() { /** * Monitor internet connectivity and perform periodic backend reachability checks + * @returns unsubscribe method */ -function subscribeToNetworkStatus() { +function subscribeToNetworkStatus(): () => void { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. const backendReachabilityCheckInterval = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; From b7be2f2acff75a13bd15543275c8dddca3348ed2 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 20 Mar 2024 18:19:16 +0700 Subject: [PATCH 0038/1525] refactor: return cleanup interval function --- src/libs/NetworkConnection.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index eb87da2b0684..57ae5be3f9fa 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -70,10 +70,10 @@ Onyx.connect({ /** * Set interval to periodically (re)check backend status - * @returns for use with clearInterval + * @returns clearInterval cleanup */ -function subscribeToBackendReachability(): NodeJS.Timeout { - return setInterval(() => { +function subscribeToBackendReachability(): () => void { + const intervalID = setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { return; @@ -95,6 +95,10 @@ function subscribeToBackendReachability(): NodeJS.Timeout { .then(NetworkActions.setIsBackendReachable) .catch(() => NetworkActions.setIsBackendReachable(false)); }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); + + return () => { + clearInterval(intervalID); + }; } /** @@ -104,7 +108,7 @@ function subscribeToBackendReachability(): NodeJS.Timeout { function subscribeToNetworkStatus(): () => void { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. - const backendReachabilityCheckInterval = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; + const unsubscribeFromBackendReachability = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; // Set up the event listener for NetInfo to tell whether the user has // internet connectivity or not. This is more reliable than the Pusher @@ -119,7 +123,7 @@ function subscribeToNetworkStatus(): () => void { }); return () => { - clearInterval(backendReachabilityCheckInterval); + unsubscribeFromBackendReachability?.(); unsubscribeNetInfo(); }; } From 2812c29ae8e72466f67937600a0e5bee651a5e5c Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 21 Mar 2024 04:43:10 +0530 Subject: [PATCH 0039/1525] fix: Fix scrollable elements in Policy pages. Signed-off-by: Krishna Gupta --- .../SelectionList/BaseSelectionList.tsx | 71 +++++++++++-------- src/components/SelectionList/types.ts | 2 + .../categories/WorkspaceCategoriesPage.tsx | 20 ++++-- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 015fd284c0b4..94287109386d 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -69,6 +69,7 @@ function BaseSelectionList( listHeaderWrapperStyle, isRowMultilineSupported = false, textInputRef, + ListHeaderComponent, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -339,6 +340,39 @@ function BaseSelectionList( ); }; + const header = () => ( + <> + {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( + + + + {!customListHeader && ( + e.preventDefault() : undefined} + > + {translate('workspace.people.selectAll')} + + )} + + {customListHeader} + + )} + {!headerMessage && !canSelectMultiple && customListHeader} + + ); + const scrollToFocusedIndexOnFirstRender = useCallback( (nativeEvent: LayoutChangeEvent) => { if (shouldUseDynamicMaxToRenderPerBatch) { @@ -523,34 +557,7 @@ function BaseSelectionList( ) : ( <> - {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( - - - - {!customListHeader && ( - e.preventDefault() : undefined} - > - {translate('workspace.people.selectAll')} - - )} - - {customListHeader} - - )} - {!headerMessage && !canSelectMultiple && customListHeader} + {!ListHeaderComponent && header()} ( onLayout={onSectionListLayout} style={(!maxToRenderPerBatch || isInitialSectionListRender) && styles.opacity0} ListFooterComponent={ShowMoreButtonInstance} + ListHeaderComponent={ + ListHeaderComponent && ( + <> + {ListHeaderComponent} + {header()} + + ) + } /> {children} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index fac78ee786a0..504a715c0181 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -250,6 +250,8 @@ type BaseSelectionListProps = Partial & { /** Custom content to display in the header */ headerContent?: ReactNode; + ListHeaderComponent?: React.JSX.Element | null; + /** Custom content to display in the footer */ footerContent?: ReactNode; diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index f3456c3875f5..55bc5ab4520c 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -261,6 +261,15 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat const shouldShowEmptyState = !categoryList.some((category) => category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) && !isLoading; + const getSmallWidthHeaderComponent = () => ( + <> + {getHeaderButtons()} + + {translate('workspace.categories.subtitle')} + + + ); + return ( @@ -291,10 +300,12 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat cancelText={translate('common.cancel')} danger /> - {isSmallScreenWidth && {getHeaderButtons()}} - - {translate('workspace.categories.subtitle')} - + {/* {isSmallScreenWidth && {getHeaderButtons()}} */} + {!isSmallScreenWidth && ( + + {translate('workspace.categories.subtitle')} + + )} {isLoading && ( )} From cfd7a8b5de5c63f058364e9dfc6811c5def6401b Mon Sep 17 00:00:00 2001 From: Brandon Henry Date: Tue, 26 Mar 2024 21:10:53 -0500 Subject: [PATCH 0040/1525] added new styling for headermessage --- src/components/SelectionList/BaseSelectionList.tsx | 3 ++- src/components/SelectionList/types.ts | 3 +++ src/pages/SearchPage/index.tsx | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 015fd284c0b4..6e31f84b24ed 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -69,6 +69,7 @@ function BaseSelectionList( listHeaderWrapperStyle, isRowMultilineSupported = false, textInputRef, + headerMessageStyle = [styles.ph5, styles.pb5] }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -514,7 +515,7 @@ function BaseSelectionList( )} {!!headerMessage && ( - + {headerMessage} )} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 8b070e1aa5cb..9a2cbdd4c13c 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -229,6 +229,9 @@ type BaseSelectionListProps = Partial & { /** Message to display at the top of the list */ headerMessage?: string; + /** Styles to apply to the header message */ + headerMessageStyle?: StyleProp; + /** Text to display on the confirm button */ confirmButtonText?: string; diff --git a/src/pages/SearchPage/index.tsx b/src/pages/SearchPage/index.tsx index 07096ce6c2d5..f535bb014943 100644 --- a/src/pages/SearchPage/index.tsx +++ b/src/pages/SearchPage/index.tsx @@ -168,6 +168,7 @@ function SearchPage({betas, reports, isSearchingForReports, navigation}: SearchP textInputHint={offlineMessage} onChangeText={setSearchValue} headerMessage={headerMessage} + headerMessageStyle={headerMessage === translate('common.noResultsFound') ? [themeStyles.ph4, themeStyles.pb5] : undefined} onLayout={setPerformanceTimersEnd} autoFocus onSelectRow={selectReport} From 32f350390c152890ab3caa67a47fc2bae9833e8b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 29 Mar 2024 02:03:33 +0530 Subject: [PATCH 0041/1525] fix conflicts --- .../Attachments/AttachmentView/AttachmentViewPdf/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index f1f141a625db..da48480e8479 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -13,6 +13,10 @@ type AttachmentViewPdfProps = Pick void; + + onError?: () => void; + + isUsedAsChatAttachment?: boolean; }; export default AttachmentViewPdfProps; From 1c814c51a14085a59de008c08db62912a7fe9476 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 29 Mar 2024 02:09:57 +0530 Subject: [PATCH 0042/1525] fix lint --- .../Attachments/AttachmentView/AttachmentViewPdf/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index d38ffcf7abab..78265c3a9a6a 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import type AttachmentViewPdfProps from './types'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, onError, isUsedAsChatAttachment} : AttachmentViewPdfProps) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, onError, isUsedAsChatAttachment}: AttachmentViewPdfProps) { return ( Date: Fri, 29 Mar 2024 03:38:54 +0530 Subject: [PATCH 0043/1525] fallback to defaultview component --- .../AttachmentViewPdf/index.tsx | 13 +++- .../AttachmentView/AttachmentViewPdf/types.ts | 5 ++ .../DefaultAttachmentView/index.tsx | 58 ++++++++++++++++ .../Attachments/AttachmentView/index.tsx | 68 ++++++------------- src/components/PDFView/index.js | 3 +- 5 files changed, 96 insertions(+), 51 deletions(-) create mode 100644 src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 78265c3a9a6a..10bcf28e7797 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,7 +2,17 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import type AttachmentViewPdfProps from './types'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, onError, isUsedAsChatAttachment}: AttachmentViewPdfProps) { +function AttachmentViewPdf({ + file, + encryptedSourceUrl, + isFocused, + onPress, + onToggleKeyboard, + onLoadComplete, + style, + renderFallbackAttachmentView, + isUsedAsChatAttachment, +}: AttachmentViewPdfProps) { return ( ); diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index da48480e8479..ecc6e5016446 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -14,9 +14,14 @@ type AttachmentViewPdfProps = Pick void; + /** Triggered when the PDF fails to load */ onError?: () => void; + /** Whether the PDF is used as a chat attachment */ isUsedAsChatAttachment?: boolean; + + /** Render a fallback view when the PDF fails to load */ + renderFallbackAttachmentView?: () => JSX.Element; }; export default AttachmentViewPdfProps; diff --git a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx new file mode 100644 index 000000000000..7360144bf09c --- /dev/null +++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {ActivityIndicator, View} from 'react-native'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import Tooltip from '@components/Tooltip'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type DefaultAttachmentViewProps = { + fileName: string; + shouldShowDownloadIcon?: boolean; + shouldShowLoadingSpinnerIcon?: boolean; + containerStyles?: StyleProp; +}; + +function DefaultAttachmentView({fileName, shouldShowDownloadIcon, shouldShowLoadingSpinnerIcon, containerStyles}: DefaultAttachmentViewProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( + + + + + + {fileName} + {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( + + + + + + )} + {shouldShowLoadingSpinnerIcon && ( + + + + + + )} + + ); +} + +export default DefaultAttachmentView; diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index e3b609852e5e..1621d8d9509d 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -1,18 +1,15 @@ import Str from 'expensify-common/lib/str'; -import React, {memo, useEffect, useState} from 'react'; -import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; -import {ActivityIndicator, View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import type {Attachment, AttachmentSource} from '@components/Attachments/types'; +import React, { memo, useEffect, useState } from 'react'; +import type { GestureResponderEvent, StyleProp, ViewStyle } from 'react-native'; +import { View } from 'react-native'; +import type { OnyxEntry } from 'react-native-onyx'; +import { withOnyx } from 'react-native-onyx'; +import type { Attachment, AttachmentSource } from '@components/Attachments/types'; import DistanceEReceipt from '@components/DistanceEReceipt'; import EReceipt from '@components/EReceipt'; import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import ScrollView from '@components/ScrollView'; -import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; -import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import { usePlaybackContext } from '@components/VideoPlayerContexts/PlaybackContext'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -21,13 +18,14 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as CachedPDFPaths from '@libs/actions/CachedPDFPaths'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as TransactionUtils from '@libs/TransactionUtils'; -import type {ColorValue} from '@styles/utils/types'; +import type { ColorValue } from '@styles/utils/types'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Transaction} from '@src/types/onyx'; +import type { Transaction } from '@src/types/onyx'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; import AttachmentViewVideo from './AttachmentViewVideo'; +import DefaultAttachmentView from './DefaultAttachmentView'; type AttachmentViewOnyxProps = { transaction: OnyxEntry; @@ -95,8 +93,8 @@ function AttachmentView({ duration, isUsedAsChatAttachment, }: AttachmentViewProps) { - const {translate} = useLocalize(); - const {updateCurrentlyPlayingURL} = usePlaybackContext(); + const { translate } = useLocalize(); + const { updateCurrentlyPlayingURL } = usePlaybackContext(); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -113,7 +111,7 @@ function AttachmentView({ const [imageError, setImageError] = useState(false); - useNetwork({onReconnect: () => setImageError(false)}); + useNetwork({ onReconnect: () => setImageError(false) }); // Handles case where source is a component (ex: SVG) or a number // Number may represent a SVG or an image @@ -239,36 +237,12 @@ function AttachmentView({ } return ( - - - - - - {file?.name} - {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - - - - - - )} - {shouldShowLoadingSpinnerIcon && ( - - - - - - )} - + ); } @@ -277,9 +251,9 @@ AttachmentView.displayName = 'AttachmentView'; export default memo( withOnyx({ transaction: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + key: ({ transactionID }) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, }, })(AttachmentView), ); -export type {AttachmentViewProps}; +export type { AttachmentViewProps }; diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index e69b52b74e95..bd4f9a584378 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -6,7 +6,6 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import Text from '@components/Text'; import withLocalize from '@components/withLocalize'; import withThemeStyles from '@components/withThemeStyles'; import withWindowDimensions from '@components/withWindowDimensions'; @@ -94,7 +93,7 @@ class PDFView extends Component { maxCanvasHeight={this.props.maxCanvasHeight} maxCanvasArea={this.props.maxCanvasArea} LoadingComponent={} - ErrorComponent={{this.props.translate('attachmentView.failedToLoadPDF')}} + ErrorComponent={this.props.renderFallbackAttachmentView} renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( Date: Fri, 29 Mar 2024 04:38:22 +0530 Subject: [PATCH 0044/1525] fix prop pass --- .../Attachments/AttachmentView/AttachmentViewPdf/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 10bcf28e7797..3afb02db5068 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -24,6 +24,7 @@ function AttachmentViewPdf({ onToggleKeyboard={onToggleKeyboard} onLoadComplete={onLoadComplete} isUsedAsChatAttachment={isUsedAsChatAttachment} + renderFallbackAttachmentView={renderFallbackAttachmentView} /> ); } From 7d5d22a60c27c70622f097ae0ebd92c0e8dcf03e Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 29 Mar 2024 05:01:20 +0530 Subject: [PATCH 0045/1525] prettier diffs --- .../Attachments/AttachmentView/index.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 1621d8d9509d..d08f10582c52 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -1,15 +1,15 @@ import Str from 'expensify-common/lib/str'; -import React, { memo, useEffect, useState } from 'react'; -import type { GestureResponderEvent, StyleProp, ViewStyle } from 'react-native'; -import { View } from 'react-native'; -import type { OnyxEntry } from 'react-native-onyx'; -import { withOnyx } from 'react-native-onyx'; -import type { Attachment, AttachmentSource } from '@components/Attachments/types'; +import React, {memo, useEffect, useState} from 'react'; +import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {Attachment, AttachmentSource} from '@components/Attachments/types'; import DistanceEReceipt from '@components/DistanceEReceipt'; import EReceipt from '@components/EReceipt'; import Icon from '@components/Icon'; import ScrollView from '@components/ScrollView'; -import { usePlaybackContext } from '@components/VideoPlayerContexts/PlaybackContext'; +import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -18,10 +18,10 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as CachedPDFPaths from '@libs/actions/CachedPDFPaths'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as TransactionUtils from '@libs/TransactionUtils'; -import type { ColorValue } from '@styles/utils/types'; +import type {ColorValue} from '@styles/utils/types'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; -import type { Transaction } from '@src/types/onyx'; +import type {Transaction} from '@src/types/onyx'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; import AttachmentViewVideo from './AttachmentViewVideo'; @@ -93,8 +93,8 @@ function AttachmentView({ duration, isUsedAsChatAttachment, }: AttachmentViewProps) { - const { translate } = useLocalize(); - const { updateCurrentlyPlayingURL } = usePlaybackContext(); + const {translate} = useLocalize(); + const {updateCurrentlyPlayingURL} = usePlaybackContext(); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -111,7 +111,7 @@ function AttachmentView({ const [imageError, setImageError] = useState(false); - useNetwork({ onReconnect: () => setImageError(false) }); + useNetwork({onReconnect: () => setImageError(false)}); // Handles case where source is a component (ex: SVG) or a number // Number may represent a SVG or an image @@ -251,9 +251,9 @@ AttachmentView.displayName = 'AttachmentView'; export default memo( withOnyx({ transaction: { - key: ({ transactionID }) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, }, })(AttachmentView), ); -export type { AttachmentViewProps }; +export type {AttachmentViewProps}; From 91daa59c3cdc13d35038ae40db35a013e12f9438 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 29 Mar 2024 22:50:15 +0300 Subject: [PATCH 0046/1525] implemented offline indicator for video --- src/components/VideoPlayer/BaseVideoPlayer.js | 20 ++++++++++++++++--- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index 91737ad3938a..9b247905db37 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -3,13 +3,17 @@ import {Video, VideoFullscreenUpdate} from 'expo-av'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; +import BlockingView from '@components/BlockingViews/BlockingView'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Hoverable from '@components/Hoverable'; +import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; +import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -73,6 +77,8 @@ function BaseVideoPlayer({ const isCurrentlyURLSet = currentlyPlayingURL === url; const isUploading = _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); + const {translate} = useLocalize(); + const theme = useTheme(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; @@ -275,9 +281,17 @@ function BaseVideoPlayer({ )} - - {(isLoading || isBuffering) && } - + {((isLoading && !isOffline) || isBuffering) && } + {isLoading && isOffline && ( + + + + )} {shouldShowVideoControls && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( `You've selected the maximum number (${count}) of participants.`, youAppearToBeOffline: 'You appear to be offline.', thisFeatureRequiresInternet: 'This feature requires an active internet connection to be used.', + attachementWillBeAvailableOnceBackOnline: 'Attachment will become available once back online.', areYouSure: 'Are you sure?', verify: 'Verify', yesContinue: 'Yes, continue', diff --git a/src/languages/es.ts b/src/languages/es.ts index da4a17e76fdc..fb417cf5364f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -257,6 +257,7 @@ export default { maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', + attachementWillBeAvailableOnceBackOnline: 'El archivo adjunto estará disponible cuando vuelvas a estar en línea.', areYouSure: '¿Estás seguro?', verify: 'Verifique', yesContinue: 'Sí, continuar', From 0af56c671213f1b2ade9f045259edcf27db8a7ef Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Mar 2024 01:50:13 +0530 Subject: [PATCH 0047/1525] remove renderfallbackview prop --- .../AttachmentViewPdf/BaseAttachmentViewPdf.tsx | 4 ++-- .../AttachmentView/AttachmentViewPdf/index.tsx | 14 ++------------ .../AttachmentView/AttachmentViewPdf/types.ts | 9 +++------ .../AttachmentView/DefaultAttachmentView/index.tsx | 4 ++-- .../Attachments/AttachmentView/index.tsx | 6 +++--- src/components/PDFView/index.js | 9 ++++++++- src/components/PDFView/index.native.js | 4 ++-- src/components/PDFView/pdfViewPropTypes.js | 5 ++++- 8 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx index 9971c2d03820..eb22535d1d5f 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx @@ -15,7 +15,7 @@ function BaseAttachmentViewPdf({ onLoadComplete, style, isUsedAsChatAttachment, - onError, + onLoadError, }: AttachmentViewPdfProps) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled; @@ -78,7 +78,7 @@ function BaseAttachmentViewPdf({ onScaleChanged={onScaleChanged} onLoadComplete={onLoadComplete} isUsedAsChatAttachment={isUsedAsChatAttachment} - onError={onError} + onLoadError={onLoadError} /> ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 3afb02db5068..c1ded9782a7a 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,17 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import type AttachmentViewPdfProps from './types'; -function AttachmentViewPdf({ - file, - encryptedSourceUrl, - isFocused, - onPress, - onToggleKeyboard, - onLoadComplete, - style, - renderFallbackAttachmentView, - isUsedAsChatAttachment, -}: AttachmentViewPdfProps) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, containerStyles}: AttachmentViewPdfProps) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index ecc6e5016446..1c3cd8adfba4 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -8,20 +8,17 @@ type AttachmentViewPdfProps = Pick; - /** Styles for the error label */ - errorLabelStyles?: StyleProp; - /** Triggered when the PDF's onScaleChanged event is triggered */ onScaleChanged?: (scale: number) => void; /** Triggered when the PDF fails to load */ - onError?: () => void; + onLoadError?: () => void; /** Whether the PDF is used as a chat attachment */ isUsedAsChatAttachment?: boolean; - /** Render a fallback view when the PDF fails to load */ - renderFallbackAttachmentView?: () => JSX.Element; + /** Additional container styles */ + containerStyles?: StyleProp; }; export default AttachmentViewPdfProps; diff --git a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx index 7360144bf09c..23d39ec096b5 100644 --- a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx @@ -10,13 +10,13 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; type DefaultAttachmentViewProps = { - fileName: string; + fileName?: string; shouldShowDownloadIcon?: boolean; shouldShowLoadingSpinnerIcon?: boolean; containerStyles?: StyleProp; }; -function DefaultAttachmentView({fileName, shouldShowDownloadIcon, shouldShowLoadingSpinnerIcon, containerStyles}: DefaultAttachmentViewProps) { +function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = false, shouldShowDownloadIcon, containerStyles}: DefaultAttachmentViewProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index d08f10582c52..5e9a1b2bf4f6 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -176,9 +176,9 @@ function AttachmentView({ onToggleKeyboard={onToggleKeyboard} onLoadComplete={onPDFLoadComplete} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} - isUsedInCarousel={isUsedInCarousel} isUsedAsChatAttachment={isUsedAsChatAttachment} - onError={() => { + containerStyles={containerStyles} + onLoadError={() => { setIsPdfFailedToLoad(true); }} /> @@ -238,7 +238,7 @@ function AttachmentView({ return ( } - ErrorComponent={this.props.renderFallbackAttachmentView} + ErrorComponent={ + + } renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( Date: Sat, 30 Mar 2024 01:57:32 +0530 Subject: [PATCH 0048/1525] fix the comment for the prop --- src/components/Attachments/AttachmentView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 5e9a1b2bf4f6..f32265bdc01f 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -68,7 +68,7 @@ type AttachmentViewProps = AttachmentViewOnyxProps & /* Whether it is hovered or not */ isHovered?: boolean; - /** Duration of the video */ + /** Whether the attachment is used as a chat attachment */ isUsedAsChatAttachment?: boolean; }; From 6436ddbef5f2928851b20ba4448489fbb2c74b8b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Mar 2024 02:32:19 +0530 Subject: [PATCH 0049/1525] fixes loading for pdf on web --- src/components/FullscreenLoadingIndicator.tsx | 5 +++-- src/components/PDFView/index.js | 9 ++++++++- src/components/PDFView/pdfViewPropTypes.js | 3 +++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx index bd3082db5fa4..57c2d7dc955e 100644 --- a/src/components/FullscreenLoadingIndicator.tsx +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -6,13 +6,14 @@ import useThemeStyles from '@hooks/useThemeStyles'; type FullScreenLoadingIndicatorProps = { style?: StyleProp; + isFullScreen?: boolean; }; -function FullScreenLoadingIndicator({style}: FullScreenLoadingIndicatorProps) { +function FullScreenLoadingIndicator({style, isFullScreen = true}: FullScreenLoadingIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); return ( - + } + LoadingComponent={} ErrorComponent={ Date: Sat, 30 Mar 2024 02:40:55 +0530 Subject: [PATCH 0050/1525] fixes proptype warning --- src/components/PDFView/index.js | 30 +++++++++++++++++----- src/components/PDFView/pdfViewPropTypes.js | 3 --- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 64904fc84b3e..d2ee3d448718 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -8,8 +8,8 @@ import DefaultAttachmentView from '@components/Attachments/AttachmentView/Defaul import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import withLocalize from '@components/withLocalize'; +import withStyleUtils, {withStyleUtilsPropTypes} from '@components/withStyleUtils'; import withThemeStyles from '@components/withThemeStyles'; -import withStyleUtils from '@components/withStyleUtils'; import withWindowDimensions from '@components/withWindowDimensions'; import compose from '@libs/compose'; import variables from '@styles/variables'; @@ -17,10 +17,16 @@ import * as CanvasSize from '@userActions/CanvasSize'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import PDFPasswordForm from './PDFPasswordForm'; -import * as pdfViewPropTypes from './pdfViewPropTypes'; +import {propTypes as pdfViewPropTypes, defaultProps as pdfViewDefaultProps} from './pdfViewPropTypes'; const LOADING_THUMBNAIL_HEIGHT = 250; const LOADING_THUMBNAIL_WIDTH = 250; + +const PDFViewPropTypes = { + ...pdfViewPropTypes, + ...withStyleUtilsPropTypes, +}; + class PDFView extends Component { constructor(props) { super(props); @@ -83,8 +89,13 @@ class PDFView extends Component { const styles = this.props.themeStyles; const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; const loadingIndicatorStyles = this.props.isUsedAsChatAttachment - ? [this.props.themeStyles.chatItemPDFAttachmentLoading, this.props.StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT), styles.justifyContentCenter, styles.alignItemsCenter] - : []; + ? [ + this.props.themeStyles.chatItemPDFAttachmentLoading, + this.props.StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT), + styles.justifyContentCenter, + styles.alignItemsCenter, + ] + : []; return ( } + LoadingComponent={ + + } ErrorComponent={ Date: Sat, 30 Mar 2024 02:47:58 +0530 Subject: [PATCH 0051/1525] prettier diffs --- src/components/PDFView/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index d2ee3d448718..5051e75266cb 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -17,7 +17,7 @@ import * as CanvasSize from '@userActions/CanvasSize'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import PDFPasswordForm from './PDFPasswordForm'; -import {propTypes as pdfViewPropTypes, defaultProps as pdfViewDefaultProps} from './pdfViewPropTypes'; +import {defaultProps as pdfViewDefaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; const LOADING_THUMBNAIL_HEIGHT = 250; const LOADING_THUMBNAIL_WIDTH = 250; From 6262dcdafe8099d6d0cae9981ff1e78b29b127c4 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 31 Mar 2024 12:00:07 +0530 Subject: [PATCH 0052/1525] revert all changes. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.ts | 214 ++++++++++++------------- tests/unit/OptionsListUtilsTest.js | 249 ++++++++++++++++++++++++++--- 2 files changed, 329 insertions(+), 134 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 042be402678a..7e4082bff481 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -7,6 +7,7 @@ import lodashSet from 'lodash/set'; import lodashSortBy from 'lodash/sortBy'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {SelectedTagOption} from '@components/TagPicker'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -19,6 +20,7 @@ import type { PolicyCategories, PolicyTag, PolicyTagList, + PolicyTags, Report, ReportAction, ReportActions, @@ -31,6 +33,7 @@ import type { import type {Participant} from '@src/types/onyx/IOU'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import times from '@src/utils/times'; import Timing from './actions/Timing'; @@ -53,12 +56,6 @@ import * as TaskUtils from './TaskUtils'; import * as TransactionUtils from './TransactionUtils'; import * as UserUtils from './UserUtils'; -type Tag = { - enabled: boolean; - name: string; - accountID: number | null; -}; - type Option = Partial; /** @@ -86,7 +83,6 @@ type PayeePersonalDetails = { type CategorySectionBase = { title: string | undefined; shouldShow: boolean; - indexOffset: number; }; type CategorySection = CategorySectionBase & { @@ -130,7 +126,7 @@ type GetOptionsConfig = { categories?: PolicyCategories; recentlyUsedCategories?: string[]; includeTags?: boolean; - tags?: Record; + tags?: PolicyTags | Array; recentlyUsedTags?: string[]; canInviteUser?: boolean; includeSelectedOptions?: boolean; @@ -154,7 +150,6 @@ type MemberForList = { type SectionForSearchTerm = { section: CategorySection; - newIndexOffset: number; }; type GetOptions = { recentReports: ReportUtils.OptionData[]; @@ -247,17 +242,6 @@ Onyx.connect({ }, }); -const policyExpenseReports: OnyxCollection = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - callback: (report, key) => { - if (!ReportUtils.isPolicyExpenseChat(report)) { - return; - } - policyExpenseReports[key] = report; - }, -}); - let allTransactions: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, @@ -480,7 +464,7 @@ function getSearchText( /** * Get an object of error messages keyed by microtime by combining all error objects related to the report. */ -function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry, transactions: OnyxCollection = allTransactions): OnyxCommon.Errors { +function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry): OnyxCommon.Errors { const reportErrors = report?.errors ?? {}; const reportErrorFields = report?.errorFields ?? {}; const reportActionErrors: OnyxCommon.ErrorFields = Object.values(reportActions ?? {}).reduce( @@ -492,7 +476,7 @@ function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry< if (parentReportAction?.actorAccountID === currentUserAccountID && ReportActionUtils.isTransactionThread(parentReportAction)) { const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage?.IOUTransactionID : null; - const transaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; if (TransactionUtils.hasMissingSmartscanFields(transaction ?? null) && !ReportUtils.isSettled(transaction?.reportID)) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); } @@ -520,7 +504,8 @@ function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry< */ function getLastActorDisplayName(lastActorDetails: Partial | null, hasMultipleParticipants: boolean) { return hasMultipleParticipants && lastActorDetails && lastActorDetails.accountID !== currentUserAccountID - ? lastActorDetails.firstName ?? PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails) + ? // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + lastActorDetails.firstName || PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails) : ''; } @@ -568,6 +553,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails ReportUtils.isChatReport(report), null, true, + lastReportAction, ); } else if (ReportActionUtils.isReimbursementQueuedAction(lastReportAction)) { lastMessageTextFromReport = ReportUtils.getReimbursementQueuedActionMessage(lastReportAction, report); @@ -732,16 +718,45 @@ function createOption( return result; } +/** + * Get the option for a given report. + */ +function getReportOption(participant: Participant): ReportUtils.OptionData { + const report = ReportUtils.getReport(participant.reportID); + + const option = createOption( + report?.visibleChatMemberAccountIDs ?? [], + allPersonalDetails ?? {}, + !isEmptyObject(report) ? report : null, + {}, + { + showChatPreviewLine: false, + forcePolicyNamePreview: false, + }, + ); + + // Update text & alternateText because createOption returns workspace name only if report is owned by the user + if (option.isSelfDM) { + option.alternateText = Localize.translateLocal('reportActionsView.yourSpace'); + } else { + option.text = ReportUtils.getPolicyName(report); + option.alternateText = Localize.translateLocal('workspace.common.workspace'); + } + option.selected = participant.selected; + option.isSelected = participant.selected; + return option; +} + /** * Get the option for a policy expense report. */ -function getPolicyExpenseReportOption(report: Report): ReportUtils.OptionData { - const expenseReport = policyExpenseReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]; +function getPolicyExpenseReportOption(participant: Participant | ReportUtils.OptionData): ReportUtils.OptionData { + const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? ReportUtils.getReport(participant.reportID) : null; const option = createOption( expenseReport?.visibleChatMemberAccountIDs ?? [], allPersonalDetails ?? {}, - expenseReport ?? null, + !isEmptyObject(expenseReport) ? expenseReport : null, {}, { showChatPreviewLine: false, @@ -752,8 +767,8 @@ function getPolicyExpenseReportOption(report: Report): ReportUtils.OptionData { // Update text & alternateText because createOption returns workspace name only if report is owned by the user option.text = ReportUtils.getPolicyName(expenseReport); option.alternateText = Localize.translateLocal('workspace.common.workspace'); - option.selected = report.selected; - option.isSelected = report.selected; + option.selected = participant.selected; + option.isSelected = participant.selected; return option; } @@ -861,7 +876,7 @@ function sortCategories(categories: Record): Category[] { if (name) { const categoryObject: Category = { name, - enabled: categories[name].enabled ?? false, + enabled: categories[name]?.enabled ?? false, }; acc.push(categoryObject); @@ -882,16 +897,11 @@ function sortCategories(categories: Record): Category[] { /** * Sorts tags alphabetically by name. */ -function sortTags(tags: Record | Tag[]) { - let sortedTags; - - if (Array.isArray(tags)) { - sortedTags = tags.sort((a, b) => localeCompare(a.name, b.name)); - } else { - sortedTags = Object.values(tags).sort((a, b) => localeCompare(a.name, b.name)); - } +function sortTags(tags: Record | Array) { + const sortedTags = Array.isArray(tags) ? tags : Object.values(tags); - return sortedTags; + // Use lodash's sortBy to ensure consistency with oldDot. + return lodashSortBy(sortedTags, 'name', localeCompare); } /** @@ -902,7 +912,7 @@ function sortTags(tags: Record | Tag[]) { * @param options[].name - a name of an option * @param [isOneLine] - a flag to determine if text should be one line */ -function getCategoryOptionTree(options: Record | Category[], isOneLine = false): OptionTree[] { +function getCategoryOptionTree(options: Record | Category[], isOneLine = false, selectedOptionsName: string[] = []): OptionTree[] { const optionCollection = new Map(); Object.values(options).forEach((option) => { if (isOneLine) { @@ -937,7 +947,7 @@ function getCategoryOptionTree(options: Record | Category[], i searchText, tooltipText: optionName, isDisabled: isChild ? !option.enabled : true, - isSelected: !!option.isSelected, + isSelected: isChild ? !!option.isSelected : selectedOptionsName.includes(searchText), }); }); }); @@ -956,64 +966,66 @@ function getCategoryListSections( maxRecentReportsToShow: number, ): CategoryTreeSection[] { const sortedCategories = sortCategories(categories); - const enabledCategoriesLength = Object.values(sortedCategories).filter((category) => category.enabled).length; - - const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); - const enabledAndSelectedCategories = [...selectedOptions, ...Object.values(sortedCategories).filter((category) => category.enabled && !selectedOptionNames.includes(category.name))]; - const enabledAndSelectedCategoriesLength = enabledAndSelectedCategories.length; + const enabledCategories = Object.values(sortedCategories).filter((category) => category.enabled); const categorySections: CategoryTreeSection[] = []; + const numberOfEnabledCategories = enabledCategories.length; - let indexOffset = 0; - - if (enabledCategoriesLength === 0 && selectedOptions.length > 0) { + if (numberOfEnabledCategories === 0 && selectedOptions.length > 0) { categorySections.push({ // "Selected" section title: '', shouldShow: false, - indexOffset, - data: getCategoryOptionTree(enabledAndSelectedCategories, true), + data: getCategoryOptionTree(selectedOptions, true), }); return categorySections; } if (searchInputValue) { - const searchCategories = enabledAndSelectedCategories.filter((category) => category.name.toLowerCase().includes(searchInputValue.toLowerCase())); + const searchCategories: Category[] = []; + + enabledCategories.forEach((category) => { + if (!category.name.toLowerCase().includes(searchInputValue.toLowerCase())) { + return; + } + searchCategories.push({ + ...category, + isSelected: selectedOptions.some((selectedOption) => selectedOption.name === category.name), + }); + }); categorySections.push({ // "Search" section title: '', shouldShow: true, - indexOffset, data: getCategoryOptionTree(searchCategories, true), }); return categorySections; } - if (enabledAndSelectedCategoriesLength < CONST.CATEGORY_LIST_THRESHOLD) { + if (selectedOptions.length > 0) { categorySections.push({ - // "All" section when items amount less than the threshold + // "Selected" section title: '', shouldShow: false, - indexOffset, - data: getCategoryOptionTree(enabledAndSelectedCategories), + data: getCategoryOptionTree(selectedOptions, true), }); - - return categorySections; } - if (selectedOptions.length > 0) { + const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); + const filteredCategories = enabledCategories.filter((category) => !selectedOptionNames.includes(category.name)); + + if (numberOfEnabledCategories < CONST.CATEGORY_LIST_THRESHOLD) { categorySections.push({ - // "Selected" section + // "All" section when items amount less than the threshold title: '', shouldShow: false, - indexOffset, - data: getCategoryOptionTree(selectedOptions, true), + data: getCategoryOptionTree(filteredCategories, false, selectedOptionNames), }); - indexOffset += selectedOptions.length; + return categorySections; } const filteredRecentlyUsedCategories = recentlyUsedCategories @@ -1030,21 +1042,15 @@ function getCategoryListSections( // "Recent" section title: Localize.translateLocal('common.recent'), shouldShow: true, - indexOffset, data: getCategoryOptionTree(cutRecentlyUsedCategories, true), }); - - indexOffset += filteredRecentlyUsedCategories.length; } - const filteredCategories = enabledAndSelectedCategories.filter((category) => !selectedOptionNames.includes(category.name)); - categorySections.push({ // "All" section when items amount more than the threshold title: Localize.translateLocal('common.all'), shouldShow: true, - indexOffset, - data: getCategoryOptionTree(filteredCategories), + data: getCategoryOptionTree(filteredCategories, false, selectedOptionNames), }); return categorySections; @@ -1055,7 +1061,7 @@ function getCategoryListSections( * * @param tags - an initial tag array */ -function getTagsOptions(tags: Category[]): Option[] { +function getTagsOptions(tags: Array>): Option[] { return tags.map((tag) => { // This is to remove unnecessary escaping backslash in tag name sent from backend. const cleanedName = PolicyUtils.getCleanedTagName(tag.name); @@ -1072,13 +1078,18 @@ function getTagsOptions(tags: Category[]): Option[] { /** * Build the section list for tags */ -function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOptions: Category[], searchInputValue: string, maxRecentReportsToShow: number) { +function getTagListSections( + tags: Array, + recentlyUsedTags: string[], + selectedOptions: SelectedTagOption[], + searchInputValue: string, + maxRecentReportsToShow: number, +) { const tagSections = []; - const sortedTags = sortTags(tags); + const sortedTags = sortTags(tags) as PolicyTag[]; const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); const enabledTags = [...selectedOptions, ...sortedTags.filter((tag) => tag.enabled && !selectedOptionNames.includes(tag.name))]; const numberOfTags = enabledTags.length; - let indexOffset = 0; // If all tags are disabled but there's a previously selected tag, show only the selected tag if (numberOfTags === 0 && selectedOptions.length > 0) { @@ -1091,7 +1102,6 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "Selected" section title: '', shouldShow: false, - indexOffset, data: getTagsOptions(selectedTagOptions), }); @@ -1105,7 +1115,6 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "Search" section title: '', shouldShow: true, - indexOffset, data: getTagsOptions(searchTags), }); @@ -1117,7 +1126,6 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "All" section when items amount less than the threshold title: '', shouldShow: false, - indexOffset, data: getTagsOptions(enabledTags), }); @@ -1143,11 +1151,8 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "Selected" section title: '', shouldShow: true, - indexOffset, data: getTagsOptions(selectedTagOptions), }); - - indexOffset += selectedOptions.length; } if (filteredRecentlyUsedTags.length > 0) { @@ -1157,18 +1162,14 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt // "Recent" section title: Localize.translateLocal('common.recent'), shouldShow: true, - indexOffset, data: getTagsOptions(cutRecentlyUsedTags), }); - - indexOffset += filteredRecentlyUsedTags.length; } tagSections.push({ // "All" section when items amount more than the threshold title: Localize.translateLocal('common.all'), shouldShow: true, - indexOffset, data: getTagsOptions(filteredTags), }); @@ -1190,8 +1191,8 @@ function hasEnabledTags(policyTagList: Array * @param taxRates - The original tax rates object. * @returns The transformed tax rates object.g */ -function transformedTaxRates(taxRates: TaxRatesWithDefault | undefined): Record { - const defaultTaxKey = taxRates?.defaultExternalID; +function transformedTaxRates(taxRates: TaxRatesWithDefault | undefined, defaultKey?: string): Record { + const defaultTaxKey = defaultKey ?? taxRates?.defaultExternalID; const getModifiedName = (data: TaxRate, code: string) => `${data.name} (${data.value})${defaultTaxKey === code ? ` • ${Localize.translateLocal('common.default')}` : ''}`; const taxes = Object.fromEntries(Object.entries(taxRates?.taxes ?? {}).map(([code, data]) => [code, {...data, code, modifiedName: getModifiedName(data, code), name: data.name}])); return taxes; @@ -1222,17 +1223,15 @@ function getTaxRatesOptions(taxRates: Array>): Option[] { /** * Builds the section list for tax rates */ -function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedOptions: Category[], searchInputValue: string): CategorySection[] { +function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedOptions: Category[], searchInputValue: string, defaultTaxKey?: string): CategorySection[] { const policyRatesSections = []; - const taxes = transformedTaxRates(taxRates); + const taxes = transformedTaxRates(taxRates, defaultTaxKey); const sortedTaxRates = sortTaxRates(taxes); const enabledTaxRates = sortedTaxRates.filter((taxRate) => !taxRate.isDisabled); const numberOfTaxRates = enabledTaxRates.length; - let indexOffset = 0; - // If all tax are disabled but there's a previously selected tag, show only the selected tag if (numberOfTaxRates === 0 && selectedOptions.length > 0) { const selectedTaxRateOptions = selectedOptions.map((option) => ({ @@ -1244,7 +1243,6 @@ function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedO // "Selected" sectiong title: '', shouldShow: false, - indexOffset, data: getTaxRatesOptions(selectedTaxRateOptions), }); @@ -1252,13 +1250,12 @@ function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedO } if (searchInputValue) { - const searchTaxRates = enabledTaxRates.filter((taxRate) => taxRate.modifiedName.toLowerCase().includes(searchInputValue.toLowerCase())); + const searchTaxRates = enabledTaxRates.filter((taxRate) => taxRate.modifiedName?.toLowerCase().includes(searchInputValue.toLowerCase())); policyRatesSections.push({ // "Search" section title: '', shouldShow: true, - indexOffset, data: getTaxRatesOptions(searchTaxRates), }); @@ -1270,7 +1267,6 @@ function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedO // "All" section when items amount less than the threshold title: '', shouldShow: false, - indexOffset, data: getTaxRatesOptions(enabledTaxRates), }); @@ -1278,7 +1274,7 @@ function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedO } const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); - const filteredTaxRates = enabledTaxRates.filter((taxRate) => !selectedOptionNames.includes(taxRate.modifiedName)); + const filteredTaxRates = enabledTaxRates.filter((taxRate) => taxRate.modifiedName && !selectedOptionNames.includes(taxRate.modifiedName)); if (selectedOptions.length > 0) { const selectedTaxRatesOptions = selectedOptions.map((option) => { @@ -1294,18 +1290,14 @@ function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedO // "Selected" section title: '', shouldShow: true, - indexOffset, data: getTaxRatesOptions(selectedTaxRatesOptions), }); - - indexOffset += selectedOptions.length; } policyRatesSections.push({ // "All" section when number of items are more than the threshold title: '', shouldShow: true, - indexOffset, data: getTaxRatesOptions(filteredTaxRates), }); @@ -1384,7 +1376,7 @@ function getOptions( } if (includeTags) { - const tagOptions = getTagListSections(Object.values(tags), recentlyUsedTags, selectedOptions as Category[], searchInputValue, maxRecentReportsToShow); + const tagOptions = getTagListSections(Object.values(tags), recentlyUsedTags, selectedOptions as SelectedTagOption[], searchInputValue, maxRecentReportsToShow); return { recentReports: [], @@ -1722,7 +1714,7 @@ function getOptions( /** * Build the options for the Search view */ -function getSearchOptions(reports: Record, personalDetails: OnyxEntry, searchValue = '', betas: Beta[] = []): GetOptions { +function getSearchOptions(reports: OnyxCollection, personalDetails: OnyxEntry, searchValue = '', betas: Beta[] = []): GetOptions { Timing.start(CONST.TIMING.LOAD_SEARCH_OPTIONS); Performance.markStart(CONST.TIMING.LOAD_SEARCH_OPTIONS); const options = getOptions(reports, personalDetails, { @@ -1757,13 +1749,15 @@ function getShareLogOptions(reports: OnyxCollection, personalDetails: On includePersonalDetails: true, forcePolicyNamePreview: true, includeOwnedWorkspaceChats: true, + includeSelfDM: true, + includeThreads: true, }); } /** * Build the IOUConfirmation options for showing the payee personalDetail */ -function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails, amountText: string): PayeePersonalDetails { +function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | EmptyObject, amountText?: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), @@ -1776,7 +1770,7 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: Person id: personalDetail.accountID, }, ], - descriptiveText: amountText, + descriptiveText: amountText ?? '', login: personalDetail.login ?? '', accountID: personalDetail.accountID, keyForList: String(personalDetail.accountID), @@ -1786,7 +1780,7 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: Person /** * Build the IOUConfirmationOptions for showing participants */ -function getIOUConfirmationOptionsFromParticipants(participants: Participant[], amountText: string): Participant[] { +function getIOUConfirmationOptionsFromParticipants(participants: Array, amountText: string): Array { return participants.map((participant) => ({ ...participant, descriptiveText: amountText, @@ -1809,7 +1803,7 @@ function getFilteredOptions( categories: PolicyCategories = {}, recentlyUsedCategories: string[] = [], includeTags = false, - tags: Record = {}, + tags: PolicyTags | Array = {}, recentlyUsedTags: string[] = [], canInviteUser = true, includeSelectedOptions = false, @@ -1981,7 +1975,6 @@ function formatSectionsFromSearchTerm( filteredRecentReports: ReportUtils.OptionData[], filteredPersonalDetails: ReportUtils.OptionData[], maxOptionsSelected: boolean, - indexOffset = 0, personalDetails: OnyxEntry = {}, shouldGetOptionDetails = false, ): SectionForSearchTerm { @@ -1999,9 +1992,7 @@ function formatSectionsFromSearchTerm( }) : selectedOptions, shouldShow: selectedOptions.length > 0, - indexOffset, }, - newIndexOffset: indexOffset + selectedOptions.length, }; } @@ -2025,9 +2016,7 @@ function formatSectionsFromSearchTerm( }) : selectedParticipantsWithoutDetails, shouldShow: selectedParticipantsWithoutDetails.length > 0, - indexOffset, }, - newIndexOffset: indexOffset + selectedParticipantsWithoutDetails.length, }; } @@ -2056,12 +2045,15 @@ export { getEnabledCategoriesCount, hasEnabledOptions, sortCategories, + sortTags, getCategoryOptionTree, hasEnabledTags, formatMemberForList, formatSectionsFromSearchTerm, transformedTaxRates, getShareLogOptions, + getReportOption, + getTaxRatesSection, }; -export type {MemberForList, CategorySection, GetOptions}; +export type {MemberForList, CategorySection, GetOptions, PayeePersonalDetails, Category}; diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index c3c84cdc2c83..d89c81f58262 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -689,7 +689,6 @@ describe('OptionsListUtils', () => { { name: 'Medical', enabled: true, - isSelected: true, }, ]; const smallCategoriesList = { @@ -714,7 +713,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, data: [ { text: 'Food', @@ -747,7 +745,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Food', @@ -772,7 +769,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; @@ -838,7 +834,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, data: [ { text: 'Medical', @@ -846,14 +841,13 @@ describe('OptionsListUtils', () => { searchText: 'Medical', tooltipText: 'Medical', isDisabled: false, - isSelected: true, + isSelected: false, }, ], }, { title: 'Recent', shouldShow: true, - indexOffset: 1, data: [ { text: 'Restaurant', @@ -868,7 +862,6 @@ describe('OptionsListUtils', () => { { title: 'All', shouldShow: true, - indexOffset: 2, data: [ { text: 'Cars', @@ -965,7 +958,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Food', @@ -998,7 +990,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; @@ -1007,7 +998,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, data: [ { text: 'Medical', @@ -1015,7 +1005,7 @@ describe('OptionsListUtils', () => { searchText: 'Medical', tooltipText: 'Medical', isDisabled: false, - isSelected: true, + isSelected: false, }, ], }, @@ -1112,7 +1102,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, // data sorted alphabetically by name data: [ { @@ -1143,7 +1132,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Accounting', @@ -1159,7 +1147,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; @@ -1213,7 +1200,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Medical', @@ -1227,7 +1213,6 @@ describe('OptionsListUtils', () => { { title: 'Recent', shouldShow: true, - indexOffset: 1, data: [ { text: 'HR', @@ -1241,7 +1226,6 @@ describe('OptionsListUtils', () => { { title: 'All', shouldShow: true, - indexOffset: 2, // data sorted alphabetically by name data: [ { @@ -1300,7 +1284,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [ { text: 'Accounting', @@ -1323,7 +1306,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; @@ -2059,6 +2041,230 @@ describe('OptionsListUtils', () => { expect(OptionsListUtils.sortCategories(categoriesIncorrectOrdering3)).toStrictEqual(result3); }); + it('sortTags', () => { + const createTagObjects = (names) => _.map(names, (name) => ({name, enabled: true})); + + const unorderedTagNames = ['10bc', 'b', '0a', '1', '中国', 'b10', '!', '2', '0', '@', 'a1', 'a', '3', 'b1', '日本', '$', '20', '20a', '#', 'a20', 'c', '10']; + const expectedOrderNames = ['!', '#', '$', '0', '0a', '1', '10', '10bc', '2', '20', '20a', '3', '@', 'a', 'a1', 'a20', 'b', 'b1', 'b10', 'c', '中国', '日本']; + const unorderedTags = createTagObjects(unorderedTagNames); + const expectedOrder = createTagObjects(expectedOrderNames); + expect(OptionsListUtils.sortTags(unorderedTags)).toStrictEqual(expectedOrder); + + const unorderedTagNames2 = ['0', 'a1', '1', 'b1', '3', '10', 'b10', 'a', '2', 'c', '20', 'a20', 'b']; + const expectedOrderNames2 = ['0', '1', '10', '2', '20', '3', 'a', 'a1', 'a20', 'b', 'b1', 'b10', 'c']; + const unorderedTags2 = createTagObjects(unorderedTagNames2); + const expectedOrder2 = createTagObjects(expectedOrderNames2); + expect(OptionsListUtils.sortTags(unorderedTags2)).toStrictEqual(expectedOrder2); + + const unorderedTagNames3 = [ + '61', + '39', + '97', + '93', + '77', + '71', + '22', + '27', + '30', + '64', + '91', + '24', + '33', + '60', + '21', + '85', + '59', + '76', + '42', + '67', + '13', + '96', + '84', + '44', + '68', + '31', + '62', + '87', + '50', + '4', + '100', + '12', + '28', + '49', + '53', + '5', + '45', + '14', + '55', + '78', + '11', + '35', + '75', + '18', + '9', + '80', + '54', + '2', + '34', + '48', + '81', + '6', + '73', + '15', + '98', + '25', + '8', + '99', + '17', + '90', + '47', + '1', + '10', + '38', + '66', + '57', + '23', + '86', + '29', + '3', + '65', + '74', + '19', + '56', + '63', + '20', + '7', + '32', + '46', + '70', + '26', + '16', + '83', + '37', + '58', + '43', + '36', + '69', + '79', + '72', + '41', + '94', + '95', + '82', + '51', + '52', + '89', + '88', + '40', + '92', + ]; + const expectedOrderNames3 = [ + '1', + '10', + '100', + '11', + '12', + '13', + '14', + '15', + '16', + '17', + '18', + '19', + '2', + '20', + '21', + '22', + '23', + '24', + '25', + '26', + '27', + '28', + '29', + '3', + '30', + '31', + '32', + '33', + '34', + '35', + '36', + '37', + '38', + '39', + '4', + '40', + '41', + '42', + '43', + '44', + '45', + '46', + '47', + '48', + '49', + '5', + '50', + '51', + '52', + '53', + '54', + '55', + '56', + '57', + '58', + '59', + '6', + '60', + '61', + '62', + '63', + '64', + '65', + '66', + '67', + '68', + '69', + '7', + '70', + '71', + '72', + '73', + '74', + '75', + '76', + '77', + '78', + '79', + '8', + '80', + '81', + '82', + '83', + '84', + '85', + '86', + '87', + '88', + '89', + '9', + '90', + '91', + '92', + '93', + '94', + '95', + '96', + '97', + '98', + '99', + ]; + const unorderedTags3 = createTagObjects(unorderedTagNames3); + const expectedOrder3 = createTagObjects(expectedOrderNames3); + expect(OptionsListUtils.sortTags(unorderedTags3)).toStrictEqual(expectedOrder3); + }); + it('getFilteredOptions() for taxRate', () => { const search = 'rate'; const emptySearch = ''; @@ -2089,7 +2295,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: false, - indexOffset: 0, // data sorted alphabetically by name data: [ { @@ -2142,7 +2347,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, // data sorted alphabetically by name data: [ { @@ -2166,7 +2370,6 @@ describe('OptionsListUtils', () => { { title: '', shouldShow: true, - indexOffset: 0, data: [], }, ]; From db7f8b1bb2aee7ef410df10a16c0f441c3734a47 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 31 Mar 2024 12:53:25 +0530 Subject: [PATCH 0053/1525] show selected categories when searching. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 7e4082bff481..6c3d45b9b588 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -967,7 +967,7 @@ function getCategoryListSections( ): CategoryTreeSection[] { const sortedCategories = sortCategories(categories); const enabledCategories = Object.values(sortedCategories).filter((category) => category.enabled); - + const enabledAndSelectedCategories = [...selectedOptions, ...enabledCategories]; const categorySections: CategoryTreeSection[] = []; const numberOfEnabledCategories = enabledCategories.length; @@ -985,7 +985,7 @@ function getCategoryListSections( if (searchInputValue) { const searchCategories: Category[] = []; - enabledCategories.forEach((category) => { + enabledAndSelectedCategories.forEach((category) => { if (!category.name.toLowerCase().includes(searchInputValue.toLowerCase())) { return; } @@ -1088,11 +1088,12 @@ function getTagListSections( const tagSections = []; const sortedTags = sortTags(tags) as PolicyTag[]; const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); - const enabledTags = [...selectedOptions, ...sortedTags.filter((tag) => tag.enabled && !selectedOptionNames.includes(tag.name))]; - const numberOfTags = enabledTags.length; + const enabledTags = sortedTags.filter((tag) => tag.enabled && !selectedOptionNames.includes(tag.name)); + const enabledAndSelectedTags = [...selectedOptions, ...enabledTags]; + const numberEnabledOfTags = enabledTags.length; // If all tags are disabled but there's a previously selected tag, show only the selected tag - if (numberOfTags === 0 && selectedOptions.length > 0) { + if (numberEnabledOfTags === 0 && selectedOptions.length > 0) { const selectedTagOptions = selectedOptions.map((option) => ({ name: option.name, // Should be marked as enabled to be able to be de-selected @@ -1109,7 +1110,7 @@ function getTagListSections( } if (searchInputValue) { - const searchTags = enabledTags.filter((tag) => PolicyUtils.getCleanedTagName(tag.name.toLowerCase()).includes(searchInputValue.toLowerCase())); + const searchTags = enabledAndSelectedTags.filter((tag) => PolicyUtils.getCleanedTagName(tag.name.toLowerCase()).includes(searchInputValue.toLowerCase())); tagSections.push({ // "Search" section @@ -1121,7 +1122,7 @@ function getTagListSections( return tagSections; } - if (numberOfTags < CONST.TAG_LIST_THRESHOLD) { + if (numberEnabledOfTags < CONST.TAG_LIST_THRESHOLD) { tagSections.push({ // "All" section when items amount less than the threshold title: '', From 686c587c94116fae91aa5cad642d4a5a7acc4f29 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 31 Mar 2024 13:36:55 +0530 Subject: [PATCH 0054/1525] update TagPicker to use SelectionList. Signed-off-by: Krishna Gupta --- src/components/TagPicker/index.tsx | 45 ++++++++++++++++++++---------- src/libs/OptionsListUtils.ts | 37 ++++++++++++++---------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/src/components/TagPicker/index.tsx b/src/components/TagPicker/index.tsx index af8acd19e8c4..8eeb0edd22f3 100644 --- a/src/components/TagPicker/index.tsx +++ b/src/components/TagPicker/index.tsx @@ -3,6 +3,8 @@ import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {EdgeInsets} from 'react-native-safe-area-context'; import OptionsSelector from '@components/OptionsSelector'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -77,6 +79,7 @@ function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRe name: selectedTag, enabled: true, accountID: null, + isSelected: true, }, ]; }, [selectedTag]); @@ -100,25 +103,37 @@ function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRe const selectedOptionKey = sections[0]?.data?.filter((policyTag) => policyTag.searchText === selectedTag)?.[0]?.keyForList; return ( - + + ); } diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 6c3d45b9b588..593f5797415d 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -99,6 +99,12 @@ type Category = { isSelected?: boolean; }; +type Tag = { + name: string; + enabled: boolean; + isSelected?: boolean; +}; + type Hierarchy = Record; type GetOptionsConfig = { @@ -1061,7 +1067,7 @@ function getCategoryListSections( * * @param tags - an initial tag array */ -function getTagsOptions(tags: Array>): Option[] { +function getTagsOptions(tags: Array>): Option[] { return tags.map((tag) => { // This is to remove unnecessary escaping backslash in tag name sent from backend. const cleanedName = PolicyUtils.getCleanedTagName(tag.name); @@ -1071,6 +1077,7 @@ function getTagsOptions(tags: Array>): Optio searchText: tag.name, tooltipText: cleanedName, isDisabled: !tag.enabled, + isSelected: tag.isSelected, }; }); } @@ -1094,23 +1101,29 @@ function getTagListSections( // If all tags are disabled but there's a previously selected tag, show only the selected tag if (numberEnabledOfTags === 0 && selectedOptions.length > 0) { - const selectedTagOptions = selectedOptions.map((option) => ({ - name: option.name, - // Should be marked as enabled to be able to be de-selected - enabled: true, - })); tagSections.push({ // "Selected" section title: '', shouldShow: false, - data: getTagsOptions(selectedTagOptions), + data: getTagsOptions(selectedOptions), }); return tagSections; } if (searchInputValue) { - const searchTags = enabledAndSelectedTags.filter((tag) => PolicyUtils.getCleanedTagName(tag.name.toLowerCase()).includes(searchInputValue.toLowerCase())); + const searchTags: Tag[] = []; + + enabledAndSelectedTags.forEach((tag) => { + if (!PolicyUtils.getCleanedTagName(tag.name.toLowerCase()).includes(searchInputValue.toLowerCase())) { + return; + } + + searchTags.push({ + ...tag, + isSelected: selectedOptions.some((selectedOption) => selectedOption.name === tag.name), + }); + }); tagSections.push({ // "Search" section @@ -1142,17 +1155,11 @@ function getTagListSections( const filteredTags = enabledTags.filter((tag) => !selectedOptionNames.includes(tag.name)); if (selectedOptions.length) { - const selectedTagOptions = selectedOptions.map((option) => ({ - name: option.name, - // Should be marked as enabled to be able to unselect even though the selected category is disabled - enabled: true, - })); - tagSections.push({ // "Selected" section title: '', shouldShow: true, - data: getTagsOptions(selectedTagOptions), + data: getTagsOptions(selectedOptions), }); } From 75c53825d392a20e97e464233ab780ea0b6c23a1 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 31 Mar 2024 15:52:17 +0530 Subject: [PATCH 0055/1525] remove redundant code. Signed-off-by: Krishna Gupta --- src/components/TagPicker/index.tsx | 35 +----------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/src/components/TagPicker/index.tsx b/src/components/TagPicker/index.tsx index 8eeb0edd22f3..c071895cfdd9 100644 --- a/src/components/TagPicker/index.tsx +++ b/src/components/TagPicker/index.tsx @@ -1,13 +1,9 @@ import React, {useMemo, useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import type {EdgeInsets} from 'react-native-safe-area-context'; -import OptionsSelector from '@components/OptionsSelector'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useThemeStyles from '@hooks/useThemeStyles'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import CONST from '@src/CONST'; @@ -43,12 +39,6 @@ type TagPickerProps = TagPickerOnyxProps & { /** Callback to submit the selected tag */ onSubmit: () => void; - /** - * Safe area insets required for reflecting the portion of the view, - * that is not covered by navigation bars, tab bars, toolbars, and other ancestor views. - */ - insets: EdgeInsets; - /** Should show the selected option that is disabled? */ shouldShowDisabledAndSelectedOption?: boolean; @@ -56,9 +46,7 @@ type TagPickerProps = TagPickerOnyxProps & { tagListIndex: number; }; -function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption = false, insets, onSubmit}: TagPickerProps) { - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); +function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption = false, onSubmit}: TagPickerProps) { const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); @@ -103,27 +91,6 @@ function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRe const selectedOptionKey = sections[0]?.data?.filter((policyTag) => policyTag.searchText === selectedTag)?.[0]?.keyForList; return ( - // - Date: Sun, 31 Mar 2024 18:57:33 +0530 Subject: [PATCH 0056/1525] added disabled styles without disabling the select/unselect functionality. Signed-off-by: Krishna Gupta --- src/components/CategoryPicker.tsx | 1 - .../SelectionList/RadioListItem.tsx | 2 +- src/components/SelectionList/types.ts | 3 + src/libs/OptionsListUtils.ts | 56 +++++++++++++++---- src/libs/ReportUtils.ts | 1 + 5 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/components/CategoryPicker.tsx b/src/components/CategoryPicker.tsx index 3033bf118e8f..c3ac3d8d2f8f 100644 --- a/src/components/CategoryPicker.tsx +++ b/src/components/CategoryPicker.tsx @@ -71,7 +71,6 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC }, [policyRecentlyUsedCategories, debouncedSearchValue, selectedOptions, policyCategories]); const selectedOptionKey = useMemo(() => (sections?.[0]?.data ?? []).filter((category) => category.searchText === selectedCategory)[0]?.keyForList, [sections, selectedCategory]); - return ( ; @@ -933,6 +935,7 @@ function getCategoryOptionTree(options: Record | Category[], i tooltipText: option.name, isDisabled: !option.enabled, isSelected: !!option.isSelected, + applyDisabledStyle: option.applyDisabledStyle, }); return; @@ -972,8 +975,25 @@ function getCategoryListSections( maxRecentReportsToShow: number, ): CategoryTreeSection[] { const sortedCategories = sortCategories(categories); - const enabledCategories = Object.values(sortedCategories).filter((category) => category.enabled); - const enabledAndSelectedCategories = [...selectedOptions, ...enabledCategories]; + const selectedOptionsWithDisabledStyle: Category[] = []; + const enabledCategoriesName: string[] = []; + const selectedOptionNames: string[] = []; + + const enabledCategories = Object.values(sortedCategories).filter((category) => { + if (category.enabled) { + enabledCategoriesName.push(category.name); + } + return category.enabled; + }); + selectedOptions.forEach((option) => { + selectedOptionNames.push(option.name); + selectedOptionsWithDisabledStyle.push({ + ...option, + applyDisabledStyle: !enabledCategoriesName.includes(option.name), + }); + }); + + const enabledAndSelectedCategories = [...selectedOptionsWithDisabledStyle, ...enabledCategories]; const categorySections: CategoryTreeSection[] = []; const numberOfEnabledCategories = enabledCategories.length; @@ -982,7 +1002,7 @@ function getCategoryListSections( // "Selected" section title: '', shouldShow: false, - data: getCategoryOptionTree(selectedOptions, true), + data: getCategoryOptionTree(selectedOptionsWithDisabledStyle, true), }); return categorySections; @@ -1016,11 +1036,10 @@ function getCategoryListSections( // "Selected" section title: '', shouldShow: false, - data: getCategoryOptionTree(selectedOptions, true), + data: getCategoryOptionTree(selectedOptionsWithDisabledStyle, true), }); } - const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); const filteredCategories = enabledCategories.filter((category) => !selectedOptionNames.includes(category.name)); if (numberOfEnabledCategories < CONST.CATEGORY_LIST_THRESHOLD) { @@ -1067,7 +1086,7 @@ function getCategoryListSections( * * @param tags - an initial tag array */ -function getTagsOptions(tags: Array>): Option[] { +function getTagsOptions(tags: Tag[]): Option[] { return tags.map((tag) => { // This is to remove unnecessary escaping backslash in tag name sent from backend. const cleanedName = PolicyUtils.getCleanedTagName(tag.name); @@ -1078,6 +1097,7 @@ function getTagsOptions(tags: Array tooltipText: cleanedName, isDisabled: !tag.enabled, isSelected: tag.isSelected, + applyDisabledStyle: tag.applyDisabledStyle, }; }); } @@ -1094,9 +1114,23 @@ function getTagListSections( ) { const tagSections = []; const sortedTags = sortTags(tags) as PolicyTag[]; - const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name); - const enabledTags = sortedTags.filter((tag) => tag.enabled && !selectedOptionNames.includes(tag.name)); - const enabledAndSelectedTags = [...selectedOptions, ...enabledTags]; + const selectedOptionNames: string[] = []; + const enabledTagsName: string[] = []; + const selectedOptionsWithDisabledStyle: Category[] = []; + const enabledTags = sortedTags.filter((tag) => { + if (tag.enabled) { + enabledTagsName.push(tag.name); + } + return tag.enabled && !selectedOptionNames.includes(tag.name); + }); + selectedOptions.forEach((option) => { + selectedOptionNames.push(option.name); + selectedOptionsWithDisabledStyle.push({ + ...option, + applyDisabledStyle: !enabledTagsName.includes(option.name), + }); + }); + const enabledAndSelectedTags = [...selectedOptionsWithDisabledStyle, ...enabledTags]; const numberEnabledOfTags = enabledTags.length; // If all tags are disabled but there's a previously selected tag, show only the selected tag @@ -1105,7 +1139,7 @@ function getTagListSections( // "Selected" section title: '', shouldShow: false, - data: getTagsOptions(selectedOptions), + data: getTagsOptions(selectedOptionsWithDisabledStyle), }); return tagSections; @@ -1159,7 +1193,7 @@ function getTagListSections( // "Selected" section title: '', shouldShow: true, - data: getTagsOptions(selectedOptions), + data: getTagsOptions(selectedOptionsWithDisabledStyle), }); } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9fa28535a7a7..9ce996d52bbd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -407,6 +407,7 @@ type OptionData = { descriptiveText?: string; notificationPreference?: NotificationPreference | null; isDisabled?: boolean | null; + applyDisabledStyle?: boolean | null; name?: string | null; isSelfDM?: boolean | null; } & Report; From 2810b865debb0c3979cbc4522b868c60247d32f3 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 31 Mar 2024 19:18:56 +0530 Subject: [PATCH 0057/1525] minor changes. Signed-off-by: Krishna Gupta --- src/components/SelectionList/RadioListItem.tsx | 1 + src/libs/OptionsListUtils.ts | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/SelectionList/RadioListItem.tsx b/src/components/SelectionList/RadioListItem.tsx index 4e114c236896..a1258bd59424 100644 --- a/src/components/SelectionList/RadioListItem.tsx +++ b/src/components/SelectionList/RadioListItem.tsx @@ -55,6 +55,7 @@ function RadioListItem({ styles.sidebarLinkTextBold, isMultilineSupported ? styles.preWrap : styles.pre, item.alternateText ? styles.mb1 : null, + /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */ (isDisabled || item.applyDisabledStyle) && styles.colorMuted, isMultilineSupported ? {paddingLeft} : null, ]} diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 20e4543d178e..61caf716f209 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -985,6 +985,7 @@ function getCategoryListSections( } return category.enabled; }); + selectedOptions.forEach((option) => { selectedOptionNames.push(option.name); selectedOptionsWithDisabledStyle.push({ @@ -997,7 +998,7 @@ function getCategoryListSections( const categorySections: CategoryTreeSection[] = []; const numberOfEnabledCategories = enabledCategories.length; - if (numberOfEnabledCategories === 0 && selectedOptions.length > 0) { + if (numberOfEnabledCategories === 0 && selectedOptionsWithDisabledStyle.length > 0) { categorySections.push({ // "Selected" section title: '', @@ -1017,7 +1018,7 @@ function getCategoryListSections( } searchCategories.push({ ...category, - isSelected: selectedOptions.some((selectedOption) => selectedOption.name === category.name), + isSelected: selectedOptionNames.includes(category.name), }); }); @@ -1031,7 +1032,7 @@ function getCategoryListSections( return categorySections; } - if (selectedOptions.length > 0) { + if (selectedOptionsWithDisabledStyle.length > 0) { categorySections.push({ // "Selected" section title: '', @@ -1134,7 +1135,7 @@ function getTagListSections( const numberEnabledOfTags = enabledTags.length; // If all tags are disabled but there's a previously selected tag, show only the selected tag - if (numberEnabledOfTags === 0 && selectedOptions.length > 0) { + if (numberEnabledOfTags === 0 && selectedOptionsWithDisabledStyle.length > 0) { tagSections.push({ // "Selected" section title: '', @@ -1155,7 +1156,7 @@ function getTagListSections( searchTags.push({ ...tag, - isSelected: selectedOptions.some((selectedOption) => selectedOption.name === tag.name), + isSelected: selectedOptionNames.includes(tag.name), }); }); @@ -1188,7 +1189,7 @@ function getTagListSections( .map((tag) => ({name: tag, enabled: true})); const filteredTags = enabledTags.filter((tag) => !selectedOptionNames.includes(tag.name)); - if (selectedOptions.length) { + if (selectedOptionsWithDisabledStyle.length) { tagSections.push({ // "Selected" section title: '', From f7470d6d999a56a28be4ddb38cb9573e8d60bd2e Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 31 Mar 2024 19:29:46 +0530 Subject: [PATCH 0058/1525] fix: tag not shown as selected. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 61caf716f209..4f7e8b502a71 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1175,7 +1175,7 @@ function getTagListSections( // "All" section when items amount less than the threshold title: '', shouldShow: false, - data: getTagsOptions(enabledTags), + data: getTagsOptions(enabledAndSelectedTags), }); return tagSections; From 454c1786a89a1ea2190272a993a8ed414835c8f5 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 1 Apr 2024 00:19:11 +0100 Subject: [PATCH 0059/1525] Fix: Gray Replies does not disappear together with reply when reply is deleted --- src/pages/home/report/ReportActionsList.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index d1b9c420b0af..3d635603e10a 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -532,14 +532,13 @@ function ReportActionsList({ mostRecentIOUReportActionID={mostRecentIOUReportActionID} shouldHideThreadDividerLine={shouldHideThreadDividerLine} shouldDisplayNewMarker={shouldDisplayNewMarker(reportAction, index)} - shouldDisplayReplyDivider={sortedReportActions.length > 1} + shouldDisplayReplyDivider={sortedVisibleReportActions.length > 1} /> ), [ report, linkedReportActionID, sortedVisibleReportActions, - sortedReportActions.length, mostRecentIOUReportActionID, shouldHideThreadDividerLine, shouldDisplayNewMarker, From ccbd4f6cf3c171a5ddef0e25ad1898ca06693d6b Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 1 Apr 2024 02:47:42 +0100 Subject: [PATCH 0060/1525] Thread - Invited thread member can click on Thread when they are not a member of main chat --- src/components/ParentNavigationSubtitle.tsx | 4 ++++ src/libs/ReportUtils.ts | 8 ++++++++ src/pages/home/report/ReportActionItem.tsx | 7 ++++++- src/pages/home/report/ReportActionItemParentAction.tsx | 3 ++- src/pages/home/report/ThreadDivider.tsx | 5 ++++- src/styles/utils/index.ts | 4 ++-- 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index 3109453ca6b0..a778e2a701db 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -25,6 +25,10 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportID const {translate} = useLocalize(); + if (!reportName){ + return; + } + return ( { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9fa28535a7a7..652c99f2b491 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5306,6 +5306,13 @@ function isReportParticipant(accountID: number, report: OnyxEntry): bool return possibleAccountIDs.includes(accountID); } +/** + * Check to see if the current user has access to view the report. + */ +function canCurrentUserOpenReport(report: OnyxEntry): boolean { + return (isReportParticipant(currentUserAccountID ?? 0, report) || isPublicRoom(report)) && canAccessReport(report, allPolicies, allBetas); +} + function shouldUseFullTitleToDisplay(report: OnyxEntry): boolean { return isMoneyRequestReport(report) || isPolicyExpenseChat(report) || isChatRoom(report) || isChatThread(report) || isTaskReport(report); } @@ -5857,6 +5864,7 @@ export { getChildReportNotificationPreference, getAllAncestorReportActions, isReportParticipant, + canCurrentUserOpenReport, isValidReport, getReportDescriptionText, isReportFieldOfTypeTitle, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 2716fedcf59a..e43f8fbbfc6b 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -145,6 +145,9 @@ type ReportActionItemProps = { /** Callback to be called on onPress */ onPress?: () => void; + + /** Should press be disabled */ + isDisabled?: boolean; } & ReportActionItemOnyxProps; const isIOUReport = (actionObj: OnyxEntry): actionObj is OnyxTypes.ReportActionBase & OnyxTypes.OriginalMessageIOU => @@ -169,6 +172,7 @@ function ReportActionItem({ policy, transaction, onPress = undefined, + isDisabled = false, }: ReportActionItemProps) { const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -831,6 +835,7 @@ function ReportActionItem({ isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} @@ -860,7 +865,7 @@ function ReportActionItem({ checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} setIsEmojiPickerActive={setIsEmojiPickerActive} /> - + ReportActions.clearAllRelatedReportActionErrors(report.reportID, action)} // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index 3d98973c86c4..c55cae369410 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -101,9 +101,10 @@ function ReportActionItemParentAction({ errorRowStyles={[styles.ml10, styles.mr2]} onClose={() => Report.navigateToConciergeChatAndDeleteReport(ancestor.report.reportID)} > - + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID ?? ''))} + isDisabled={!ReportUtils.canCurrentUserOpenReport(ReportUtils.getReport(ancestor?.report?.parentReportID) as OnyxTypes.Report)} parentReportAction={parentReportAction} report={ancestor.report} reportActions={reportActions} diff --git a/src/pages/home/report/ThreadDivider.tsx b/src/pages/home/report/ThreadDivider.tsx index 083129e15e6d..4500af526428 100644 --- a/src/pages/home/report/ThreadDivider.tsx +++ b/src/pages/home/report/ThreadDivider.tsx @@ -16,9 +16,11 @@ import ROUTES from '@src/ROUTES'; type ThreadDividerProps = { /** Thread ancestor */ ancestor: Ancestor; + /** Whether the link is disbled */ + isDisabled: boolean; }; -function ThreadDivider({ancestor}: ThreadDividerProps) { +function ThreadDivider({ancestor, isDisabled}: ThreadDividerProps) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -30,6 +32,7 @@ function ThreadDivider({ancestor}: ThreadDividerProps) { accessibilityLabel={translate('threads.thread')} role={CONST.ROLE.BUTTON} style={[styles.flexRow, styles.alignItemsCenter, styles.gap1]} + disabled={isDisabled} > ({ /** * Generate the styles for the ReportActionItem wrapper view. */ - getReportActionItemStyle: (isHovered = false, isClickable = false): ViewStyle => + getReportActionItemStyle: (isHovered = false, isClickable = false, isDisabled = false): ViewStyle => // TODO: Remove this "eslint-disable-next" once the theme switching migration is done and styles are fully typed (GH Issue: https://github.com/Expensify/App/issues/27337) // eslint-disable-next-line @typescript-eslint/no-unsafe-return ({ @@ -1462,7 +1462,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ : // Warning: Setting this to a non-transparent color will cause unread indicator to break on Android theme.transparent, opacity: 1, - ...(isClickable ? styles.cursorPointer : styles.cursorInitial), + ...(isDisabled ? styles.cursorDisabled : (isClickable ? styles.cursorPointer : styles.cursorInitial)), }), /** From 2fc36ea9e2ea31ab8a4cb32135f003f9027c13f7 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 1 Apr 2024 02:48:36 +0100 Subject: [PATCH 0061/1525] Fix: Thread-Cursor is hand cursor instead of text cursor when hovering over edit composer in thread --- src/pages/home/report/ReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index e43f8fbbfc6b..732e7ce06e55 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -865,7 +865,7 @@ function ReportActionItem({ checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} setIsEmojiPickerActive={setIsEmojiPickerActive} /> - + ReportActions.clearAllRelatedReportActionErrors(report.reportID, action)} // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing From 80078c34e96b189b79396e40999043155836f410 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 1 Apr 2024 02:51:19 +0100 Subject: [PATCH 0062/1525] prettier --- src/components/ParentNavigationSubtitle.tsx | 2 +- src/pages/home/report/ReportActionItem.tsx | 8 +++++++- src/pages/home/report/ReportActionItemParentAction.tsx | 5 ++++- src/styles/utils/index.ts | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index a778e2a701db..24b2ddf8a10c 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -25,7 +25,7 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportID const {translate} = useLocalize(); - if (!reportName){ + if (!reportName) { return; } diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 732e7ce06e55..ebabb189a23d 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -865,7 +865,13 @@ function ReportActionItem({ checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} setIsEmojiPickerActive={setIsEmojiPickerActive} /> - + ReportActions.clearAllRelatedReportActionErrors(report.reportID, action)} // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index c55cae369410..4bdbe842df96 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -101,7 +101,10 @@ function ReportActionItemParentAction({ errorRowStyles={[styles.ml10, styles.mr2]} onClose={() => Report.navigateToConciergeChatAndDeleteReport(ancestor.report.reportID)} > - + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID ?? ''))} isDisabled={!ReportUtils.canCurrentUserOpenReport(ReportUtils.getReport(ancestor?.report?.parentReportID) as OnyxTypes.Report)} diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 34ba0fdb9a8d..59f0a6fb322e 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1462,7 +1462,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ : // Warning: Setting this to a non-transparent color will cause unread indicator to break on Android theme.transparent, opacity: 1, - ...(isDisabled ? styles.cursorDisabled : (isClickable ? styles.cursorPointer : styles.cursorInitial)), + ...(isDisabled ? styles.cursorDisabled : isClickable ? styles.cursorPointer : styles.cursorInitial), }), /** From 92fd58acbca1d92dbf9399c2eabce84b2a1b82c7 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 1 Apr 2024 02:55:49 +0100 Subject: [PATCH 0063/1525] fix lint error --- src/styles/utils/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 59f0a6fb322e..1ef2298392ae 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1462,6 +1462,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ : // Warning: Setting this to a non-transparent color will cause unread indicator to break on Android theme.transparent, opacity: 1, + // eslint-disable-next-line no-nested-ternary ...(isDisabled ? styles.cursorDisabled : isClickable ? styles.cursorPointer : styles.cursorInitial), }), From 496791196798dce7ea14db3a6c7a6acf4f4a4bc8 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 1 Apr 2024 17:10:21 +0700 Subject: [PATCH 0064/1525] remove redundant comment --- src/components/OfflineIndicator.tsx | 31 +++++++++++++---------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index bb497fad5b4e..26a9dd740e24 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -49,23 +49,20 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { height={variables.iconSizeSmall} /> - { - // If we reversed the ternary, unreachability message would always show even when offline - isOffline ? ( - translate('common.youAppearToBeOffline') - ) : ( - <> - {translate('common.weMightHaveProblem')} - - {new URL(CONST.STATUS_EXPENSIFY_URL).host} - - . - - ) - } + {isOffline ? ( + translate('common.youAppearToBeOffline') + ) : ( + <> + {translate('common.weMightHaveProblem')} + + {new URL(CONST.STATUS_EXPENSIFY_URL).host} + + . + + )} ); From 4213ccf99135100c4963a4e127dd7689efa3797b Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 1 Apr 2024 17:10:30 +0700 Subject: [PATCH 0065/1525] fix typecheck --- .storybook/preview.tsx | 2 +- src/types/onyx/Network.ts | 1 + tests/unit/APITest.ts | 38 +++++++++++++++++++------------------- tests/unit/NetworkTest.ts | 10 +++++----- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 4767c7d81343..9c6738704c45 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -16,7 +16,7 @@ import './fonts.css'; Onyx.init({ keys: ONYXKEYS, initialKeyStates: { - [ONYXKEYS.NETWORK]: {isOffline: false}, + [ONYXKEYS.NETWORK]: {isOffline: false, isBackendReachable: true}, }, }); diff --git a/src/types/onyx/Network.ts b/src/types/onyx/Network.ts index d4162d1ffdb7..e9a56ba013a8 100644 --- a/src/types/onyx/Network.ts +++ b/src/types/onyx/Network.ts @@ -2,6 +2,7 @@ type Network = { /** Is the network currently offline or not */ isOffline: boolean; + /** Is the backend reachable when online */ isBackendReachable: boolean; /** Should the network be forced offline */ diff --git a/tests/unit/APITest.ts b/tests/unit/APITest.ts index 359288b2a1ef..b9404973d418 100644 --- a/tests/unit/APITest.ts +++ b/tests/unit/APITest.ts @@ -68,7 +68,7 @@ describe('APITests', () => { const xhr = jest.spyOn(HttpUtils, 'xhr').mockRejectedValue(new Error('Unexpected xhr call')); // Given we're offline - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false}) .then(() => { // When API Writes and Reads are called // @ts-expect-error - mocking the parameter @@ -104,7 +104,7 @@ describe('APITests', () => { // Given we have some requests made while we're offline return ( Onyx.multiSet({ - [ONYXKEYS.NETWORK]: {isOffline: true}, + [ONYXKEYS.NETWORK]: {isOffline: true, isBackendReachable: false}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'}, [ONYXKEYS.SESSION]: {authToken: 'testToken'}, }) @@ -122,7 +122,7 @@ describe('APITests', () => { }) // When we resume connectivity - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { expect(NetworkStore.isOffline()).toBe(false); @@ -158,7 +158,7 @@ describe('APITests', () => { // Given we have some requests made while we're offline return ( - Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) + Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false}) .then(() => { // When API Write commands are made // @ts-expect-error - mocking the parameter @@ -169,7 +169,7 @@ describe('APITests', () => { }) // When we resume connectivity - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { // Then requests should remain persisted until the xhr call is resolved @@ -222,7 +222,7 @@ describe('APITests', () => { // Given we have a request made while we're offline return ( - Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) + Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false}) .then(() => { // When API Write commands are made // @ts-expect-error - mocking the parameter @@ -231,7 +231,7 @@ describe('APITests', () => { }) // When we resume connectivity - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { // Then there has only been one request so far @@ -310,7 +310,7 @@ describe('APITests', () => { Onyx.merge(ONYXKEYS.CREDENTIALS, {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'}); return ( waitForBatchedUpdates() - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: true})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false})) .then(() => { // @ts-expect-error - mocking the parameter API.write('Mock', {param1: 'value1'}); @@ -318,7 +318,7 @@ describe('APITests', () => { }) // When we resume connectivity - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { const nonLogCalls = xhr.mock.calls.filter(([commandName]) => commandName !== 'Log'); @@ -341,7 +341,7 @@ describe('APITests', () => { const xhr = jest.spyOn(HttpUtils, 'xhr').mockResolvedValue({jsonCode: CONST.JSON_CODE.SUCCESS}); return Onyx.multiSet({ [ONYXKEYS.SESSION]: {authToken: 'anyToken'}, - [ONYXKEYS.NETWORK]: {isOffline: true}, + [ONYXKEYS.NETWORK]: {isOffline: true, isBackendReachable: false}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test_user', autoGeneratedPassword: 'psswd'}, }) .then(() => { @@ -363,7 +363,7 @@ describe('APITests', () => { return waitForBatchedUpdates(); }) - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { // Then expect all 7 calls to have been made and for the Writes to be made in the order that we made them @@ -384,7 +384,7 @@ describe('APITests', () => { const xhr = jest.spyOn(HttpUtils, 'xhr').mockResolvedValueOnce({jsonCode: CONST.JSON_CODE.NOT_AUTHENTICATED}).mockResolvedValue({jsonCode: CONST.JSON_CODE.SUCCESS}); return Onyx.multiSet({ - [ONYXKEYS.NETWORK]: {isOffline: true}, + [ONYXKEYS.NETWORK]: {isOffline: true, isBackendReachable: false}, [ONYXKEYS.SESSION]: {authToken: 'test'}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'}, }) @@ -404,7 +404,7 @@ describe('APITests', () => { API.write('MockCommand', {content: 'value6'}); return waitForBatchedUpdates(); }) - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { // Then expect only 8 calls to have been made total and for them to be made in the order that we made them despite requiring reauthentication @@ -434,7 +434,7 @@ describe('APITests', () => { return Onyx.multiSet({ [ONYXKEYS.SESSION]: {authToken: 'oldToken'}, - [ONYXKEYS.NETWORK]: {isOffline: false}, + [ONYXKEYS.NETWORK]: {isOffline: false, isBackendReachable: true}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test_user', autoGeneratedPassword: 'psswd'}, }) .then(() => { @@ -448,7 +448,7 @@ describe('APITests', () => { forceNetworkRequest: false, }); - Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}); + Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false}); expect(NetworkStore.isOffline()).toBe(false); expect(NetworkStore.isAuthenticating()).toBe(false); return waitForBatchedUpdates(); @@ -467,7 +467,7 @@ describe('APITests', () => { waitForBatchedUpdates(); // Come back from offline to trigger the sequential queue flush - Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}); }) .then(() => { // When we wait for the sequential queue to finish @@ -514,7 +514,7 @@ describe('APITests', () => { // Given a simulated a condition where the credentials have not yet been read from storage and we are offline return Onyx.multiSet({ - [ONYXKEYS.NETWORK]: {isOffline: true}, + [ONYXKEYS.NETWORK]: {isOffline: true, isBackendReachable: false}, [ONYXKEYS.CREDENTIALS]: {}, [ONYXKEYS.SESSION]: null, }) @@ -531,7 +531,7 @@ describe('APITests', () => { expect(PersistedRequests.getAll().length).toBe(1); // When we go online and wait for promises to resolve - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}); }) .then(waitForBatchedUpdates) .then(() => { @@ -555,7 +555,7 @@ describe('APITests', () => { test('Write request will move directly to the SequentialQueue when we are online and block non-Write requests', () => { const xhr = jest.spyOn(HttpUtils, 'xhr'); - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { // GIVEN that we are online expect(NetworkStore.isOffline()).toBe(false); diff --git a/tests/unit/NetworkTest.ts b/tests/unit/NetworkTest.ts index 63b275a1a6b6..eef607f8036b 100644 --- a/tests/unit/NetworkTest.ts +++ b/tests/unit/NetworkTest.ts @@ -110,7 +110,7 @@ describe('NetworkTests', () => { // This should first trigger re-authentication and then a Failed to fetch PersonalDetails.openPersonalDetails(); return waitForBatchedUpdates() - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(() => { expect(isOffline).toBe(false); @@ -261,7 +261,7 @@ describe('NetworkTests', () => { const logHmmmSpy = jest.spyOn(Log, 'hmmm'); // Given we have a request made while online - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { Network.post('MockBadNetworkResponse', {param1: 'value1'}); return waitForBatchedUpdates(); @@ -277,7 +277,7 @@ describe('NetworkTests', () => { const logAlertSpy = jest.spyOn(Log, 'alert'); // Given we have a request made while online - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { Network.post('MockBadNetworkResponse', {param1: 'value1'}); return waitForBatchedUpdates(); @@ -293,7 +293,7 @@ describe('NetworkTests', () => { const onResolved = jest.fn(); // Given we have a request made while online - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { expect(NetworkStore.isOffline()).toBe(false); @@ -314,7 +314,7 @@ describe('NetworkTests', () => { // GIVEN a mock that will return a "cancelled" request error global.fetch = jest.fn().mockRejectedValue(new DOMException('Aborted', CONST.ERROR.REQUEST_CANCELLED)); - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { // WHEN we make a few requests and then cancel them Network.post('MockCommandOne'); From 5c860463ab31c2772a937e9570aa82586a8241c7 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 1 Apr 2024 17:23:54 +0700 Subject: [PATCH 0066/1525] rename constant --- src/CONST.ts | 2 +- src/libs/NetworkConnection.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 74938324357a..4b1786fdf643 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -976,7 +976,7 @@ const CONST = { MAX_RETRY_WAIT_TIME_MS: 10 * 1000, PROCESS_REQUEST_DELAY_MS: 1000, MAX_PENDING_TIME_MS: 10 * 1000, - REACHABILITY_TIMEOUT_MS: 60 * 1000, + BACKEND_CHECK_INTERVAL_MS: 60 * 1000, MAX_REQUEST_RETRIES: 10, }, WEEK_STARTS_ON: 1, // Monday diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 57ae5be3f9fa..4048cc209c59 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -94,7 +94,7 @@ function subscribeToBackendReachability(): () => void { }) .then(NetworkActions.setIsBackendReachable) .catch(() => NetworkActions.setIsBackendReachable(false)); - }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); + }, CONST.NETWORK.BACKEND_CHECK_INTERVAL_MS); return () => { clearInterval(intervalID); From f38039d720ce9c639eac7a784d923e4501472d81 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Tue, 2 Apr 2024 04:36:58 +0100 Subject: [PATCH 0067/1525] added explanation comment --- src/components/ParentNavigationSubtitle.tsx | 1 + src/pages/home/report/ThreadDivider.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index 24b2ddf8a10c..e8cb2e634045 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -25,6 +25,7 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportID const {translate} = useLocalize(); + // We should not display the parent navigation subtitle if the user does not have access to the parent chat (the reportName is empty in this case) if (!reportName) { return; } diff --git a/src/pages/home/report/ThreadDivider.tsx b/src/pages/home/report/ThreadDivider.tsx index 4500af526428..d055ea6a9da4 100644 --- a/src/pages/home/report/ThreadDivider.tsx +++ b/src/pages/home/report/ThreadDivider.tsx @@ -16,7 +16,8 @@ import ROUTES from '@src/ROUTES'; type ThreadDividerProps = { /** Thread ancestor */ ancestor: Ancestor; - /** Whether the link is disbled */ + + /** Whether the link is disabled */ isDisabled: boolean; }; From 20912d7fde785437a66b90b9cede7bae9e775290 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 2 Apr 2024 11:15:37 +0530 Subject: [PATCH 0068/1525] feat: sticky header component selection list. Signed-off-by: Krishna Gupta --- .../SelectionList/BaseSelectionList.tsx | 22 +++++++++---------- src/styles/index.ts | 4 ++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 0d8c8d06bf68..619cb2ce40ab 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -301,7 +301,7 @@ function BaseSelectionList( }; const renderSectionHeader = ({section}: {section: SectionListDataType}) => { - if (!section.title || isEmptyObject(section.data)) { + if (!section.title || isEmptyObject(section.data) || ListHeaderComponent) { return null; } @@ -345,7 +345,7 @@ function BaseSelectionList( const header = () => ( <> {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( - + ( ( + <> + {renderSectionHeader(arg)} + {ListHeaderComponent && header()} + + )} renderItem={renderItem} getItemLayout={getItemLayout} onScroll={onScroll} @@ -582,14 +586,8 @@ function BaseSelectionList( onLayout={onSectionListLayout} style={(!maxToRenderPerBatch || isInitialSectionListRender) && styles.opacity0} ListFooterComponent={ShowMoreButtonInstance} - ListHeaderComponent={ - ListHeaderComponent && ( - <> - {ListHeaderComponent} - {header()} - - ) - } + ListHeaderComponent={ListHeaderComponent && ListHeaderComponent} + stickySectionHeadersEnabled={!!ListHeaderComponent && true} /> {children} diff --git a/src/styles/index.ts b/src/styles/index.ts index a736bc537fa6..6f4383fbbaff 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4338,6 +4338,10 @@ const styles = (theme: ThemeColors) => borderRadius: 8, }, + selectionListStickyHeader: { + backgroundColor: theme.dropUIBG, + }, + draggableTopBar: { height: 30, width: '100%', From 950c69d0d94657b3cf103e778a49ff1f5b8d67c7 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 2 Apr 2024 11:55:57 +0530 Subject: [PATCH 0069/1525] feat: make header buttons scroll on small screen width in policy pages. Signed-off-by: Krishna Gupta --- src/pages/workspace/WorkspaceMembersPage.tsx | 11 +++++++++-- .../distanceRates/PolicyDistanceRatesPage.tsx | 19 +++++++++++++++---- .../workspace/tags/WorkspaceTagsPage.tsx | 19 +++++++++++++++---- .../workspace/taxes/WorkspaceTaxesPage.tsx | 19 +++++++++++++++---- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 7b77f6b60ede..8c93fd9fe455 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -563,7 +563,6 @@ function WorkspaceMembersPage({ > {!isSmallScreenWidth && getHeaderButtons()} - {isSmallScreenWidth && {getHeaderButtons()}} toggleUser(item.accountID)} onSelectAll={() => toggleAllUsers(data)} @@ -600,6 +599,14 @@ function WorkspaceMembersPage({ shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} textInputRef={textInputRef} customListHeader={getCustomListHeader()} + ListHeaderComponent={ + isSmallScreenWidth ? ( + + {getHeaderContent()} + {getHeaderButtons()} + + ) : null + } listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} /> diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index a5356a8fd05a..6a4fdbcf6e17 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -267,6 +267,15 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) ); + const getSmallWidthHeaderComponent = () => ( + <> + {headerButtons} + + {translate('workspace.distanceRates.centrallyManage')} + + + ); + return ( @@ -287,10 +296,11 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) > {!isSmallScreenWidth && headerButtons} - {isSmallScreenWidth && {headerButtons}} - - {translate('workspace.distanceRates.centrallyManage')} - + {!isSmallScreenWidth && ( + + {translate('workspace.distanceRates.centrallyManage')} + + )} {isLoading && ( )} diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 53376c05878f..072082eda916 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -256,6 +256,15 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { ); }; + const getSmallWidthHeaderComponent = () => ( + <> + {getHeaderButtons()} + + {translate('workspace.tags.subtitle')} + + + ); + return ( @@ -287,10 +296,11 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { cancelText={translate('common.cancel')} danger /> - {isSmallScreenWidth && {getHeaderButtons()}} - - {translate('workspace.tags.subtitle')} - + {!isSmallScreenWidth && ( + + {translate('workspace.tags.subtitle')} + + )} {isLoading && ( Policy.clearPolicyTagErrors(route.params.policyID, item.value)} /> diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index 4f8782dcdf3f..b46d5564aedc 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -234,6 +234,15 @@ function WorkspaceTaxesPage({ /> ); + const getSmallWidthHeaderComponent = () => ( + <> + {headerButtons} + + {translate('workspace.taxes.subtitle')} + + + ); + return ( @@ -255,11 +264,12 @@ function WorkspaceTaxesPage({ {!isSmallScreenWidth && headerButtons} - {isSmallScreenWidth && {headerButtons}} + {!isSmallScreenWidth && ( + + {translate('workspace.taxes.subtitle')} + + )} - - {translate('workspace.taxes.subtitle')} - {isLoading && ( (item.keyForList ? clearTaxRateError(policyID, item.keyForList, item.pendingAction) : undefined)} /> From d46dc8eaa13d4ba70835cb36a5ea041d7fd1b8d4 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 2 Apr 2024 12:34:49 +0530 Subject: [PATCH 0070/1525] fix header bg color. Signed-off-by: Krishna Gupta --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 6f4383fbbaff..1629840a6bd0 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4339,7 +4339,7 @@ const styles = (theme: ThemeColors) => }, selectionListStickyHeader: { - backgroundColor: theme.dropUIBG, + backgroundColor: theme.appBG, }, draggableTopBar: { From 5de949bb681aa01fa9fee121d78878dcb9c67023 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 2 Apr 2024 15:48:30 +0700 Subject: [PATCH 0071/1525] Modify comment --- src/libs/NetworkConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 4048cc209c59..4383a921662f 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -106,7 +106,7 @@ function subscribeToBackendReachability(): () => void { * @returns unsubscribe method */ function subscribeToNetworkStatus(): () => void { - // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". + // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for reachability. // If you need to test the "recheck" feature then switch to the production API proxy server. const unsubscribeFromBackendReachability = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; From 928b10f8484ab5fdfd8b8680bcd6e46fc40f01b1 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:03:40 +0100 Subject: [PATCH 0072/1525] make the thread link look gray when it's disabled --- src/pages/home/report/ThreadDivider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ThreadDivider.tsx b/src/pages/home/report/ThreadDivider.tsx index d055ea6a9da4..98bcec50750e 100644 --- a/src/pages/home/report/ThreadDivider.tsx +++ b/src/pages/home/report/ThreadDivider.tsx @@ -37,11 +37,11 @@ function ThreadDivider({ancestor, isDisabled}: ThreadDividerProps) { > - {translate('threads.thread')} + {translate('threads.thread')} {!ancestor.shouldDisplayNewMarker && } From 2c4a34a8e7765dbae30e0fd9ce223e7ffef4135b Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:04:58 +0100 Subject: [PATCH 0073/1525] prevent Thread and Replies words from being copied --- src/pages/home/report/RepliesDivider.tsx | 2 +- src/pages/home/report/ThreadDivider.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/RepliesDivider.tsx b/src/pages/home/report/RepliesDivider.tsx index deac38596c99..9d237e20b9c9 100644 --- a/src/pages/home/report/RepliesDivider.tsx +++ b/src/pages/home/report/RepliesDivider.tsx @@ -19,7 +19,7 @@ function RepliesDivider({shouldHideThreadDividerLine}: RepliesDividerProps) { const {translate} = useLocalize(); return ( - + + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor?.report?.parentReportID ?? ''))} accessibilityLabel={translate('threads.thread')} From 4860d792d5ee863fef8e8ca053261eec7049dfdf Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:34:48 +0100 Subject: [PATCH 0074/1525] prevent Thread and Replies words from being selected and copied --- src/pages/home/report/RepliesDivider.tsx | 8 ++++++-- src/pages/home/report/ThreadDivider.tsx | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/RepliesDivider.tsx b/src/pages/home/report/RepliesDivider.tsx index 9d237e20b9c9..d6d5e1d3cfc8 100644 --- a/src/pages/home/report/RepliesDivider.tsx +++ b/src/pages/home/report/RepliesDivider.tsx @@ -7,6 +7,7 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; type RepliesDividerProps = { /** Whether we should hide thread divider line */ @@ -19,14 +20,17 @@ function RepliesDivider({shouldHideThreadDividerLine}: RepliesDividerProps) { const {translate} = useLocalize(); return ( - + - {translate('threads.replies')} + {translate('threads.replies')} {!shouldHideThreadDividerLine && } ); diff --git a/src/pages/home/report/ThreadDivider.tsx b/src/pages/home/report/ThreadDivider.tsx index 360408c4a823..2ac2d6449f67 100644 --- a/src/pages/home/report/ThreadDivider.tsx +++ b/src/pages/home/report/ThreadDivider.tsx @@ -27,7 +27,10 @@ function ThreadDivider({ancestor, isDisabled}: ThreadDividerProps) { const {translate} = useLocalize(); return ( - + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor?.report?.parentReportID ?? ''))} accessibilityLabel={translate('threads.thread')} From 9bd4c96250b25ac591c8904fa9709510dbcfda50 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 2 Apr 2024 23:32:51 +0530 Subject: [PATCH 0075/1525] adds onError prop in react-fast-pdf --- package-lock.json | 246 +++++++----------- package.json | 4 +- .../AttachmentViewPdf/index.tsx | 3 +- .../DefaultAttachmentView/index.tsx | 6 + src/components/FullscreenLoadingIndicator.tsx | 5 +- src/components/PDFView/index.js | 48 ++-- 6 files changed, 130 insertions(+), 182 deletions(-) diff --git a/package-lock.json b/package-lock.json index 809b4bd5c4df..7fc027a16e9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,7 +72,7 @@ "react-content-loader": "^7.0.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "^1.0.6", + "react-fast-pdf": "https://github.com/ishpaul777/react-fast-pdf/tree/fix-blankArea-after-loading-pdf", "react-map-gl": "^7.1.3", "react-native": "0.73.2", "react-native-android-location-enabler": "^2.0.1", @@ -3098,8 +3098,10 @@ }, "node_modules/@expensify/react-native-live-markdown": { "version": "0.1.35", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.35.tgz", - "integrity": "sha512-W0FFIiU/sT+AwIrIOUHiNAHYjODAkEdYsf75tfBbkA6v2byHPxUlbzaJrZEQc0HgbvtAfTf9iQQqGWjNqe4pog==", + "license": "MIT", + "workspaces": [ + "example" + ], "engines": { "node": ">= 18.0.0" }, @@ -6402,8 +6404,6 @@ }, "node_modules/@jest/types": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", "optional": true, @@ -6795,8 +6795,9 @@ "license": "BSD-3-Clause" }, "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.10", - "license": "BSD-3-Clause", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "optional": true, "dependencies": { "detect-libc": "^2.0.0", @@ -6815,7 +6816,8 @@ }, "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { "version": "3.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "optional": true, "dependencies": { "semver": "^6.0.0" @@ -6829,26 +6831,13 @@ }, "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { "version": "6.3.1", - "license": "ISC", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "optional": true, "bin": { "semver": "bin/semver.js" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { - "version": "5.0.0", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@mapbox/point-geometry": { "version": "0.1.0", "license": "ISC" @@ -8187,8 +8176,7 @@ }, "node_modules/@react-native-community/cli-doctor/node_modules/ip": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" + "license": "MIT" }, "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { "version": "5.2.0", @@ -8271,8 +8259,7 @@ }, "node_modules/@react-native-community/cli-hermes/node_modules/ip": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" + "license": "MIT" }, "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { "version": "7.2.0", @@ -8445,8 +8432,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -8460,8 +8446,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -8488,8 +8473,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8517,8 +8501,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8542,8 +8525,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -19039,8 +19021,6 @@ }, "node_modules/@types/yargs": { "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "license": "MIT", "optional": true, @@ -19051,8 +19031,6 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "license": "MIT" }, "node_modules/@types/yauzl": { @@ -19991,8 +19969,6 @@ }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true, "license": "BSD-2-Clause" }, @@ -20007,7 +19983,8 @@ }, "node_modules/abbrev": { "version": "1.1.1", - "license": "ISC", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "node_modules/abort-controller": { @@ -20934,8 +20911,6 @@ }, "node_modules/async": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", "dev": true, "license": "MIT" }, @@ -22399,9 +22374,8 @@ }, "node_modules/bs-logger": { "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, + "license": "MIT", "dependencies": { "fast-json-stable-stringify": "2.x" }, @@ -22920,8 +22894,9 @@ }, "node_modules/canvas": { "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", "hasInstallScript": true, - "license": "MIT", "optional": true, "dependencies": { "@mapbox/node-pre-gyp": "^1.0.0", @@ -23334,8 +23309,7 @@ }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -23661,9 +23635,8 @@ }, "node_modules/concurrently": { "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.2", "date-fns": "^2.30.0", @@ -23688,9 +23661,8 @@ }, "node_modules/concurrently/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -23703,9 +23675,8 @@ }, "node_modules/concurrently/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -23719,9 +23690,8 @@ }, "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -23731,9 +23701,8 @@ }, "node_modules/concurrently/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -23743,24 +23712,21 @@ }, "node_modules/concurrently/node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/concurrently/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/concurrently/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -25335,8 +25301,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "license": "Apache-2.0", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "optional": true, "engines": { "node": ">=8" @@ -25671,8 +25638,6 @@ }, "node_modules/duplexer": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true, "license": "MIT" }, @@ -27640,8 +27605,7 @@ }, "node_modules/expo-image": { "version": "1.11.0", - "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-1.11.0.tgz", - "integrity": "sha512-CfkSGWIGidxxqzErke16bCS9aefS04qvgjk+T9Nc34LAb3ysBAqCv5hoCnvylHOvi/7jTCC/ouLm5oLLqkDSRQ==", + "license": "MIT", "dependencies": { "@react-native/assets-registry": "~0.73.1" }, @@ -27651,16 +27615,14 @@ }, "node_modules/expo-image-loader": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-4.6.0.tgz", - "integrity": "sha512-RHQTDak7/KyhWUxikn2yNzXL7i2cs16cMp6gEAgkHOjVhoCJQoOJ0Ljrt4cKQ3IowxgCuOrAgSUzGkqs7omj8Q==", + "license": "MIT", "peerDependencies": { "expo": "*" } }, "node_modules/expo-image-manipulator": { "version": "11.8.0", - "resolved": "https://registry.npmjs.org/expo-image-manipulator/-/expo-image-manipulator-11.8.0.tgz", - "integrity": "sha512-ZWVrHnYmwJq6h7auk+ropsxcNi+LyZcPFKQc8oy+JA0SaJosfShvkCm7RADWAunHmfPCmjHrhwPGEu/rs7WG/A==", + "license": "MIT", "dependencies": { "expo-image-loader": "~4.6.0" }, @@ -29657,8 +29619,6 @@ }, "node_modules/html-entities": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", - "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "funding": [ { "type": "github", @@ -29668,7 +29628,8 @@ "type": "patreon", "url": "https://patreon.com/mdevils" } - ] + ], + "license": "MIT" }, "node_modules/html-escaper": { "version": "2.0.2", @@ -30573,9 +30534,8 @@ }, "node_modules/ip": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ip-regex": { "version": "2.1.0", @@ -34355,8 +34315,6 @@ }, "node_modules/klaw-sync": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", "dev": true, "license": "MIT", "dependencies": { @@ -34676,9 +34634,8 @@ }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -36710,6 +36667,21 @@ "url": "https://github.com/sponsors/antelle" } }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "license": "BSD-2-Clause", @@ -37624,8 +37596,6 @@ }, "node_modules/patch-package": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", - "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", "dev": true, "license": "MIT", "dependencies": { @@ -37655,8 +37625,6 @@ }, "node_modules/patch-package/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -37671,8 +37639,6 @@ }, "node_modules/patch-package/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -37688,8 +37654,6 @@ }, "node_modules/patch-package/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -37701,15 +37665,11 @@ }, "node_modules/patch-package/node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/patch-package/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -37718,8 +37678,6 @@ }, "node_modules/patch-package/node_modules/open": { "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "dev": true, "license": "MIT", "dependencies": { @@ -37735,8 +37693,6 @@ }, "node_modules/patch-package/node_modules/rimraf": { "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "license": "ISC", "dependencies": { @@ -37748,8 +37704,6 @@ }, "node_modules/patch-package/node_modules/slash": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true, "license": "MIT", "engines": { @@ -37758,8 +37712,6 @@ }, "node_modules/patch-package/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -37771,8 +37723,6 @@ }, "node_modules/patch-package/node_modules/tmp": { "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "license": "MIT", "dependencies": { @@ -38956,8 +38906,7 @@ }, "node_modules/react-content-loader": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/react-content-loader/-/react-content-loader-7.0.0.tgz", - "integrity": "sha512-xaBwpO7eiJyEc4ndym+g6wcruV9W2y3DKqbw4U48QFBsv0IeAVZO+aCUb8GptlDLWM8n5zi2HcFSGlj5r+53Tg==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -39051,8 +39000,8 @@ }, "node_modules/react-fast-pdf": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/react-fast-pdf/-/react-fast-pdf-1.0.6.tgz", - "integrity": "sha512-CdAnBSZaLCGLSEuiqWLzzXhV9Wvdf1VRixaXCrb3NFrXyeltahF7PY+u7eU6ynrWZGmNI6g0cMLPv0DQhJEeew==", + "resolved": "git+ssh://git@github.com/ishpaul777/react-fast-pdf.git#0a8a073ad5f0d376c1827a2dacfafd197500bf3b", + "license": "MIT", "dependencies": { "react-pdf": "^7.7.0", "react-window": "^1.8.10" @@ -39109,14 +39058,6 @@ } } }, - "node_modules/react-fast-pdf/node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/react-freeze": { "version": "1.0.3", "license": "MIT", @@ -39407,6 +39348,7 @@ "node_modules/react-native-image-size": { "version": "1.1.3", "resolved": "git+ssh://git@github.com/Expensify/react-native-image-size.git#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", + "integrity": "sha512-uIZLaqqjSivO+iBGx3VpZRSn/cYy18ct6S1H35gK8n74eJFb/Ds6qUJ+jGw5PUt0KEzw+aXLgPq6gHDXT5Q29A==", "license": "MIT" }, "node_modules/react-native-key-command": { @@ -39836,8 +39778,7 @@ }, "node_modules/react-native/node_modules/@jest/types": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -39851,8 +39792,7 @@ }, "node_modules/react-native/node_modules/@types/yargs": { "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -39879,8 +39819,7 @@ }, "node_modules/react-native/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -39920,8 +39859,7 @@ }, "node_modules/react-native/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -39976,8 +39914,7 @@ }, "node_modules/react-native/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -40593,8 +40530,7 @@ }, "node_modules/react-window": { "version": "1.8.10", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", - "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "memoize-one": ">=3.1.1 <6" @@ -41551,9 +41487,8 @@ }, "node_modules/rxjs": { "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -42048,6 +41983,8 @@ }, "node_modules/simple-concat": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", "funding": [ { "type": "github", @@ -42062,12 +41999,12 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "optional": true }, "node_modules/simple-get": { "version": "3.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", "optional": true, "dependencies": { "decompress-response": "^4.2.0", @@ -42077,7 +42014,8 @@ }, "node_modules/simple-get/node_modules/decompress-response": { "version": "4.2.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "optional": true, "dependencies": { "mimic-response": "^2.0.0" @@ -42088,7 +42026,8 @@ }, "node_modules/simple-get/node_modules/mimic-response": { "version": "2.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", "optional": true, "engines": { "node": ">=8" @@ -42537,8 +42476,6 @@ }, "node_modules/spawn-command": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, "node_modules/spdx-correct": { @@ -44234,9 +44171,8 @@ }, "node_modules/ts-jest": { "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, + "license": "MIT", "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", @@ -44277,9 +44213,8 @@ }, "node_modules/ts-jest/node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -45340,6 +45275,14 @@ "version": "0.1.1", "license": "MIT" }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "license": "MIT", @@ -46625,8 +46568,7 @@ }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -46650,16 +46592,14 @@ }, "node_modules/yargs/node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs/node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { "node": ">=12" } diff --git a/package.json b/package.json index 9f1fbc894e1f..3b770debe4f0 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "react-content-loader": "^7.0.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "^1.0.6", + "react-fast-pdf": "https://github.com/ishpaul777/react-fast-pdf/tree/fix-blankArea-after-loading-pdf", "react-map-gl": "^7.1.3", "react-native": "0.73.2", "react-native-android-location-enabler": "^2.0.1", @@ -248,8 +248,8 @@ "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^4.0.0", - "copy-webpack-plugin": "^10.1.0", "concurrently": "^8.2.2", + "copy-webpack-plugin": "^10.1.0", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index c1ded9782a7a..500d14082be7 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import type AttachmentViewPdfProps from './types'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, containerStyles}: AttachmentViewPdfProps) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, containerStyles, onLoadError}: AttachmentViewPdfProps) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx index 23d39ec096b5..6130aeed866c 100644 --- a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx @@ -10,9 +10,13 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; type DefaultAttachmentViewProps = { + /** The name of the file */ fileName?: string; + /** Should show the download icon */ shouldShowDownloadIcon?: boolean; + /** Should show the loading spinner icon */ shouldShowLoadingSpinnerIcon?: boolean; + /** Additional styles for the container */ containerStyles?: StyleProp; }; @@ -55,4 +59,6 @@ function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = fa ); } +DefaultAttachmentView.displayName = 'DefaultAttachmentView'; + export default DefaultAttachmentView; diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx index 57c2d7dc955e..bd3082db5fa4 100644 --- a/src/components/FullscreenLoadingIndicator.tsx +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -6,14 +6,13 @@ import useThemeStyles from '@hooks/useThemeStyles'; type FullScreenLoadingIndicatorProps = { style?: StyleProp; - isFullScreen?: boolean; }; -function FullScreenLoadingIndicator({style, isFullScreen = true}: FullScreenLoadingIndicatorProps) { +function FullScreenLoadingIndicator({style}: FullScreenLoadingIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); return ( - + + + + ); + } + + return ; + } + renderPDFView() { const styles = this.props.themeStyles; const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; - const loadingIndicatorStyles = this.props.isUsedAsChatAttachment - ? [ - this.props.themeStyles.chatItemPDFAttachmentLoading, - this.props.StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT), - styles.justifyContentCenter, - styles.alignItemsCenter, - ] - : []; return ( - } - ErrorComponent={ - - } + LoadingComponent={this.renderLoadingIndicator()} + shouldShowErrorComponent={false} + onLoadError={this.props.onLoadError} renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( Date: Tue, 2 Apr 2024 19:14:04 +0100 Subject: [PATCH 0076/1525] make isDisabled prop optional --- src/pages/home/report/ThreadDivider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ThreadDivider.tsx b/src/pages/home/report/ThreadDivider.tsx index 2ac2d6449f67..a6830eea3de2 100644 --- a/src/pages/home/report/ThreadDivider.tsx +++ b/src/pages/home/report/ThreadDivider.tsx @@ -18,10 +18,10 @@ type ThreadDividerProps = { ancestor: Ancestor; /** Whether the link is disabled */ - isDisabled: boolean; + isDisabled?: boolean; }; -function ThreadDivider({ancestor, isDisabled}: ThreadDividerProps) { +function ThreadDivider({ancestor, isDisabled = false}: ThreadDividerProps) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); From 9321b99225a23576a0501e8106647d941ad06009 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 4 Apr 2024 00:33:07 +0700 Subject: [PATCH 0077/1525] add solution --- src/libs/actions/Report.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index a27f92ef8f57..852624381501 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -90,6 +90,7 @@ import * as CachedPDFPaths from './CachedPDFPaths'; import * as Modal from './Modal'; import * as Session from './Session'; import * as Welcome from './Welcome'; +import getDraftComment from '@libs/ComposerUtils/getDraftComment'; type SubscriberCallback = (isFromCurrentUser: boolean, reportActionID: string | undefined) => void; @@ -1116,6 +1117,8 @@ function handleReportChanged(report: OnyxEntry) { // In this case, the API will let us know by returning a preexistingReportID. // We should clear out the optimistically created report and re-route the user to the preexisting report. if (report?.reportID && report.preexistingReportID) { + const draftComment = getDraftComment(report.reportID); + saveReportComment(report.preexistingReportID, draftComment ?? ''); Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, null); // Only re-route them if they are still looking at the optimistically created report From bc4b3654f192fa4e94211cf4807c2c27c8b7a3d1 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Thu, 4 Apr 2024 18:43:16 +0100 Subject: [PATCH 0078/1525] Remove not allowed cursor on hover for the top-most message when the user don't have access to the parent thread --- src/pages/home/report/ReportActionItem.tsx | 6 ------ src/pages/home/report/ReportActionItemParentAction.tsx | 7 +++++-- src/styles/utils/index.ts | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a0acfe686c16..7c7ddd5e9efd 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -145,9 +145,6 @@ type ReportActionItemProps = { /** Callback to be called on onPress */ onPress?: () => void; - - /** Should press be disabled */ - isDisabled?: boolean; } & ReportActionItemOnyxProps; const isIOUReport = (actionObj: OnyxEntry): actionObj is OnyxTypes.ReportActionBase & OnyxTypes.OriginalMessageIOU => @@ -172,7 +169,6 @@ function ReportActionItem({ policy, transaction, onPress = undefined, - isDisabled = false, }: ReportActionItemProps) { const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -835,7 +831,6 @@ function ReportActionItem({ isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} @@ -869,7 +864,6 @@ function ReportActionItem({ style={StyleUtils.getReportActionItemStyle( hovered || isWhisper || isContextMenuActive || !!isEmojiPickerActive || draftMessage !== undefined, draftMessage === undefined && !!onPress, - isDisabled, )} > Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID ?? ''))} - isDisabled={!ReportUtils.canCurrentUserOpenReport(ReportUtils.getReport(ancestor?.report?.parentReportID) as OnyxTypes.Report)} + onPress={ + ReportUtils.canCurrentUserOpenReport(ReportUtils.getReport(ancestor?.report?.parentReportID) as OnyxTypes.Report) + ? () => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID ?? '')) + : undefined + } parentReportAction={parentReportAction} report={ancestor.report} reportActions={reportActions} diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 1ef2298392ae..329cc4fbdf85 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1451,7 +1451,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ /** * Generate the styles for the ReportActionItem wrapper view. */ - getReportActionItemStyle: (isHovered = false, isClickable = false, isDisabled = false): ViewStyle => + getReportActionItemStyle: (isHovered = false, isClickable = false): ViewStyle => // TODO: Remove this "eslint-disable-next" once the theme switching migration is done and styles are fully typed (GH Issue: https://github.com/Expensify/App/issues/27337) // eslint-disable-next-line @typescript-eslint/no-unsafe-return ({ @@ -1463,7 +1463,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ theme.transparent, opacity: 1, // eslint-disable-next-line no-nested-ternary - ...(isDisabled ? styles.cursorDisabled : isClickable ? styles.cursorPointer : styles.cursorInitial), + ...(isClickable ? styles.cursorPointer : styles.cursorInitial), }), /** From 623345d9680499039df73712ac5cbae177ecdd31 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:25:05 +0100 Subject: [PATCH 0079/1525] Remove not allowed cursor on hover for the 'Thread' link for when it is disabled (use default cursor) --- .../report/ReportActionItemParentAction.tsx | 2 +- src/pages/home/report/ThreadDivider.tsx | 45 ++++++++++++------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index f2117cb57191..d4119e738eac 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -103,7 +103,7 @@ function ReportActionItemParentAction({ > - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor?.report?.parentReportID ?? ''))} - accessibilityLabel={translate('threads.thread')} - role={CONST.ROLE.BUTTON} - style={[styles.flexRow, styles.alignItemsCenter, styles.gap1]} - disabled={isDisabled} - > - - {translate('threads.thread')} - + {isLinkDisabled ? ( + <> + + {translate('threads.thread')} + + ) : ( + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor?.report?.parentReportID ?? ''))} + accessibilityLabel={translate('threads.thread')} + role={CONST.ROLE.BUTTON} + style={[styles.flexRow, styles.alignItemsCenter, styles.gap1]} + > + + {translate('threads.thread')} + + )} {!ancestor.shouldDisplayNewMarker && } ); From 7b007e769a31f71f8c14638c822437e2d74d53a6 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 4 Apr 2024 23:39:14 +0300 Subject: [PATCH 0080/1525] Created attachment offline indicator component --- src/components/AttachmentOfflineIndicator.tsx | 59 +++++++++++++++++++ src/components/VideoPlayer/BaseVideoPlayer.js | 11 ++-- src/components/VideoPlayerPreview/index.tsx | 1 + 3 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/components/AttachmentOfflineIndicator.tsx diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx new file mode 100644 index 000000000000..476af1dcd31c --- /dev/null +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -0,0 +1,59 @@ +import React, {useMemo} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import variables from '@styles/variables'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import Text from './Text'; + +type AttachmentOfflineIndicatorProps = { + /** Optional styles for container element that will override the default styling for the offline indicator */ + containerStyles?: StyleProp; + + /** Optional styles for the container */ + style?: StyleProp; +}; + +function AttachmentOfflineIndicator({containerStyles, title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {isOffline} = useNetwork(); + const {isSmallScreenWidth} = useWindowDimensions(); + + const computedStyles = useMemo((): StyleProp => { + if (containerStyles) { + return containerStyles; + } + + return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; + }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); + + if (!isOffline) { + return null; + } + + return ( + + + {!isPreview && ( + + {title} + {subtitle} + + )} + + ); +} + +AttachmentOfflineIndicator.displayName = 'OfflineIndicator'; + +export default AttachmentOfflineIndicator; diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index d1533b2bf487..2cbd22e173d5 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -3,17 +3,15 @@ import {Video, VideoFullscreenUpdate} from 'expo-av'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import BlockingView from '@components/BlockingViews/BlockingView'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Hoverable from '@components/Hoverable'; -import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -44,6 +42,7 @@ function BaseVideoPlayer({ // user hovers the mouse over the carousel arrows, but this UI bug feels much less troublesome for now. // eslint-disable-next-line no-unused-vars isVideoHovered, + isPreview, }) { const styles = useThemeStyles(); const { @@ -78,7 +77,6 @@ function BaseVideoPlayer({ const isUploading = _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); const {translate} = useLocalize(); - const theme = useTheme(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; @@ -285,11 +283,10 @@ function BaseVideoPlayer({ {((isLoading && !isOffline) || isBuffering) && } {isLoading && isOffline && ( - )} diff --git a/src/components/VideoPlayerPreview/index.tsx b/src/components/VideoPlayerPreview/index.tsx index 37ddacb1f0db..dcbbbc900074 100644 --- a/src/components/VideoPlayerPreview/index.tsx +++ b/src/components/VideoPlayerPreview/index.tsx @@ -83,6 +83,7 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions, videoDuration={videoDuration} shouldUseSmallVideoControls style={[styles.w100, styles.h100]} + isPreview /> Date: Fri, 5 Apr 2024 20:51:27 +0300 Subject: [PATCH 0081/1525] did code cleanup --- src/components/AttachmentOfflineIndicator.tsx | 30 +++++++------------ src/components/VideoPlayer/types.ts | 1 + 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index 476af1dcd31c..76830abb42e8 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -1,36 +1,28 @@ -import React, {useMemo} from 'react'; -import type {StyleProp, ViewStyle} from 'react-native'; +import React from 'react'; import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; type AttachmentOfflineIndicatorProps = { - /** Optional styles for container element that will override the default styling for the offline indicator */ - containerStyles?: StyleProp; + /** Whether the offline indicator is displayed for the attachment preview. */ + isPreview?: boolean; - /** Optional styles for the container */ - style?: StyleProp; + /** Title text to be displayed. */ + title: string; + + /** Subtitle text to be displayed. */ + subtitle: string; }; -function AttachmentOfflineIndicator({containerStyles, title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { +function AttachmentOfflineIndicator({title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const {isSmallScreenWidth} = useWindowDimensions(); - - const computedStyles = useMemo((): StyleProp => { - if (containerStyles) { - return containerStyles; - } - - return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; - }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); if (!isOffline) { return null; @@ -39,7 +31,7 @@ function AttachmentOfflineIndicator({containerStyles, title, subtitle, isPreview return ( ; shouldPlay?: boolean; + isPreview?: boolean; }; export type {VideoPlayerProps, VideoWithOnFullScreenUpdate}; From e6153f2fb460b2e7fc6843cea89e158d15c3c07c Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 20:59:44 +0300 Subject: [PATCH 0082/1525] fix lint and type --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 0d6635db1e77..e01f2c7a2c8b 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -19,7 +19,7 @@ import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import CONST from '@src/CONST'; import shouldReplayVideo from './shouldReplayVideo'; -import type {VideoWithOnFullScreenUpdate} from './types'; +import type {VideoPlayerProps, VideoWithOnFullScreenUpdate} from './types'; import * as VideoUtils from './utils'; import VideoPlayerControls from './VideoPlayerControls'; @@ -44,9 +44,8 @@ function BaseVideoPlayer({ // but current workaround is just not to use it here for now. This causes not displaying the video controls when // user hovers the mouse over the carousel arrows, but this UI bug feels much less troublesome for now. // eslint-disable-next-line no-unused-vars - isVideoHovered = false, isPreview, -}) { +}: VideoPlayerProps) { const styles = useThemeStyles(); const {pauseVideo, playVideo, currentlyPlayingURL, sharedElement, originalParent, shareVideoPlayerElements, currentVideoPlayerRef, updateCurrentlyPlayingURL, videoResumeTryNumber} = usePlaybackContext(); From cbf52fe51b31d4d29b88e21095e97d4df865a077 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 21:01:30 +0300 Subject: [PATCH 0083/1525] revert unnecessary change --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index e01f2c7a2c8b..b771e3fc61be 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -43,7 +43,8 @@ function BaseVideoPlayer({ // isVideoHovered caused a bug with unexpected video switching. We are investigating the root cause of the issue, // but current workaround is just not to use it here for now. This causes not displaying the video controls when // user hovers the mouse over the carousel arrows, but this UI bug feels much less troublesome for now. - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isVideoHovered = false, isPreview, }: VideoPlayerProps) { const styles = useThemeStyles(); From b3f14d666b65e008f0d207a9ab0f28d5b11d45dd Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 21:04:52 +0300 Subject: [PATCH 0084/1525] removed unnecessary view --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index b771e3fc61be..e7eb78b12ae0 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -308,13 +308,11 @@ function BaseVideoPlayer({ {((isLoading && !isOffline) || isBuffering) && } {isLoading && isOffline && ( - - - + )} {controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( Date: Fri, 5 Apr 2024 22:06:54 +0300 Subject: [PATCH 0085/1525] implemented for image view --- src/components/AttachmentOfflineIndicator.tsx | 14 +++++--------- src/components/ImageView/index.tsx | 6 +++++- src/components/VideoPlayer/BaseVideoPlayer.tsx | 8 +------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index 76830abb42e8..b22478330bff 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -11,18 +12,13 @@ import Text from './Text'; type AttachmentOfflineIndicatorProps = { /** Whether the offline indicator is displayed for the attachment preview. */ isPreview?: boolean; - - /** Title text to be displayed. */ - title: string; - - /** Subtitle text to be displayed. */ - subtitle: string; }; -function AttachmentOfflineIndicator({title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { +function AttachmentOfflineIndicator({isPreview = false}: AttachmentOfflineIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); + const {translate} = useLocalize(); if (!isOffline) { return null; @@ -38,8 +34,8 @@ function AttachmentOfflineIndicator({title, subtitle, isPreview = false}: Attach /> {!isPreview && ( - {title} - {subtitle} + {translate('common.youAppearToBeOffline')} + {translate('common.attachementWillBeAvailableOnceBackOnline')} )} diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 5d09e7abf41d..2316577158f8 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -2,11 +2,13 @@ import type {SyntheticEvent} from 'react'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import type {GestureResponderEvent, LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Image from '@components/Image'; import RESIZE_MODES from '@components/Image/resizeModes'; import type {ImageOnLoadEvent} from '@components/Image/types'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -33,6 +35,7 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV const [imgHeight, setImgHeight] = useState(0); const [zoomScale, setZoomScale] = useState(0); const [zoomDelta, setZoomDelta] = useState(); + const {isOffline} = useNetwork(); const scrollableRef = useRef(null); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); @@ -243,7 +246,8 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV /> - {isLoading && } + {isLoading && !isOffline && } + {isLoading && } ); } diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index e7eb78b12ae0..10d5051c5234 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -307,13 +307,7 @@ function BaseVideoPlayer({ )} {((isLoading && !isOffline) || isBuffering) && } - {isLoading && isOffline && ( - - )} + {isLoading && } {controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( Date: Fri, 5 Apr 2024 22:42:34 +0300 Subject: [PATCH 0086/1525] implemented for thumbnail preview --- src/components/AttachmentOfflineIndicator.tsx | 2 +- src/components/ImageWithSizeCalculation.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index b22478330bff..8a831b02b407 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -25,7 +25,7 @@ function AttachmentOfflineIndicator({isPreview = false}: AttachmentOfflineIndica } return ( - + - {isLoading && !isImageCached && } + {isLoading && !isImageCached && !isOffline && } + {isLoading && !isImageCached && } ); } From da534453d52c1fb2993b2e31451503bfedd83bd3 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 23:01:48 +0300 Subject: [PATCH 0087/1525] minor fix --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 10d5051c5234..08036890d35c 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -70,7 +70,6 @@ function BaseVideoPlayer({ const isCurrentlyURLSet = currentlyPlayingURL === url; const isUploading = CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); - const {translate} = useLocalize(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; From e714b2fea5cf1b0475c5f78f031ca2786553522b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 01:51:43 +0530 Subject: [PATCH 0088/1525] resolve conflicts --- src/components/PDFView/index.tsx | 48 +++++++++++++++++++++++--------- src/components/PDFView/types.ts | 9 ++++-- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index ff28a8f88849..df6543290b96 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -1,30 +1,36 @@ import 'core-js/features/array/at'; // eslint-disable-next-line no-restricted-imports -import type {CSSProperties} from 'react'; -import React, {memo, useCallback, useEffect, useState} from 'react'; -import {PDFPreviewer} from 'react-fast-pdf'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import type { CSSProperties } from 'react'; +import React, { memo, useCallback, useEffect, useState } from 'react'; +import { PDFPreviewer } from 'react-fast-pdf'; +import { ActivityIndicator, View } from 'react-native'; +import { withOnyx } from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; +import useTheme from '@hooks/useTheme'; +import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; import * as CanvasSize from '@userActions/CanvasSize'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import PDFPasswordForm from './PDFPasswordForm'; -import type {PDFViewOnyxProps, PDFViewProps} from './types'; +import type { PDFViewOnyxProps, PDFViewProps } from './types'; -function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, errorLabelStyles, maxCanvasArea, maxCanvasHeight, maxCanvasWidth, style}: PDFViewProps) { +const LOADING_THUMBNAIL_HEIGHT = 250; +const LOADING_THUMBNAIL_WIDTH = 250; + +function PDFView({ onToggleKeyboard, fileName, onPress, isFocused, sourceURL, maxCanvasArea, maxCanvasHeight, maxCanvasWidth, style, isUsedAsChatAttachment, onLoadError }: PDFViewProps) { const [isKeyboardOpen, setIsKeyboardOpen] = useState(false); const styles = useThemeStyles(); - const {windowHeight, isSmallScreenWidth} = useWindowDimensions(); + const StyleUtils = useStyleUtils(); + const theme = useTheme(); + const { windowHeight, isSmallScreenWidth } = useWindowDimensions(); const prevWindowHeight = usePrevious(windowHeight); - const {translate} = useLocalize(); + const { translate } = useLocalize(); /** * On small screens notify parent that the keyboard has opened or closed. @@ -79,6 +85,21 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, err } }, [isKeyboardOpen, prevWindowHeight, toggleKeyboardOnSmallScreens, windowHeight]); + const renderLoadingIndicator = () => { + if (isUsedAsChatAttachment) { + return ( + + + + ); + } + + return ; + } + const renderPDFView = () => { const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; @@ -95,9 +116,10 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, err maxCanvasWidth={maxCanvasWidth} maxCanvasHeight={maxCanvasHeight} maxCanvasArea={maxCanvasArea} - LoadingComponent={} - ErrorComponent={{translate('attachmentView.failedToLoadPDF')}} - renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( + LoadingComponent={renderLoadingIndicator()} + shouldShowErrorComponent={false} + onLoadError={onLoadError} + renderPasswordForm={({ isPasswordInvalid, onSubmit, onPasswordChange }) => ( ; + /** Callback to call when the PDF fails to load */ + onLoadError?: () => void; + + /** Whether the PDF is used as a chat attachment */ + isUsedAsChatAttachment?: boolean; }; type PDFViewOnyxProps = { From 15eea966597d1465650cfe61dd3dec2face0d9f5 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 01:52:50 +0530 Subject: [PATCH 0089/1525] code formatting --- .../AttachmentViewPdf/index.tsx | 13 ++++++- src/components/PDFView/index.native.tsx | 5 +-- src/components/PDFView/index.tsx | 35 +++++++++++-------- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 9984779557eb..ba108092ab8f 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,7 +2,18 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import type AttachmentViewPdfProps from './types'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, containerStyles, onLoadError}: AttachmentViewPdfProps) { +function AttachmentViewPdf({ + file, + encryptedSourceUrl, + isFocused, + onPress, + onToggleKeyboard, + onLoadComplete, + style, + isUsedAsChatAttachment, + containerStyles, + onLoadError, +}: AttachmentViewPdfProps) { return ( { if (isUsedAsChatAttachment) { return ( - + ; - } + }; const renderPDFView = () => { const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; @@ -119,7 +126,7 @@ function PDFView({ onToggleKeyboard, fileName, onPress, isFocused, sourceURL, ma LoadingComponent={renderLoadingIndicator()} shouldShowErrorComponent={false} onLoadError={onLoadError} - renderPasswordForm={({ isPasswordInvalid, onSubmit, onPasswordChange }) => ( + renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( Date: Sun, 7 Apr 2024 02:16:30 +0530 Subject: [PATCH 0090/1525] remove unnecessary change --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79f0a58cc10d..5e00815f1e3e 100644 --- a/package.json +++ b/package.json @@ -248,8 +248,8 @@ "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^4.0.0", - "concurrently": "^8.2.2", "copy-webpack-plugin": "^10.1.0", + "concurrently": "^8.2.2", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", From 69129794b08fe9d66175d41bb0c35d9083965562 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:21:23 +0530 Subject: [PATCH 0091/1525] fix type checks --- .../Attachments/AttachmentView/AttachmentViewPdf/index.tsx | 2 -- .../Attachments/AttachmentView/AttachmentViewPdf/types.ts | 3 --- src/components/Attachments/AttachmentView/index.tsx | 1 - 3 files changed, 6 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index ba108092ab8f..3cae1ee45f1b 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -11,7 +11,6 @@ function AttachmentViewPdf({ onLoadComplete, style, isUsedAsChatAttachment, - containerStyles, onLoadError, }: AttachmentViewPdfProps) { return ( @@ -24,7 +23,6 @@ function AttachmentViewPdf({ onToggleKeyboard={onToggleKeyboard} onLoadComplete={onLoadComplete} isUsedAsChatAttachment={isUsedAsChatAttachment} - containerStyles={containerStyles} onLoadError={onLoadError} /> ); diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index 1c3cd8adfba4..c20593449d58 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -16,9 +16,6 @@ type AttachmentViewPdfProps = Pick; }; export default AttachmentViewPdfProps; diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index f32265bdc01f..bc8f7476b4d5 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -177,7 +177,6 @@ function AttachmentView({ onLoadComplete={onPDFLoadComplete} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedAsChatAttachment={isUsedAsChatAttachment} - containerStyles={containerStyles} onLoadError={() => { setIsPdfFailedToLoad(true); }} From 917095c5f6f29feb6c5b9d1bc29d56acd2cc9c06 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:23:07 +0530 Subject: [PATCH 0092/1525] lint code --- src/components/Attachments/AttachmentView/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index bc8f7476b4d5..1743a1904ad3 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -163,6 +163,10 @@ function AttachmentView({ } }; + const onPDFLoadError = () => { + setIsPdfFailedToLoad(true); + } + // We need the following View component on android native // So that the event will propagate properly and // the Password protected preview will be shown for pdf attachement we are about to send. @@ -177,9 +181,7 @@ function AttachmentView({ onLoadComplete={onPDFLoadComplete} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedAsChatAttachment={isUsedAsChatAttachment} - onLoadError={() => { - setIsPdfFailedToLoad(true); - }} + onLoadError={onPDFLoadError} /> ); From c9681471de4445131308598d7c644f8ffd9c7b1d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:24:22 +0530 Subject: [PATCH 0093/1525] prettier diffs --- .../AttachmentView/AttachmentViewPdf/index.tsx | 12 +----------- src/components/Attachments/AttachmentView/index.tsx | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 3cae1ee45f1b..e8f6568f98c0 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,17 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import type AttachmentViewPdfProps from './types'; -function AttachmentViewPdf({ - file, - encryptedSourceUrl, - isFocused, - onPress, - onToggleKeyboard, - onLoadComplete, - style, - isUsedAsChatAttachment, - onLoadError, -}: AttachmentViewPdfProps) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, onLoadError}: AttachmentViewPdfProps) { return ( { setIsPdfFailedToLoad(true); - } + }; // We need the following View component on android native // So that the event will propagate properly and From cadf1f1dee2058299d5ed4e792b75ebb7fbaac6d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:32:19 +0530 Subject: [PATCH 0094/1525] resolve lint failure --- src/components/PDFView/index.native.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PDFView/index.native.tsx b/src/components/PDFView/index.native.tsx index 0fb063f57e8b..db76c005b37a 100644 --- a/src/components/PDFView/index.native.tsx +++ b/src/components/PDFView/index.native.tsx @@ -127,9 +127,9 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused if (shouldRequestPassword) { pdfStyles.push(themeStyles.invisible); } - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const containerStyles = - isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + (isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth)) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] : []; From 54eb29517e53a484d21a7a9a5a37d2bfdf91f05c Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:39:32 +0530 Subject: [PATCH 0095/1525] prettier diffs --- src/components/PDFView/index.native.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PDFView/index.native.tsx b/src/components/PDFView/index.native.tsx index db76c005b37a..7d523ef13805 100644 --- a/src/components/PDFView/index.native.tsx +++ b/src/components/PDFView/index.native.tsx @@ -129,7 +129,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused } const containerStyles = // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth)) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; + isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] : []; From 0f7ecdb40060b838e11d2c906f5545d523729b1d Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:52:59 +0100 Subject: [PATCH 0096/1525] remove comment --- src/styles/utils/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 329cc4fbdf85..a3357b8982a1 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1462,7 +1462,6 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ : // Warning: Setting this to a non-transparent color will cause unread indicator to break on Android theme.transparent, opacity: 1, - // eslint-disable-next-line no-nested-ternary ...(isClickable ? styles.cursorPointer : styles.cursorInitial), }), From 2a69717b6ce7afd5350777351018c84c588bbe64 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 20:27:35 +0300 Subject: [PATCH 0097/1525] implemented offline indicator for native --- src/components/Lightbox/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 86a52c2baf6c..e5261771c5a4 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,12 +2,14 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -40,6 +42,7 @@ type LightboxProps = { function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChangedProp, onError, style, zoomRange = DEFAULT_ZOOM_RANGE}: LightboxProps) { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); + const {isOffline} = useNetwork(); /** * React hooks must be used in the render function of the component at top-level and unconditionally. @@ -243,12 +246,13 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan )} {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && ( + {isLoading && !isOffline && ( )} + {isLoading && } )} From bf1786e5a387a9663f3f11cfee6290bb7bc58ac6 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 20:32:31 +0300 Subject: [PATCH 0098/1525] minor fix --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 08036890d35c..2ddb19716c8e 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -12,7 +12,6 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; -import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; From 395468dc259e680429e64971479f72a40f2431a3 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 9 Apr 2024 11:39:38 +0200 Subject: [PATCH 0099/1525] receipt scan ui change wip --- assets/images/receipt-scan.svg | 14 ++++++++++++++ src/components/Icon/Expensicons.ts | 2 ++ .../MoneyRequestPreviewContent.tsx | 5 +++++ src/languages/en.ts | 3 ++- src/languages/es.ts | 1 + src/pages/home/report/ReportActionsList.tsx | 2 ++ 6 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 assets/images/receipt-scan.svg diff --git a/assets/images/receipt-scan.svg b/assets/images/receipt-scan.svg new file mode 100644 index 000000000000..c93986de3c9b --- /dev/null +++ b/assets/images/receipt-scan.svg @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 1fcf0d07276c..ba00ad684473 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -120,6 +120,7 @@ import Printer from '@assets/images/printer.svg'; import Profile from '@assets/images/profile.svg'; import QrCode from '@assets/images/qrcode.svg'; import QuestionMark from '@assets/images/question-mark-circle.svg'; +import ReceiptScan from '@assets/images/receipt-scan.svg'; import ReceiptSearch from '@assets/images/receipt-search.svg'; import Receipt from '@assets/images/receipt.svg'; import RemoveMembers from '@assets/images/remove-members.svg'; @@ -283,6 +284,7 @@ export { QrCode, QuestionMark, Receipt, + ReceiptScan, RemoveMembers, ReceiptSearch, Rotate, diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 2c6f14cec4c2..8d1b7880726b 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -312,6 +312,11 @@ function MoneyRequestPreviewContent({ )} + + {true && ( + {translate('iou.receiptScanInProgress')} + )} + diff --git a/src/languages/en.ts b/src/languages/en.ts index 55a4c586716a..ad5377d8349b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -632,7 +632,8 @@ export default { posted: 'Posted', deleteReceipt: 'Delete receipt', routePending: 'Route pending...', - receiptScanning: 'Scan in progress…', + receiptScanning: 'Receipt scanning…', + receiptScanInProgress: 'Receipt scan in progress.', receiptMissingDetails: 'Receipt missing details', missingAmount: 'Missing amount', missingMerchant: 'Missing merchant', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5956f1457005..61c6c82bbc1c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -629,6 +629,7 @@ export default { deleteReceipt: 'Eliminar recibo', routePending: 'Ruta pendiente...', receiptScanning: 'Escaneo en curso…', + receiptScanInProgress: 'Escaneo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', missingMerchant: 'Falta comerciante', diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index d1b9c420b0af..e261f1f3c38e 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -6,6 +6,7 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're import {DeviceEventEmitter, InteractionManager} from 'react-native'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import InvertedFlatList from '@components/InvertedFlatList'; import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList'; @@ -184,6 +185,7 @@ function ReportActionsList({ const hasFooterRendered = useRef(false); const lastVisibleActionCreatedRef = useRef(report.lastVisibleActionCreated); const lastReadTimeRef = useRef(report.lastReadTime); + Onyx.merge('transactions_8811441407757684730', {cardID: 1, merchant: 'Google', hasEReceipt: true, status: 'Pending'}); const sortedVisibleReportActions = useMemo( () => From 2a38baacb0f59c0a4349b4118adbb9edda97fea2 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:52:15 +0100 Subject: [PATCH 0100/1525] Fix lint error --- src/pages/home/report/ReportActionItemParentAction.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index d4119e738eac..afbeb734e2b6 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -103,11 +103,11 @@ function ReportActionItemParentAction({ > Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID ?? '')) : undefined } From 2e1f4ce29cca3c94151a02cc2f58a244517c25d2 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 9 Apr 2024 22:20:26 +0200 Subject: [PATCH 0101/1525] scanning receipt wip --- src/components/MoneyRequestHeader.tsx | 17 +++++++++++++++++ src/components/MoneyRequestHeaderStatusBar.tsx | 3 ++- .../MoneyRequestPreviewContent.tsx | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f451f5f15581..ac6cfd911a6a 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -19,9 +19,13 @@ import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import HoldBanner from './HoldBanner'; +import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; +import {ReceiptScan} from './Icon/Expensicons'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu'; +import variables from '@styles/variables'; +import theme from '@styles/theme'; type MoneyRequestHeaderOnyxProps = { /** Session info for the currently logged in user. */ @@ -216,6 +220,19 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, ); } +function ScanningReceiptHeaderTitle() { + return ( + + + + ); +} + MoneyRequestHeader.displayName = 'MoneyRequestHeader'; const MoneyRequestHeaderWithTransaction = withOnyx>({ diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index 59ef4ee0bd26..828650fa8fba 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -1,3 +1,4 @@ +import type {ReactElement} from 'react'; import React from 'react'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -5,7 +6,7 @@ import Text from './Text'; type MoneyRequestHeaderStatusBarProps = { /** Title displayed in badge */ - title: string; + title: string | ReactElement; /** Banner Description */ description: string; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 8d1b7880726b..745c13ce9d0d 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -7,6 +7,7 @@ import type {GestureResponderEvent} from 'react-native'; import ConfirmedRoute from '@components/ConfirmedRoute'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import {ReceiptScan} from '@components/Icon/Expensicons'; import MoneyRequestSkeletonView from '@components/MoneyRequestSkeletonView'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -30,6 +31,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; +import variables from '@styles/variables'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; @@ -312,11 +314,17 @@ function MoneyRequestPreviewContent({ )} - - {true && ( + {isScanning && ( + + {translate('iou.receiptScanInProgress')} - )} - + + )} From 9ee70da12544f7017ed8d70e1c04b833600edfa4 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 08:15:15 +0200 Subject: [PATCH 0102/1525] finalize scanning receipt --- src/components/MoneyRequestHeader.tsx | 29 ++++++++----------- .../MoneyRequestHeaderStatusBar.tsx | 14 +++++---- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index ac6cfd911a6a..2f80cf3c6e59 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as HeaderUtils from '@libs/HeaderUtils'; @@ -10,6 +11,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; +import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -24,8 +26,6 @@ import * as Expensicons from './Icon/Expensicons'; import {ReceiptScan} from './Icon/Expensicons'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu'; -import variables from '@styles/variables'; -import theme from '@styles/theme'; type MoneyRequestHeaderOnyxProps = { /** Session info for the currently logged in user. */ @@ -58,6 +58,7 @@ type MoneyRequestHeaderProps = MoneyRequestHeaderOnyxProps & { function MoneyRequestHeader({session, parentReport, report, parentReportAction, transaction, shownHoldUseExplanation = false, policy}: MoneyRequestHeaderProps) { const styles = useThemeStyles(); + const theme = useTheme(); const {translate} = useLocalize(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false); @@ -192,8 +193,15 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, )} {isScanning && ( + } + description={translate('iou.receiptScanInProgressDescription')} shouldShowBorderBottom /> )} @@ -220,19 +228,6 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, ); } -function ScanningReceiptHeaderTitle() { - return ( - - - - ); -} - MoneyRequestHeader.displayName = 'MoneyRequestHeader'; const MoneyRequestHeaderWithTransaction = withOnyx>({ diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index 828650fa8fba..d21e66ba39eb 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -1,4 +1,4 @@ -import type {ReactElement} from 'react'; +import type {ReactNode} from 'react'; import React from 'react'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -6,7 +6,7 @@ import Text from './Text'; type MoneyRequestHeaderStatusBarProps = { /** Title displayed in badge */ - title: string | ReactElement; + title: string | ReactNode; /** Banner Description */ description: string; @@ -20,9 +20,13 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom const borderBottomStyle = shouldShowBorderBottom ? styles.borderBottom : {}; return ( - - {title} - + {typeof title === 'string' ? ( + + {title} + + ) : ( + {title} + )} {description} diff --git a/src/languages/en.ts b/src/languages/en.ts index ad5377d8349b..c7ea86ff8064 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -634,6 +634,7 @@ export default { routePending: 'Route pending...', receiptScanning: 'Receipt scanning…', receiptScanInProgress: 'Receipt scan in progress.', + receiptScanInProgressDescription: 'Receipt scan in progress. Check back later or enter the details now.', receiptMissingDetails: 'Receipt missing details', missingAmount: 'Missing amount', missingMerchant: 'Missing merchant', diff --git a/src/languages/es.ts b/src/languages/es.ts index 61c6c82bbc1c..c9aabe62087a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -630,6 +630,7 @@ export default { routePending: 'Ruta pendiente...', receiptScanning: 'Escaneo en curso…', receiptScanInProgress: 'Escaneo en curso…', + receiptScanInProgressDescription: 'Escaneo en curso.', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', missingMerchant: 'Falta comerciante', From 2a7fac59e5fde23d38751b0732de483d13c4da43 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 10 Apr 2024 14:32:00 +0300 Subject: [PATCH 0103/1525] revert light box change --- src/components/Lightbox/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index e5261771c5a4..86a52c2baf6c 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,14 +2,12 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; -import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; -import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -42,7 +40,6 @@ type LightboxProps = { function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChangedProp, onError, style, zoomRange = DEFAULT_ZOOM_RANGE}: LightboxProps) { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); - const {isOffline} = useNetwork(); /** * React hooks must be used in the render function of the component at top-level and unconditionally. @@ -246,13 +243,12 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan )} {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && !isOffline && ( + {isLoading && ( )} - {isLoading && } )} From bbfda4d6a9292afc983458922a22a40a543fbd70 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 10 Apr 2024 14:53:25 +0300 Subject: [PATCH 0104/1525] fix buffering case --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 2ddb19716c8e..26a44364f807 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -305,7 +305,7 @@ function BaseVideoPlayer({ )} {((isLoading && !isOffline) || isBuffering) && } - {isLoading && } + {isLoading && !isBuffering && } {controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( Date: Wed, 10 Apr 2024 17:06:49 +0200 Subject: [PATCH 0105/1525] Add useActiveWorkspaceFromNavigationState --- .../useActiveWorkspaceFromNavigationState.ts | 13 ++++ src/libs/Navigation/types.ts | 4 +- src/libs/PolicyUtils.ts | 68 ++++++++----------- src/pages/home/sidebar/SidebarLinksData.tsx | 4 +- .../SidebarScreen/BaseSidebarScreen.tsx | 4 +- 5 files changed, 48 insertions(+), 45 deletions(-) create mode 100644 src/hooks/useActiveWorkspaceFromNavigationState.ts diff --git a/src/hooks/useActiveWorkspaceFromNavigationState.ts b/src/hooks/useActiveWorkspaceFromNavigationState.ts new file mode 100644 index 000000000000..8f9da3d9df37 --- /dev/null +++ b/src/hooks/useActiveWorkspaceFromNavigationState.ts @@ -0,0 +1,13 @@ +import {useNavigationState} from '@react-navigation/native'; +import type {BottomTabNavigatorParamList} from '@libs/Navigation/types'; + +/** + * Get the currently selected policy ID stored in the navigation state. This hook should only be called only from screens in BottomTab. + */ +function useActiveWorkspaceFromNavigationState() { + const activeWorkpsaceID = useNavigationState((state) => state.routes.at(-1)?.params?.policyID); + + return activeWorkpsaceID; +} + +export default useActiveWorkspaceFromNavigationState; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 1f1e7ec9a459..04bb7797804e 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -662,8 +662,8 @@ type WelcomeVideoModalNavigatorParamList = { }; type BottomTabNavigatorParamList = { - [SCREENS.HOME]: undefined; - [SCREENS.SETTINGS.ROOT]: undefined; + [SCREENS.HOME]: {policyID?: string}; + [SCREENS.SETTINGS.ROOT]: {policyID?: string}; }; type SharedScreensParamList = { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 665830ca7167..6c5e4ea695c2 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -8,9 +8,7 @@ import type {PersonalDetailsList, Policy, PolicyCategories, PolicyMembers, Polic import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import getPolicyIDFromState from './Navigation/getPolicyIDFromState'; -import Navigation, {navigationRef} from './Navigation/Navigation'; -import type {RootStackParamList, State} from './Navigation/types'; +import Navigation from './Navigation/Navigation'; type MemberEmailsToAccountIDs = Record; @@ -310,51 +308,43 @@ function isPolicyFeatureEnabled(policy: OnyxEntry | EmptyObject, feature return Boolean(policy?.[featureName]); } -/** - * Get the currently selected policy ID stored in the navigation state. - */ -function getPolicyIDFromNavigationState() { - return getPolicyIDFromState(navigationRef.getRootState() as State); -} - export { + canEditTaxRate, + extractPolicyIDFromPath, getActivePolicies, + getCleanedTagName, + getCountOfEnabledTagsOfList, + getIneligibleInvitees, + getMemberAccountIDsForWorkspace, + getNumericValue, + getPathWithoutPolicyID, + getPolicyBrickRoadIndicatorStatus, + getPolicyMembersByIdWithoutCurrentUser, + getSortedTagKeys, + getTagList, + getTagListName, + getTagLists, + getTaxByID, + getUnitRateValue, + goBackFromInvalidPolicy, hasAccountingConnections, - hasPolicyMemberError, + hasCustomUnitsError, + hasPolicyCategoriesError, hasPolicyError, hasPolicyErrorFields, - hasCustomUnitsError, - getNumericValue, - getUnitRateValue, - getPolicyBrickRoadIndicatorStatus, - shouldShowPolicy, + hasPolicyMemberError, + hasTaxRateError, isExpensifyTeam, - isInstantSubmitEnabled, isFreeGroupPolicy, - isPolicyAdmin, - isTaxTrackingEnabled, - isSubmitAndClose, - getMemberAccountIDsForWorkspace, - getIneligibleInvitees, - getTagLists, - getTagListName, - getSortedTagKeys, - canEditTaxRate, - getTagList, - getCleanedTagName, - getCountOfEnabledTagsOfList, - isPendingDeletePolicy, - isPolicyMember, + isInstantSubmitEnabled, isPaidGroupPolicy, - extractPolicyIDFromPath, - getPathWithoutPolicyID, - getPolicyMembersByIdWithoutCurrentUser, - goBackFromInvalidPolicy, + isPendingDeletePolicy, + isPolicyAdmin, isPolicyFeatureEnabled, - hasTaxRateError, - getTaxByID, - hasPolicyCategoriesError, - getPolicyIDFromNavigationState, + isPolicyMember, + isSubmitAndClose, + isTaxTrackingEnabled, + shouldShowPolicy, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index e56962a331a2..4a90d6e96239 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -9,7 +9,7 @@ import type {EdgeInsets} from 'react-native-safe-area-context'; import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import withCurrentReportID from '@components/withCurrentReportID'; -import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useActiveWorkspaceFromNavigationState from '@hooks/useActiveWorkspaceFromNavigationState'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -85,7 +85,7 @@ function SidebarLinksData({ const network = useNetwork(); const isFocused = useIsFocused(); const styles = useThemeStyles(); - const {activeWorkspaceID} = useActiveWorkspace(); + const activeWorkspaceID = useActiveWorkspaceFromNavigationState(); const {translate} = useLocalize(); const prevPriorityMode = usePrevious(priorityMode); const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, accountID); diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index fe61af021d7f..a8df74cfc4e9 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -1,11 +1,11 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import ScreenWrapper from '@components/ScreenWrapper'; +import useActiveWorkspaceFromNavigationState from '@hooks/useActiveWorkspaceFromNavigationState'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Browser from '@libs/Browser'; import TopBar from '@libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; import Performance from '@libs/Performance'; -import {getPolicyIDFromNavigationState} from '@libs/PolicyUtils'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; @@ -20,7 +20,7 @@ const startTimer = () => { function BaseSidebarScreen() { const styles = useThemeStyles(); - const activeWorkspaceID = getPolicyIDFromNavigationState(); + const activeWorkspaceID = useActiveWorkspaceFromNavigationState(); useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); Timing.start(CONST.TIMING.SIDEBAR_LOADED, true); From 8f22291f1a8ea5b6b2953cf13e7a17202502f6ce Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 17:18:46 +0200 Subject: [PATCH 0106/1525] finalize pending transaction, cleanup wip --- assets/images/credit-card-hourglass.svg | 19 +++++++++++++++ src/components/Icon/Expensicons.ts | 2 ++ src/components/MoneyRequestHeader.tsx | 14 +++++++---- .../MoneyRequestPreviewContent.tsx | 10 -------- .../ReportActionItem/MoneyRequestView.tsx | 5 ---- .../ReportActionItem/ReportPreview.tsx | 14 +++++++++++ src/languages/en.ts | 3 ++- src/libs/CardUtils.ts | 2 +- src/pages/home/ReportScreen.tsx | 23 ++++++++++--------- src/pages/home/report/ReportActionItem.tsx | 1 + src/pages/home/report/ReportActionsList.tsx | 2 +- 11 files changed, 62 insertions(+), 33 deletions(-) create mode 100644 assets/images/credit-card-hourglass.svg diff --git a/assets/images/credit-card-hourglass.svg b/assets/images/credit-card-hourglass.svg new file mode 100644 index 000000000000..2acd013fbe59 --- /dev/null +++ b/assets/images/credit-card-hourglass.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index ba00ad684473..78583f3af4d4 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -41,6 +41,7 @@ import Collapse from '@assets/images/collapse.svg'; import Concierge from '@assets/images/concierge.svg'; import Connect from '@assets/images/connect.svg'; import Copy from '@assets/images/copy.svg'; +import CreditCardHourglass from '@assets/images/credit-card-hourglass.svg'; import CreditCard from '@assets/images/creditcard.svg'; import DocumentPlus from '@assets/images/document-plus.svg'; import DocumentSlash from '@assets/images/document-slash.svg'; @@ -198,6 +199,7 @@ export { Connect, Copy, CreditCard, + CreditCardHourglass, DeletedRoomAvatar, Document, DocumentSlash, diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 2f80cf3c6e59..5ea46f339d46 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -23,7 +23,6 @@ import HeaderWithBackButton from './HeaderWithBackButton'; import HoldBanner from './HoldBanner'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import {ReceiptScan} from './Icon/Expensicons'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu'; @@ -186,8 +185,15 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, /> {isPending && ( + } + description={translate('iou.transactionPendingDescription')} shouldShowBorderBottom={!isScanning} /> )} @@ -195,7 +201,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, 0; const isScanning = hasReceipts && areAllRequestsBeingSmartScanned; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const hasErrors = hasMissingSmartscanFields || (canUseViolations && ReportUtils.hasViolations(iouReportID, transactionViolations)) || ReportUtils.hasActionsWithErrors(iouReportID); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); @@ -226,6 +228,7 @@ function ReportPreview({ const shouldShowSingleRequestMerchantOrDescription = numberOfRequests === 1 && (!!formattedMerchant || !!formattedDescription) && !(hasOnlyTransactionsWithPendingRoutes && !totalDisplaySpend); const shouldShowSubtitle = !isScanning && (shouldShowSingleRequestMerchantOrDescription || numberOfRequests > 1); + const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && transactionsWithReceipts.length === 1; const {isSupportTextHtml, supportText} = useMemo(() => { if (formattedMerchant) { @@ -318,6 +321,17 @@ function ReportPreview({ )} + {shouldShowPendingSubtitle && ( + + + {translate('iou.transactionPending')} + + )} {shouldShowSettlementButton && ( diff --git a/src/languages/en.ts b/src/languages/en.ts index c7ea86ff8064..27637aeb9602 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -641,7 +641,7 @@ export default { receiptStatusTitle: 'Scanning…', receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.", receiptScanningFailed: 'Receipt scanning failed. Enter the details manually.', - transactionPendingText: 'It takes a few days from the date the card was used for the transaction to post.', + transactionPendingDescription: 'Transaction pending. It can take a few days from the date the card was used for the transaction to post.', requestCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('request', 'requests', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pending` : '' @@ -737,6 +737,7 @@ export default { set: 'set', changed: 'changed', removed: 'removed', + transactionPending: 'Transaction pending.', }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index c4d67adcd54a..0d8de86f63bc 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -46,7 +46,7 @@ function isExpensifyCard(cardID?: number) { * @returns boolean if the cardID is in the cardList from ONYX. Includes Expensify Cards. */ function isCorporateCard(cardID: number) { - return !!allCards[cardID]; + return true; } /** diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 940cba181db7..e8c00f807e06 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -323,7 +323,7 @@ function ReportScreen({ /> ); - if (isSingleTransactionView) { + if (true) { headerView = ( ReportActionsUtils.getOneTransactionThreadReportID(reportActions ?? []), [reportActions]); - if (ReportUtils.isMoneyRequestReport(report)) { - headerView = ( - - ); - } + // if (ReportUtils.isMoneyRequestReport(report)) { + // headerView = ( + // + // ); + // } /** * When false the ReportActionsView will completely unmount and we will show a loader until it returns true. @@ -603,6 +603,7 @@ function ReportScreen({ ); } + console.log('REPORT SCREEN'); return ( diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index eeeb5b95273c..02d139780ed0 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -832,6 +832,7 @@ function ReportActionItem({ ? (Object.values(personalDetails ?? {}).filter((details) => whisperedToAccountIDs.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) : []; const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; + return ( @@ -196,6 +195,7 @@ function ReportActionsList({ ), [sortedReportActions, isOffline], ); + const lastActionIndex = sortedVisibleReportActions[0]?.reportActionID; const reportActionSize = useRef(sortedVisibleReportActions.length); const hasNewestReportAction = sortedReportActions?.[0].created === report.lastVisibleActionCreated; From efe88ce911f70c4d74fe90d08c42cb17c7b9b0c9 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 17:31:16 +0200 Subject: [PATCH 0107/1525] cleanup --- src/languages/es.ts | 5 +++-- src/pages/home/report/ReportActionsList.tsx | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index c9aabe62087a..a6a19d2bf35c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -630,14 +630,14 @@ export default { routePending: 'Ruta pendiente...', receiptScanning: 'Escaneo en curso…', receiptScanInProgress: 'Escaneo en curso…', - receiptScanInProgressDescription: 'Escaneo en curso.', + receiptScanInProgressDescription: ' Escaneando recibo. Vuelva a comprobarlo más tarde o introduzca los detalles ahora.', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', missingMerchant: 'Falta comerciante', receiptStatusTitle: 'Escaneando…', receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.', - transactionPendingText: 'La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.', + transactionPendingDescription: 'La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.', requestCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('solicitude', 'solicitudes', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : '' @@ -735,6 +735,7 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', + transactionPending: 'Transaction pending.', }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 0068ed875b82..60a620f186cc 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -6,7 +6,6 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're import {DeviceEventEmitter, InteractionManager} from 'react-native'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import InvertedFlatList from '@components/InvertedFlatList'; import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList'; From c3cd931cfaccaece3414103983483b7683c8f04f Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 10 Apr 2024 17:39:17 +0200 Subject: [PATCH 0108/1525] Remove redundant SCREENS.SETTINGS.ROOT route --- src/libs/Navigation/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 04bb7797804e..bffe333900e5 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -71,7 +71,6 @@ type BackToParams = { }; type SettingsNavigatorParamList = { - [SCREENS.SETTINGS.ROOT]: undefined; [SCREENS.SETTINGS.SHARE_CODE]: undefined; [SCREENS.SETTINGS.PROFILE.ROOT]: undefined; [SCREENS.SETTINGS.PROFILE.PRONOUNS]: undefined; From 9a664e5d4db4201533fe1d9c9180b6fe4bdf81a9 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 17:43:25 +0200 Subject: [PATCH 0109/1525] cleanup --- src/pages/home/ReportScreen.tsx | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index e8c00f807e06..940cba181db7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -323,7 +323,7 @@ function ReportScreen({ /> ); - if (true) { + if (isSingleTransactionView) { headerView = ( ReportActionsUtils.getOneTransactionThreadReportID(reportActions ?? []), [reportActions]); - // if (ReportUtils.isMoneyRequestReport(report)) { - // headerView = ( - // - // ); - // } + if (ReportUtils.isMoneyRequestReport(report)) { + headerView = ( + + ); + } /** * When false the ReportActionsView will completely unmount and we will show a loader until it returns true. @@ -603,7 +603,6 @@ function ReportScreen({ ); } - console.log('REPORT SCREEN'); return ( From 945872ab306ed01b352caf10a3a3aea01d6cc087 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 10 Apr 2024 17:47:50 +0200 Subject: [PATCH 0110/1525] Handle mocking useActiveWorkspaceFromNavigationState in tests --- tests/unit/SidebarOrderTest.ts | 1 + tests/unit/SidebarTest.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 2758d43fb81e..ac74fb50245b 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -14,6 +14,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch jest.mock('@libs/Permissions'); jest.mock('@hooks/usePermissions.ts'); jest.mock('@components/Icon/Expensicons'); +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState') const ONYXKEYS = { PERSONAL_DETAILS_LIST: 'personalDetailsList', diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts index 23ea0d377634..ba2d950232b5 100644 --- a/tests/unit/SidebarTest.ts +++ b/tests/unit/SidebarTest.ts @@ -12,6 +12,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch // Be sure to include the mocked Permissions and Expensicons libraries as well as the usePermissions hook or else the beta tests won't work jest.mock('@src/libs/Permissions'); jest.mock('@src/hooks/usePermissions.ts'); +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState') jest.mock('@src/components/Icon/Expensicons'); describe('Sidebar', () => { From 3be474286b2de7510841a1e2d80baf45f203ba4f Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 10 Apr 2024 17:55:50 +0200 Subject: [PATCH 0111/1525] Run prettier --- tests/unit/SidebarOrderTest.ts | 2 +- tests/unit/SidebarTest.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index ac74fb50245b..e0858c4e78b9 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -14,7 +14,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch jest.mock('@libs/Permissions'); jest.mock('@hooks/usePermissions.ts'); jest.mock('@components/Icon/Expensicons'); -jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState') +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState'); const ONYXKEYS = { PERSONAL_DETAILS_LIST: 'personalDetailsList', diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts index ba2d950232b5..e35a479c1add 100644 --- a/tests/unit/SidebarTest.ts +++ b/tests/unit/SidebarTest.ts @@ -12,7 +12,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch // Be sure to include the mocked Permissions and Expensicons libraries as well as the usePermissions hook or else the beta tests won't work jest.mock('@src/libs/Permissions'); jest.mock('@src/hooks/usePermissions.ts'); -jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState') +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState'); jest.mock('@src/components/Icon/Expensicons'); describe('Sidebar', () => { From d315efb2d08469d717e820189c2d4a388976cfee Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 17:57:36 +0200 Subject: [PATCH 0112/1525] cleanup --- src/libs/CardUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 0d8de86f63bc..c4d67adcd54a 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -46,7 +46,7 @@ function isExpensifyCard(cardID?: number) { * @returns boolean if the cardID is in the cardList from ONYX. Includes Expensify Cards. */ function isCorporateCard(cardID: number) { - return true; + return !!allCards[cardID]; } /** From 4e08e070c709b6e8350d3cbe05221a8b56098ac0 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 11 Apr 2024 00:26:01 +0200 Subject: [PATCH 0113/1525] cleanup translations --- src/languages/es.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index a6a19d2bf35c..877d745fa59c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -628,16 +628,16 @@ export default { posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', routePending: 'Ruta pendiente...', - receiptScanning: 'Escaneo en curso…', - receiptScanInProgress: 'Escaneo en curso…', - receiptScanInProgressDescription: ' Escaneando recibo. Vuelva a comprobarlo más tarde o introduzca los detalles ahora.', + receiptScanning: 'Escaneando recibo…', + receiptScanInProgress: 'Escaneo en curso.', + receiptScanInProgressDescription: 'Escaneando recibo. Vuelva a comprobarlo más tarde o introduzca los detalles ahora.', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', missingMerchant: 'Falta comerciante', receiptStatusTitle: 'Escaneando…', receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.', - transactionPendingDescription: 'La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.', + transactionPendingDescription: 'Transacción pendiente. Esto puede tardar algunos días en registrarse a partir de la fecha en que se utilizó la tarjeta.', requestCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('solicitude', 'solicitudes', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : '' @@ -735,7 +735,7 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', - transactionPending: 'Transaction pending.', + transactionPending: 'Transacción pendiente.', }, notificationPreferencesPage: { header: 'Preferencias de avisos', From 648e333f6178eb2b4699d024936d4ef281aacd7d Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 11 Apr 2024 00:41:33 +0200 Subject: [PATCH 0114/1525] fix preview --- .../MoneyRequestPreviewContent.tsx | 12 ++++++++++++ src/pages/home/report/ReportActionsList.tsx | 1 + 2 files changed, 13 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index b0363135e273..e149891b0365 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -87,6 +87,7 @@ function MoneyRequestPreviewContent({ const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const hasReceipt = TransactionUtils.hasReceipt(transaction); const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); + const isPending = TransactionUtils.isPending(transaction); const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = Boolean(iouReport?.pendingFields?.partial); const isPartialHold = isSettlementOrApprovalPartial && isOnHold; @@ -315,6 +316,17 @@ function MoneyRequestPreviewContent({ {translate('iou.receiptScanInProgress')} )} + {isPending && ( + + + {translate('iou.transactionPending')} + + )} diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 60a620f186cc..0068ed875b82 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -6,6 +6,7 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're import {DeviceEventEmitter, InteractionManager} from 'react-native'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import InvertedFlatList from '@components/InvertedFlatList'; import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList'; From 6cb699dd7a02320971348b2c636e96852f2c5bb8 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 11 Apr 2024 14:05:43 +0300 Subject: [PATCH 0115/1525] fix touch screen case for image view --- src/components/ImageView/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 2316577158f8..9865adb04d3d 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -213,7 +213,8 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV onLoad={imageLoad} onError={onError} /> - {(isLoading || zoomScale === 0) && } + {((isLoading && !isOffline) || zoomScale === 0) && } + {isLoading && } ); } From cbea4acce2f432b71940929f52b6d99c2daa1b22 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 11 Apr 2024 14:17:38 +0300 Subject: [PATCH 0116/1525] fix logic for zoom scale --- src/components/ImageView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 9865adb04d3d..f08941ef7d77 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -213,7 +213,7 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV onLoad={imageLoad} onError={onError} /> - {((isLoading && !isOffline) || zoomScale === 0) && } + {((isLoading && !isOffline) || (!isLoading && zoomScale === 0)) && } {isLoading && } ); From a3c565ae84371cb37342c62946603356a138551c Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 11 Apr 2024 14:03:48 +0200 Subject: [PATCH 0117/1525] Fix sidebar links perf tests --- tests/perf-test/SidebarLinks.perf-test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/perf-test/SidebarLinks.perf-test.tsx b/tests/perf-test/SidebarLinks.perf-test.tsx index 2848015d5c63..036d4ea84ff9 100644 --- a/tests/perf-test/SidebarLinks.perf-test.tsx +++ b/tests/perf-test/SidebarLinks.perf-test.tsx @@ -10,6 +10,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch jest.mock('@libs/Permissions'); jest.mock('@hooks/usePermissions.ts'); +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState'); jest.mock('@libs/Navigation/Navigation'); jest.mock('@components/Icon/Expensicons'); From f2bcf10e8e12bef7242a95adbab46f0a3352e04c Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 11 Apr 2024 15:57:59 +0200 Subject: [PATCH 0118/1525] wip --- ios/Podfile.lock | 2 +- src/components/MoneyRequestHeader.tsx | 2 ++ .../MoneyRequestPreviewContent.tsx | 1 + .../ReportActionItem/ReportPreview.tsx | 14 +++++++++++++- src/libs/ReportUtils.ts | 1 + src/pages/home/ReportScreen.tsx | 2 ++ src/pages/home/report/ReportActionsList.tsx | 19 +++++++++++++++++++ 7 files changed, 39 insertions(+), 2 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 32a8bca75bcd..94bd6e35f31d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1921,7 +1921,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 9f26224fce1233ffdad9fa4e56863e3de2190dc0 - Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 + Yoga: 13c8ef87792450193e117976337b8527b49e8c03 PODFILE CHECKSUM: a431c146e1501391834a2f299a74093bac53b530 diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 5ea46f339d46..909b6e6f71f6 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -165,6 +165,8 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, }); } + console.log('MONEY REQUEST HEADER'); + return ( <> diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index e149891b0365..2a56e2f2405d 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -67,6 +67,7 @@ function MoneyRequestPreviewContent({ const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); const parser = new ExpensiMark(); + console.warn('TRANSACTION ', transaction); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? -1; const ownerAccountID = iouReport?.ownerAccountID ?? -1; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 40c9f1afcc21..5b9c2269028c 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -228,7 +228,8 @@ function ReportPreview({ const shouldShowSingleRequestMerchantOrDescription = numberOfRequests === 1 && (!!formattedMerchant || !!formattedDescription) && !(hasOnlyTransactionsWithPendingRoutes && !totalDisplaySpend); const shouldShowSubtitle = !isScanning && (shouldShowSingleRequestMerchantOrDescription || numberOfRequests > 1); - const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && transactionsWithReceipts.length === 1; + const shouldShowScanningSubtitle = numberOfScanningReceipts === 1 && allTransactions.length === 1; + const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && allTransactions.length === 1; const {isSupportTextHtml, supportText} = useMemo(() => { if (formattedMerchant) { @@ -321,6 +322,17 @@ function ReportPreview({ )} + {shouldShowScanningSubtitle && ( + + + {translate('iou.receiptScanInProgress')} + + )} {shouldShowPendingSubtitle && ( ): boolean { */ function isMoneyRequest(reportOrID: OnyxEntry | string): boolean { const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID; + console.log('REPORT ', report); return isIOURequest(report) || isExpenseRequest(report); } diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 940cba181db7..de02342ff414 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -323,6 +323,8 @@ function ReportScreen({ /> ); + console.warn('ReportUtils.isMoneyRequest(report) ', ReportUtils.isMoneyRequest(report)); + if (isSingleTransactionView) { headerView = ( { const unsubscriber = Visibility.onVisibilityChange(() => { setIsVisible(Visibility.isVisible()); From c3285b8e4baf6fc2cf64dd0cc17dd3c3fb10c6ec Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 11 Apr 2024 16:18:56 +0200 Subject: [PATCH 0119/1525] cleanup --- src/components/MoneyRequestHeader.tsx | 2 -- .../MoneyRequestPreviewContent.tsx | 1 - src/libs/ReportUtils.ts | 1 - src/pages/home/ReportScreen.tsx | 2 -- src/pages/home/report/ReportActionsList.tsx | 20 ------------------- 5 files changed, 26 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 909b6e6f71f6..5ea46f339d46 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -165,8 +165,6 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, }); } - console.log('MONEY REQUEST HEADER'); - return ( <> diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 2a56e2f2405d..e149891b0365 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -67,7 +67,6 @@ function MoneyRequestPreviewContent({ const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); const parser = new ExpensiMark(); - console.warn('TRANSACTION ', transaction); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? -1; const ownerAccountID = iouReport?.ownerAccountID ?? -1; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f03a5051d634..fec64efaac7f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1282,7 +1282,6 @@ function isTrackExpenseReport(report: OnyxEntry): boolean { */ function isMoneyRequest(reportOrID: OnyxEntry | string): boolean { const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID; - console.log('REPORT ', report); return isIOURequest(report) || isExpenseRequest(report); } diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index de02342ff414..940cba181db7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -323,8 +323,6 @@ function ReportScreen({ /> ); - console.warn('ReportUtils.isMoneyRequest(report) ', ReportUtils.isMoneyRequest(report)); - if (isSingleTransactionView) { headerView = ( { const unsubscriber = Visibility.onVisibilityChange(() => { setIsVisible(Visibility.isVisible()); From 9c1527be8952d96dbc1d758b08717b346833eb8d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 11 Apr 2024 22:34:06 +0530 Subject: [PATCH 0120/1525] remove unnecessary styles --- src/components/PDFView/index.tsx | 2 -- src/styles/index.ts | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index 2725e3c2a6aa..e63a4b5ac4c1 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -92,8 +92,6 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, max style={[ styles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT), - styles.alignItemsCenter, - styles.justifyContentCenter, ]} > borderColor: theme.border, borderWidth: 1, borderRadius: variables.componentBorderRadiusNormal, - textAlign: 'center', - verticalAlign: 'middle', - opacity: 1, + ...flex.alignItemsCenter, + ...flex.justifyContentCenter, }, sidebarVisible: { From bffd5f03956f13c18f4d49cb25fc5ecc8d64b241 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Apr 2024 11:24:10 +0700 Subject: [PATCH 0121/1525] implement internet reachability for android --- src/CONST.ts | 1 + src/libs/NetworkConnection.ts | 17 ++++++++++--- .../index.android.ts | 24 +++++++++++++++++++ src/libs/checkInternetReachability/index.ts | 5 ++++ src/libs/checkInternetReachability/types.ts | 3 +++ 5 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/libs/checkInternetReachability/index.android.ts create mode 100644 src/libs/checkInternetReachability/index.ts create mode 100644 src/libs/checkInternetReachability/types.ts diff --git a/src/CONST.ts b/src/CONST.ts index 0ce5458e0849..9e4e41a64c38 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -543,6 +543,7 @@ const CONST = { STATUS_EXPENSIFY_URL: 'https://status.expensify.com', GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com', GOOGLE_DOC_IMAGE_LINK_MATCH: 'googleusercontent.com', + GOOGLE_CLOUD_URL: 'https://clients3.google.com/generate_204', IMAGE_BASE64_MATCH: 'base64', DEEPLINK_BASE_URL: 'new-expensify://', PDF_VIEWER_URL: '/pdf/web/viewer.html', diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 4383a921662f..c2b348cc0f6f 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -6,6 +6,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as NetworkActions from './actions/Network'; import AppStateMonitor from './AppStateMonitor'; +import checkInternetReachability from './checkInternetReachability'; import Log from './Log'; let isOffline = false; @@ -69,10 +70,11 @@ Onyx.connect({ }); /** - * Set interval to periodically (re)check backend status + * Set interval to periodically (re)check backend status. + * Because backend unreachability might imply lost internet connection, we need to check internet reachability. * @returns clearInterval cleanup */ -function subscribeToBackendReachability(): () => void { +function subscribeToBackendAndInternetReachability(): () => void { const intervalID = setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { @@ -92,6 +94,15 @@ function subscribeToBackendReachability(): () => void { .then((json) => Promise.resolve(json.jsonCode === 200)) .catch(() => Promise.resolve(false)); }) + .then((isBackendReachable: boolean) => { + if (isBackendReachable) { + return Promise.resolve(true); + } + return checkInternetReachability().then((isInternetReachable: boolean) => { + NetworkActions.setIsOffline(!isInternetReachable); + return Promise.resolve(false); + }); + }) .then(NetworkActions.setIsBackendReachable) .catch(() => NetworkActions.setIsBackendReachable(false)); }, CONST.NETWORK.BACKEND_CHECK_INTERVAL_MS); @@ -108,7 +119,7 @@ function subscribeToBackendReachability(): () => void { function subscribeToNetworkStatus(): () => void { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for reachability. // If you need to test the "recheck" feature then switch to the production API proxy server. - const unsubscribeFromBackendReachability = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; + const unsubscribeFromBackendReachability = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendAndInternetReachability() : undefined; // Set up the event listener for NetInfo to tell whether the user has // internet connectivity or not. This is more reliable than the Pusher diff --git a/src/libs/checkInternetReachability/index.android.ts b/src/libs/checkInternetReachability/index.android.ts new file mode 100644 index 000000000000..2dfb015dc9ed --- /dev/null +++ b/src/libs/checkInternetReachability/index.android.ts @@ -0,0 +1,24 @@ +import CONST from '@src/CONST'; +import type InternetReachabilityCheck from './types'; + +/** + * Although Android supports internet reachability check, it only does on initiating the connection. + * We need to implement a test for a highly-available endpoint to cover the case internet is lost during connection. + */ +export default function checkInternetReachability(): InternetReachabilityCheck { + // Using the API url ensures reachability is tested over internet + return fetch(CONST.GOOGLE_CLOUD_URL, { + method: 'GET', + cache: 'no-cache', + }) + .then((response) => { + if (!response.ok) { + return Promise.resolve(false); + } + return response + .json() + .then((json) => Promise.resolve(json.jsonCode === 204)) + .catch(() => Promise.resolve(false)); + }) + .catch(() => Promise.resolve(false)); +} diff --git a/src/libs/checkInternetReachability/index.ts b/src/libs/checkInternetReachability/index.ts new file mode 100644 index 000000000000..d7d0808c6efc --- /dev/null +++ b/src/libs/checkInternetReachability/index.ts @@ -0,0 +1,5 @@ +import type InternetReachabilityCheck from './types'; + +export default function checkInternetReachability(): InternetReachabilityCheck { + return Promise.resolve(true); +} diff --git a/src/libs/checkInternetReachability/types.ts b/src/libs/checkInternetReachability/types.ts new file mode 100644 index 000000000000..3e1cb96cb26c --- /dev/null +++ b/src/libs/checkInternetReachability/types.ts @@ -0,0 +1,3 @@ +type InternetReachabilityCheck = Promise; + +export default InternetReachabilityCheck; From e3644d1acd2a3bba3ae94caa5a1e5ee2219ad280 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 08:22:14 +0200 Subject: [PATCH 0122/1525] Remove SCREENS.WORKSPACES_CENTRAL_PANE --- .../Navigators/FullScreenNavigator.tsx | 77 +++++++++++++++-- .../CustomFullScreenRouter.tsx | 20 +---- .../getTopmostWorkspacesCentralPaneName.ts | 17 +--- src/libs/Navigation/linkingConfig/config.ts | 82 +++++++++---------- .../linkingConfig/getAdaptedStateFromPath.ts | 6 +- src/libs/Navigation/types.ts | 56 ++++++++++++- 6 files changed, 172 insertions(+), 86 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 07b069462dd1..065e11495a01 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -5,7 +5,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import createCustomFullScreenNavigator from '@libs/Navigation/AppNavigator/createCustomFullScreenNavigator'; import getRootNavigatorScreenOptions from '@libs/Navigation/AppNavigator/getRootNavigatorScreenOptions'; -import * as ModalStackNavigators from '@libs/Navigation/AppNavigator/ModalStackNavigators'; import SCREENS from '@src/SCREENS'; const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; @@ -17,19 +16,85 @@ function FullScreenNavigator() { const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth, styles, StyleUtils); - return ( - + require('@pages/workspace/WorkspaceProfilePage').default as React.ComponentType} + /> + require('@pages/workspace/card/WorkspaceCardPage').default as React.ComponentType} + /> + require('@pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType} + /> + require('@pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType} + /> + require('@pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType} + /> + require('@pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType} + /> + require('@pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType} + /> + require('@pages/workspace/WorkspaceMembersPage').default as React.ComponentType} + /> + + require('@pages/workspace/accounting/WorkspaceAccountingPage').default as React.ComponentType} + /> + + require('@pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType} + /> + require('@pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType} + /> + require('@pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType} + /> + require('@pages/workspace/taxes/WorkspaceTaxesPage').default as React.ComponentType} + /> + require('@pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType} /> diff --git a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx index eb19f891ecd5..99e6fb23b8ba 100644 --- a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx @@ -11,11 +11,6 @@ const isAtLeastOneInState = (state: StackState, screenName: string): boolean => function adaptStateIfNecessary(state: StackState) { const isNarrowLayout = getIsNarrowLayout(); const workspaceCentralPane = state.routes.at(-1); - const topmostWorkspaceCentralPaneRoute = workspaceCentralPane?.state?.routes[0]; - - // When a screen from the FullScreenNavigator is opened from the deeplink then params should be passed to SCREENS.WORKSPACE.INITIAL from the variable defined below. - const workspacesCentralPaneParams = - workspaceCentralPane?.params && 'params' in workspaceCentralPane.params ? (workspaceCentralPane.params.params as Record) : undefined; // There should always be WORKSPACE.INITIAL screen in the state to make sure go back works properly if we deeplinkg to a subpage of settings. if (!isAtLeastOneInState(state, SCREENS.WORKSPACE.INITIAL)) { @@ -28,7 +23,7 @@ function adaptStateIfNecessary(state: StackState) { // Unshift the root screen to fill left pane. state.routes.unshift({ name: SCREENS.WORKSPACE.INITIAL, - params: topmostWorkspaceCentralPaneRoute?.params ?? workspacesCentralPaneParams, + params: workspaceCentralPane?.params, }); } } @@ -37,22 +32,15 @@ function adaptStateIfNecessary(state: StackState) { // - WORKSPACE.INITIAL to cover left pane. // - WORKSPACES_CENTRAL_PANE to cover central pane. if (!isNarrowLayout) { - if (!isAtLeastOneInState(state, SCREENS.WORKSPACES_CENTRAL_PANE)) { + if (state.routes.length === 1 && state.routes[0].name === SCREENS.WORKSPACE.INITIAL) { // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.stale = true; // eslint-disable-line // Push the default settings central pane screen. if (state.stale === true) { state.routes.push({ - name: SCREENS.WORKSPACES_CENTRAL_PANE, - state: { - routes: [ - { - name: SCREENS.WORKSPACE.PROFILE, - params: state.routes[0]?.params, - }, - ], - }, + name: SCREENS.WORKSPACE.PROFILE, + params: state.routes[0]?.params, }); } } diff --git a/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts b/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts index db11368c1345..ec3159ade059 100644 --- a/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts +++ b/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts @@ -1,5 +1,4 @@ import type {NavigationState, PartialState} from '@react-navigation/native'; -import SCREENS from '@src/SCREENS'; // Get the name of topmost report in the navigation stack. function getTopmostWorkspacesCentralPaneName(state: NavigationState | PartialState): string | undefined { @@ -7,21 +6,7 @@ function getTopmostWorkspacesCentralPaneName(state: NavigationState | PartialSta return; } - const topmostCentralPane = state.routes.filter((route) => typeof route !== 'number' && 'name' in route && route.name === SCREENS.WORKSPACES_CENTRAL_PANE).at(-1); - - if (!topmostCentralPane) { - return; - } - - if (!!topmostCentralPane.params && 'screen' in topmostCentralPane.params && typeof topmostCentralPane.params.screen === 'string') { - return topmostCentralPane.params.screen; - } - - if (!topmostCentralPane.state) { - return; - } - - return topmostCentralPane.state?.routes.at(-1)?.name; + return state.routes.at(-1)?.name } export default getTopmostWorkspacesCentralPaneName; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 95294b7711b5..468a5ce8c3d9 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -635,49 +635,45 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.INITIAL]: { path: ROUTES.WORKSPACE_INITIAL.route, }, - [SCREENS.WORKSPACES_CENTRAL_PANE]: { - screens: { - [SCREENS.WORKSPACE.PROFILE]: ROUTES.WORKSPACE_PROFILE.route, - [SCREENS.WORKSPACE.CARD]: { - path: ROUTES.WORKSPACE_CARD.route, - }, - [SCREENS.WORKSPACE.WORKFLOWS]: { - path: ROUTES.WORKSPACE_WORKFLOWS.route, - }, - [SCREENS.WORKSPACE.REIMBURSE]: { - path: ROUTES.WORKSPACE_REIMBURSE.route, - }, - [SCREENS.WORKSPACE.BILLS]: { - path: ROUTES.WORKSPACE_BILLS.route, - }, - [SCREENS.WORKSPACE.INVOICES]: { - path: ROUTES.WORKSPACE_INVOICES.route, - }, - [SCREENS.WORKSPACE.TRAVEL]: { - path: ROUTES.WORKSPACE_TRAVEL.route, - }, - [SCREENS.WORKSPACE.MEMBERS]: { - path: ROUTES.WORKSPACE_MEMBERS.route, - }, - [SCREENS.WORKSPACE.ACCOUNTING]: { - path: ROUTES.WORKSPACE_ACCOUNTING.route, - }, - [SCREENS.WORKSPACE.CATEGORIES]: { - path: ROUTES.WORKSPACE_CATEGORIES.route, - }, - [SCREENS.WORKSPACE.MORE_FEATURES]: { - path: ROUTES.WORKSPACE_MORE_FEATURES.route, - }, - [SCREENS.WORKSPACE.TAGS]: { - path: ROUTES.WORKSPACE_TAGS.route, - }, - [SCREENS.WORKSPACE.TAXES]: { - path: ROUTES.WORKSPACE_TAXES.route, - }, - [SCREENS.WORKSPACE.DISTANCE_RATES]: { - path: ROUTES.WORKSPACE_DISTANCE_RATES.route, - }, - }, + [SCREENS.WORKSPACE.PROFILE]: ROUTES.WORKSPACE_PROFILE.route, + [SCREENS.WORKSPACE.CARD]: { + path: ROUTES.WORKSPACE_CARD.route, + }, + [SCREENS.WORKSPACE.WORKFLOWS]: { + path: ROUTES.WORKSPACE_WORKFLOWS.route, + }, + [SCREENS.WORKSPACE.REIMBURSE]: { + path: ROUTES.WORKSPACE_REIMBURSE.route, + }, + [SCREENS.WORKSPACE.BILLS]: { + path: ROUTES.WORKSPACE_BILLS.route, + }, + [SCREENS.WORKSPACE.INVOICES]: { + path: ROUTES.WORKSPACE_INVOICES.route, + }, + [SCREENS.WORKSPACE.TRAVEL]: { + path: ROUTES.WORKSPACE_TRAVEL.route, + }, + [SCREENS.WORKSPACE.MEMBERS]: { + path: ROUTES.WORKSPACE_MEMBERS.route, + }, + [SCREENS.WORKSPACE.ACCOUNTING]: { + path: ROUTES.WORKSPACE_ACCOUNTING.route, + }, + [SCREENS.WORKSPACE.CATEGORIES]: { + path: ROUTES.WORKSPACE_CATEGORIES.route, + }, + [SCREENS.WORKSPACE.MORE_FEATURES]: { + path: ROUTES.WORKSPACE_MORE_FEATURES.route, + }, + [SCREENS.WORKSPACE.TAGS]: { + path: ROUTES.WORKSPACE_TAGS.route, + }, + [SCREENS.WORKSPACE.TAXES]: { + path: ROUTES.WORKSPACE_TAXES.route, + }, + [SCREENS.WORKSPACE.DISTANCE_RATES]: { + path: ROUTES.WORKSPACE_DISTANCE_RATES.route, }, }, }, diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index d7bcfbb68952..52e62604c0a5 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -86,11 +86,9 @@ function createFullScreenNavigator(route?: NavigationPartialRoute; + [SCREENS.WORKSPACE.PROFILE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.CARD]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS_APPROVER]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: { + policyID: string; + }; + [SCREENS.WORKSPACE.REIMBURSE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.BILLS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.INVOICES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.TRAVEL]: { + policyID: string; + }; + [SCREENS.WORKSPACE.MEMBERS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING]: { + policyID: string; + }; + [SCREENS.WORKSPACE.CATEGORIES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.MORE_FEATURES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.TAGS]: { + policyID: string; + tagName: string; + }; + [SCREENS.WORKSPACE.TAXES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.DISTANCE_RATES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING]: { + policyID: string; + }; }; type OnboardingModalNavigatorParamList = { From 4b1681c76db57613511ac7bbe4be900bbe7954fd Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 08:44:20 +0200 Subject: [PATCH 0123/1525] Add a comment to useActiveWorkspaceFromNavigationState --- src/hooks/useActiveWorkspaceFromNavigationState.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/useActiveWorkspaceFromNavigationState.ts b/src/hooks/useActiveWorkspaceFromNavigationState.ts index 8f9da3d9df37..63a6865a516b 100644 --- a/src/hooks/useActiveWorkspaceFromNavigationState.ts +++ b/src/hooks/useActiveWorkspaceFromNavigationState.ts @@ -5,6 +5,7 @@ import type {BottomTabNavigatorParamList} from '@libs/Navigation/types'; * Get the currently selected policy ID stored in the navigation state. This hook should only be called only from screens in BottomTab. */ function useActiveWorkspaceFromNavigationState() { + // The last policyID value is always stored in the last route in BottomTabNavigator. const activeWorkpsaceID = useNavigationState((state) => state.routes.at(-1)?.params?.policyID); return activeWorkpsaceID; From 01aafb06eecf79d97444b3e57d41905136723a65 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Apr 2024 14:58:36 +0700 Subject: [PATCH 0124/1525] modify fetch logic --- src/libs/NetworkConnection.ts | 17 +++++++++++------ .../checkInternetReachability/index.android.ts | 10 +--------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index c2b348cc0f6f..86253b0cca78 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -96,15 +96,20 @@ function subscribeToBackendAndInternetReachability(): () => void { }) .then((isBackendReachable: boolean) => { if (isBackendReachable) { - return Promise.resolve(true); + NetworkActions.setIsBackendReachable(true); + return; } - return checkInternetReachability().then((isInternetReachable: boolean) => { - NetworkActions.setIsOffline(!isInternetReachable); - return Promise.resolve(false); + checkInternetReachability().then((isInternetReachable: boolean) => { + setOfflineStatus(!isInternetReachable); + NetworkActions.setIsBackendReachable(false); }); }) - .then(NetworkActions.setIsBackendReachable) - .catch(() => NetworkActions.setIsBackendReachable(false)); + .catch(() => { + checkInternetReachability().then((isInternetReachable: boolean) => { + setOfflineStatus(!isInternetReachable); + NetworkActions.setIsBackendReachable(false); + }); + }); }, CONST.NETWORK.BACKEND_CHECK_INTERVAL_MS); return () => { diff --git a/src/libs/checkInternetReachability/index.android.ts b/src/libs/checkInternetReachability/index.android.ts index 2dfb015dc9ed..9e1a0170d3e4 100644 --- a/src/libs/checkInternetReachability/index.android.ts +++ b/src/libs/checkInternetReachability/index.android.ts @@ -11,14 +11,6 @@ export default function checkInternetReachability(): InternetReachabilityCheck { method: 'GET', cache: 'no-cache', }) - .then((response) => { - if (!response.ok) { - return Promise.resolve(false); - } - return response - .json() - .then((json) => Promise.resolve(json.jsonCode === 204)) - .catch(() => Promise.resolve(false)); - }) + .then((response) => Promise.resolve(response.status === 204)) .catch(() => Promise.resolve(false)); } From c851a49ecc1b83c3bc642b331d866c78d48804b8 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Apr 2024 15:08:23 +0700 Subject: [PATCH 0125/1525] modify comment --- src/libs/checkInternetReachability/index.android.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/checkInternetReachability/index.android.ts b/src/libs/checkInternetReachability/index.android.ts index 9e1a0170d3e4..da46623c3e05 100644 --- a/src/libs/checkInternetReachability/index.android.ts +++ b/src/libs/checkInternetReachability/index.android.ts @@ -3,10 +3,9 @@ import type InternetReachabilityCheck from './types'; /** * Although Android supports internet reachability check, it only does on initiating the connection. - * We need to implement a test for a highly-available endpoint to cover the case internet is lost during connection. + * We need to implement a test for a highly-available endpoint in case of lost internet after initiation. */ export default function checkInternetReachability(): InternetReachabilityCheck { - // Using the API url ensures reachability is tested over internet return fetch(CONST.GOOGLE_CLOUD_URL, { method: 'GET', cache: 'no-cache', From bc8db3bb0d71df4415b80ebe1a79da7368f084cd Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 11:22:09 +0200 Subject: [PATCH 0126/1525] Refactor FullScreenNavigator --- .../Navigators/FullScreenNavigator.tsx | 99 +++++-------------- .../CustomFullScreenRouter.tsx | 2 +- src/libs/Navigation/getTopmostRouteName.ts | 12 +++ .../getTopmostWorkspacesCentralPaneName.ts | 12 --- src/pages/workspace/WorkspaceInitialPage.tsx | 4 +- 5 files changed, 42 insertions(+), 87 deletions(-) create mode 100644 src/libs/Navigation/getTopmostRouteName.ts delete mode 100644 src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 065e11495a01..42bca6e2de4c 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -5,12 +5,32 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import createCustomFullScreenNavigator from '@libs/Navigation/AppNavigator/createCustomFullScreenNavigator'; import getRootNavigatorScreenOptions from '@libs/Navigation/AppNavigator/getRootNavigatorScreenOptions'; +import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; const RootStack = createCustomFullScreenNavigator(); +type Screens = Partial React.ComponentType>>; + +const workspacesScreens = { + [SCREENS.WORKSPACE.PROFILE]: () => require('../../../../pages/workspace/WorkspaceProfilePage').default as React.ComponentType, + [SCREENS.WORKSPACE.CARD]: () => require('../../../../pages/workspace/card/WorkspaceCardPage').default as React.ComponentType, + [SCREENS.WORKSPACE.WORKFLOWS]: () => require('../../../../pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.REIMBURSE]: () => require('../../../../pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType, + [SCREENS.WORKSPACE.BILLS]: () => require('../../../../pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.INVOICES]: () => require('../../../../pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TRAVEL]: () => require('../../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType, + [SCREENS.WORKSPACE.MEMBERS]: () => require('../../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType, + [SCREENS.WORKSPACE.ACCOUNTING]: () => require('../../../../pages/workspace/accounting/WorkspaceAccountingPage').default as React.ComponentType, + [SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.MORE_FEATURES]: () => require('../../../../pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAGS]: () => require('../../../../pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAXES]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType, +} satisfies Screens; + function FullScreenNavigator() { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -24,78 +44,13 @@ function FullScreenNavigator() { options={screenOptions.homeScreen} getComponent={loadWorkspaceInitialPage} /> - require('@pages/workspace/WorkspaceProfilePage').default as React.ComponentType} - /> - require('@pages/workspace/card/WorkspaceCardPage').default as React.ComponentType} - /> - require('@pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType} - /> - require('@pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType} - /> - require('@pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType} - /> - require('@pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType} - /> - require('@pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType} - /> - require('@pages/workspace/WorkspaceMembersPage').default as React.ComponentType} - /> - - require('@pages/workspace/accounting/WorkspaceAccountingPage').default as React.ComponentType} - /> - - require('@pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType} - /> - require('@pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType} - /> - require('@pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType} - /> - require('@pages/workspace/taxes/WorkspaceTaxesPage').default as React.ComponentType} - /> - require('@pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType} - /> + {Object.entries(workspacesScreens).map(([screenName, componentGetter]) => ( + + ))} ); diff --git a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx index 99e6fb23b8ba..27e976d9be0c 100644 --- a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx @@ -30,7 +30,7 @@ function adaptStateIfNecessary(state: StackState) { // If the screen is wide, there should be at least two screens inside: // - WORKSPACE.INITIAL to cover left pane. - // - WORKSPACES_CENTRAL_PANE to cover central pane. + // - WORKSPACE.PROFILE (first workspace settings screen) to cover central pane. if (!isNarrowLayout) { if (state.routes.length === 1 && state.routes[0].name === SCREENS.WORKSPACE.INITIAL) { // @ts-expect-error Updating read only property diff --git a/src/libs/Navigation/getTopmostRouteName.ts b/src/libs/Navigation/getTopmostRouteName.ts new file mode 100644 index 000000000000..7ae3afaf2cc9 --- /dev/null +++ b/src/libs/Navigation/getTopmostRouteName.ts @@ -0,0 +1,12 @@ +import type {NavigationState, PartialState} from '@react-navigation/native'; + +// Get the name of topmost route in the navigation stack. +function getTopmostRouteName(state: NavigationState | PartialState): string | undefined { + if (!state) { + return; + } + + return state.routes.at(-1)?.name; +} + +export default getTopmostRouteName; diff --git a/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts b/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts deleted file mode 100644 index ec3159ade059..000000000000 --- a/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type {NavigationState, PartialState} from '@react-navigation/native'; - -// Get the name of topmost report in the navigation stack. -function getTopmostWorkspacesCentralPaneName(state: NavigationState | PartialState): string | undefined { - if (!state) { - return; - } - - return state.routes.at(-1)?.name -} - -export default getTopmostWorkspacesCentralPaneName; diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index a6a131f5372c..f1c215447e4e 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -19,7 +19,7 @@ import usePrevious from '@hooks/usePrevious'; import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; -import getTopmostWorkspacesCentralPaneName from '@libs/Navigation/getTopmostWorkspacesCentralPaneName'; +import getTopmostRouteName from '@libs/Navigation/getTopmostRouteName'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; @@ -68,7 +68,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const hasPolicyCreationError = !!(policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && policy.errors); const waitForNavigate = useWaitForNavigation(); const {singleExecution, isExecuting} = useSingleExecution(); - const activeRoute = useNavigationState(getTopmostWorkspacesCentralPaneName); + const activeRoute = useNavigationState(getTopmostRouteName); const {translate} = useLocalize(); const {canUseAccountingIntegrations} = usePermissions(); From a451aaf0ea7d7322f4439d78c0701cfae66bf3be Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 11:42:16 +0200 Subject: [PATCH 0127/1525] Refactor styles in FullScreenNavigator --- .../AppNavigator/Navigators/FullScreenNavigator.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 42bca6e2de4c..3b2d64db9778 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -7,6 +7,7 @@ import createCustomFullScreenNavigator from '@libs/Navigation/AppNavigator/creat import getRootNavigatorScreenOptions from '@libs/Navigation/AppNavigator/getRootNavigatorScreenOptions'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; +import useModalScreenOptions from '@libs/Navigation/AppNavigator/ModalStackNavigators/useModalScreenOptions'; const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; @@ -36,9 +37,11 @@ function FullScreenNavigator() { const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth, styles, StyleUtils); + const workspaceScreenOptions = useModalScreenOptions((screenStyles) => ({cardStyle: screenStyles.navigationScreenCardStyle, headerShown: false})); + return ( - + Date: Fri, 12 Apr 2024 11:42:44 +0200 Subject: [PATCH 0128/1525] Remove WorkspaceSettingsModalStackNavigator --- .../WorkspaceSettingsModalStackNavigator.tsx | 89 ------------------- .../ModalStackNavigators/index.tsx | 2 - 2 files changed, 91 deletions(-) delete mode 100644 src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx deleted file mode 100644 index 2dce4247c7ae..000000000000 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import {createStackNavigator} from '@react-navigation/stack'; -import React from 'react'; -import SCREENS from '@src/SCREENS'; -import useModalScreenOptions from './useModalScreenOptions'; - -const StackNavigator = createStackNavigator(); - -function WorkspaceSettingsModalStackNavigator() { - const screenOptions = useModalScreenOptions((styles) => ({cardStyle: styles.navigationScreenCardStyle, headerShown: false})); - - return ( - - require('@pages/workspace/WorkspaceProfilePage').default as React.ComponentType} - /> - require('@pages/workspace/card/WorkspaceCardPage').default as React.ComponentType} - /> - require('@pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType} - /> - require('@pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType} - /> - require('@pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType} - /> - require('@pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType} - /> - require('@pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType} - /> - require('@pages/workspace/WorkspaceMembersPage').default as React.ComponentType} - /> - - require('@pages/workspace/accounting/WorkspaceAccountingPage').default as React.ComponentType} - /> - - require('@pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType} - /> - require('@pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType} - /> - require('@pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType} - /> - require('@pages/workspace/taxes/WorkspaceTaxesPage').default as React.ComponentType} - /> - require('@pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType} - /> - - ); -} - -export default WorkspaceSettingsModalStackNavigator; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index c251f8143631..e8a5f5d17cb5 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -35,7 +35,6 @@ import type {ThemeStyles} from '@styles/index'; import type {Screen} from '@src/SCREENS'; import SCREENS from '@src/SCREENS'; import useModalScreenOptions from './useModalScreenOptions'; -import WorkspaceSettingsModalStackNavigator from './WorkspaceSettingsModalStackNavigator'; type Screens = Partial React.ComponentType>>; @@ -345,5 +344,4 @@ export { TaskModalStackNavigator, WalletStatementStackNavigator, ProcessMoneyRequestHoldStackNavigator, - WorkspaceSettingsModalStackNavigator, }; From 0578de4d75897be641adddb942c578f4fff13c23 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 12:48:05 +0200 Subject: [PATCH 0129/1525] Remove wrapper for lhp screens --- src/SCREENS.ts | 3 --- .../ModalStackNavigators/index.tsx | 18 +++--------------- .../Navigators/FullScreenNavigator.tsx | 2 +- .../Navigators/LeftModalNavigator.tsx | 8 +++++--- src/libs/Navigation/linkingConfig/config.ts | 12 ++---------- src/libs/Navigation/types.ts | 16 +++------------- src/pages/SearchPage/index.tsx | 2 +- src/styles/theme/themes/dark.ts | 2 +- src/styles/theme/themes/light.ts | 2 +- tests/perf-test/SearchPage.perf-test.tsx | 2 +- 10 files changed, 18 insertions(+), 49 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index b3c2012e90d2..7b653da0ea29 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -98,9 +98,6 @@ const SCREENS = { SEARCH: 'Search', WORKSPACE_SWITCHER: 'WorkspaceSwitcher', }, - WORKSPACE_SWITCHER: { - ROOT: 'WorkspaceSwitcher_Root', - }, RIGHT_MODAL: { SETTINGS: 'Settings', NEW_CHAT: 'NewChat', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index e8a5f5d17cb5..94298d24f37e 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -22,14 +22,12 @@ import type { ReportSettingsNavigatorParamList, RoomInviteNavigatorParamList, RoomMembersNavigatorParamList, - SearchNavigatorParamList, SettingsNavigatorParamList, SignInNavigatorParamList, SplitDetailsNavigatorParamList, TaskDetailsNavigatorParamList, TeachersUniteNavigatorParamList, WalletStatementNavigatorParamList, - WorkspaceSwitcherNavigatorParamList, } from '@navigation/types'; import type {ThemeStyles} from '@styles/index'; import type {Screen} from '@src/SCREENS'; @@ -142,10 +140,6 @@ const RoomInviteModalStackNavigator = createModalStackNavigator require('../../../../pages/RoomInvitePage').default as React.ComponentType, }); -const SearchModalStackNavigator = createModalStackNavigator({ - [SCREENS.SEARCH_ROOT]: () => require('../../../../pages/SearchPage').default as React.ComponentType, -}); - const NewChatModalStackNavigator = createModalStackNavigator({ [SCREENS.NEW_CHAT.ROOT]: () => require('../../../../pages/NewChatSelectorPage').default as React.ComponentType, [SCREENS.NEW_CHAT.NEW_CHAT_CONFIRM]: () => require('../../../../pages/NewChatConfirmPage').default as React.ComponentType, @@ -173,10 +167,6 @@ const NewTeachersUniteNavigator = createModalStackNavigator require('../../../../pages/TeachersUnite/ImTeacherPage').default as React.ComponentType, }); -const WorkspaceSwitcherModalStackNavigator = createModalStackNavigator({ - [SCREENS.WORKSPACE_SWITCHER.ROOT]: () => require('../../../../pages/WorkspaceSwitcherPage').default as React.ComponentType, -}); - const SettingsModalStackNavigator = createModalStackNavigator({ [SCREENS.SETTINGS.SHARE_CODE]: () => require('../../../../pages/ShareCodePage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.PRONOUNS]: () => require('../../../../pages/settings/Profile/PronounsPage').default as React.ComponentType, @@ -318,7 +308,6 @@ const ProcessMoneyRequestHoldStackNavigator = createModalStackNavigator({ export { AddPersonalBankAccountModalStackNavigator, DetailsModalStackNavigator, - OnboardEngagementModalStackNavigator, EditRequestStackNavigator, EnablePaymentsStackNavigator, FlagCommentStackNavigator, @@ -326,22 +315,21 @@ export { NewChatModalStackNavigator, NewTaskModalStackNavigator, NewTeachersUniteNavigator, + OnboardEngagementModalStackNavigator, PrivateNotesModalStackNavigator, + ProcessMoneyRequestHoldStackNavigator, ProfileModalStackNavigator, ReferralModalStackNavigator, - WorkspaceSwitcherModalStackNavigator, ReimbursementAccountModalStackNavigator, + ReportDescriptionModalStackNavigator, ReportDetailsModalStackNavigator, ReportParticipantsModalStackNavigator, ReportSettingsModalStackNavigator, - ReportDescriptionModalStackNavigator, RoomInviteModalStackNavigator, RoomMembersModalStackNavigator, - SearchModalStackNavigator, SettingsModalStackNavigator, SignInModalStackNavigator, SplitDetailsModalStackNavigator, TaskModalStackNavigator, WalletStatementStackNavigator, - ProcessMoneyRequestHoldStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 3b2d64db9778..f6fdba422fae 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -5,9 +5,9 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import createCustomFullScreenNavigator from '@libs/Navigation/AppNavigator/createCustomFullScreenNavigator'; import getRootNavigatorScreenOptions from '@libs/Navigation/AppNavigator/getRootNavigatorScreenOptions'; +import useModalScreenOptions from '@libs/Navigation/AppNavigator/ModalStackNavigators/useModalScreenOptions'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; -import useModalScreenOptions from '@libs/Navigation/AppNavigator/ModalStackNavigators/useModalScreenOptions'; const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; diff --git a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx index 8f76d8fbdd7b..f83f0d7f0d59 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx @@ -6,7 +6,6 @@ import NoDropZone from '@components/DragAndDrop/NoDropZone'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import ModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator/ModalNavigatorScreenOptions'; -import * as ModalStackNavigators from '@libs/Navigation/AppNavigator/ModalStackNavigators'; import type {AuthScreensParamList, LeftModalNavigatorParamList} from '@libs/Navigation/types'; import type NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; @@ -14,6 +13,9 @@ import Overlay from './Overlay'; type LeftModalNavigatorProps = StackScreenProps; +const loadSearchPage = () => require('../../../../pages/SearchPage').default as React.ComponentType; +const loadWorkspaceSwitcherPage = () => require('../../../../pages/WorkspaceSwitcherPage').default as React.ComponentType; + const Stack = createStackNavigator(); function LeftModalNavigator({navigation}: LeftModalNavigatorProps) { @@ -33,11 +35,11 @@ function LeftModalNavigator({navigation}: LeftModalNavigatorProps) { diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 468a5ce8c3d9..3e14d0f01ac1 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -67,17 +67,9 @@ const config: LinkingOptions['config'] = { [SCREENS.NOT_FOUND]: '*', [NAVIGATORS.LEFT_MODAL_NAVIGATOR]: { screens: { - [SCREENS.LEFT_MODAL.SEARCH]: { - screens: { - [SCREENS.SEARCH_ROOT]: ROUTES.SEARCH, - }, - }, + [SCREENS.LEFT_MODAL.SEARCH]: ROUTES.SEARCH, [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: { - screens: { - [SCREENS.WORKSPACE_SWITCHER.ROOT]: { - path: ROUTES.WORKSPACE_SWITCHER, - }, - }, + path: ROUTES.WORKSPACE_SWITCHER, }, }, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c51be87b4191..c8587b2b84c6 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -62,10 +62,6 @@ type CentralPaneNavigatorParamList = { [SCREENS.SETTINGS.SAVE_THE_WORLD]: undefined; }; -type WorkspaceSwitcherNavigatorParamList = { - [SCREENS.WORKSPACE_SWITCHER.ROOT]: undefined; -}; - type BackToParams = { backTo?: Routes; }; @@ -294,10 +290,6 @@ type NewChatNavigatorParamList = { [SCREENS.NEW_CHAT.ROOT]: undefined; }; -type SearchNavigatorParamList = { - [SCREENS.SEARCH_ROOT]: undefined; -}; - type DetailsNavigatorParamList = { [SCREENS.DETAILS_ROOT]: { login: string; @@ -568,8 +560,8 @@ type PrivateNotesNavigatorParamList = { }; type LeftModalNavigatorParamList = { - [SCREENS.LEFT_MODAL.SEARCH]: NavigatorScreenParams; - [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: NavigatorScreenParams; + [SCREENS.LEFT_MODAL.SEARCH]: undefined; + [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: undefined; }; type RightModalNavigatorParamList = { @@ -798,7 +790,7 @@ type AuthScreensParamList = SharedScreensParamList & { }; }; -type RootStackParamList = PublicScreensParamList & AuthScreensParamList & SearchNavigatorParamList; +type RootStackParamList = PublicScreensParamList & AuthScreensParamList & LeftModalNavigatorParamList; type BottomTabName = keyof BottomTabNavigatorParamList; @@ -842,7 +834,6 @@ export type { ParticipantsNavigatorParamList, RoomMembersNavigatorParamList, RoomInviteNavigatorParamList, - SearchNavigatorParamList, NewChatNavigatorParamList, NewTaskNavigatorParamList, TeachersUniteNavigatorParamList, @@ -857,7 +848,6 @@ export type { ReferralDetailsNavigatorParamList, ReimbursementAccountNavigatorParamList, State, - WorkspaceSwitcherNavigatorParamList, OnboardEngagementNavigatorParamList, SwitchPolicyIDParams, FullScreenNavigatorParamList, diff --git a/src/pages/SearchPage/index.tsx b/src/pages/SearchPage/index.tsx index 7d2a5bfecbb8..99c2b084aec0 100644 --- a/src/pages/SearchPage/index.tsx +++ b/src/pages/SearchPage/index.tsx @@ -35,7 +35,7 @@ type SearchPageOnyxProps = { isSearchingForReports: OnyxEntry; }; -type SearchPageProps = SearchPageOnyxProps & StackScreenProps; +type SearchPageProps = SearchPageOnyxProps & StackScreenProps; type Options = OptionsListUtils.Options & {headerMessage: string}; diff --git a/src/styles/theme/themes/dark.ts b/src/styles/theme/themes/dark.ts index e43341f3e8a9..2c3e36a584a2 100644 --- a/src/styles/theme/themes/dark.ts +++ b/src/styles/theme/themes/dark.ts @@ -128,7 +128,7 @@ const darkTheme = { backgroundColor: colors.productDark100, statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, }, - [SCREENS.WORKSPACE_SWITCHER.ROOT]: { + [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: { backgroundColor: colors.productDark100, statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, }, diff --git a/src/styles/theme/themes/light.ts b/src/styles/theme/themes/light.ts index 6444cb9d4073..716deb17893c 100644 --- a/src/styles/theme/themes/light.ts +++ b/src/styles/theme/themes/light.ts @@ -128,7 +128,7 @@ const lightTheme = { backgroundColor: colors.productLight100, statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, }, - [SCREENS.WORKSPACE_SWITCHER.ROOT]: { + [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: { backgroundColor: colors.productLight100, statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, }, diff --git a/tests/perf-test/SearchPage.perf-test.tsx b/tests/perf-test/SearchPage.perf-test.tsx index ea759a1201b2..29bb016230e7 100644 --- a/tests/perf-test/SearchPage.perf-test.tsx +++ b/tests/perf-test/SearchPage.perf-test.tsx @@ -116,7 +116,7 @@ afterEach(() => { PusherHelper.teardown(); }); -type SearchPageProps = StackScreenProps & { +type SearchPageProps = StackScreenProps & { betas: OnyxEntry; reports: OnyxCollection; isSearchingForReports: OnyxEntry; From 1142478d389bcafdbb5868e826c2c47110c4abd4 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 12 Apr 2024 17:08:49 +0300 Subject: [PATCH 0130/1525] fix light box offline indicator --- src/components/Lightbox/index.tsx | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 86a52c2baf6c..909c4d942e28 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,12 +2,14 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -47,6 +49,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan * we need to create a shared value that can be used in the render function. */ const isPagerScrollingFallback = useSharedValue(false); + const {isOffline} = useNetwork(); const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const { @@ -219,9 +222,9 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan style={[contentSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} onError={onError} - onLoad={updateContentSize} - onLoadEnd={() => { + onLoad={(e) => { setLightboxImageLoaded(true); + updateContentSize(e); }} /> @@ -236,21 +239,23 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan resizeMode="contain" style={[fallbackSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} - onLoad={updateContentSize} - onLoadEnd={() => setFallbackImageLoaded(true)} + onLoad={(e) => { + setFallbackImageLoaded(true); + updateContentSize(e); + }} /> )} - - {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && ( - - )} )} + {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} + {isLoading && !isOffline && ( + + )} + {isLoading && } ); } From df8edb102940b5e5c1d51437fd0c49b5cfff5c3f Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 12 Apr 2024 22:39:25 +0300 Subject: [PATCH 0131/1525] fix cached image case offline indicator --- src/components/Lightbox/index.tsx | 64 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 909c4d942e28..a6b30f1e6815 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; @@ -133,10 +133,12 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan const indexOutOfRange = page > activePage + indexCanvasOffset || page < activePage - indexCanvasOffset; return !indexOutOfRange; }, [activePage, hasSiblingCarouselItems, page]); - const [isLightboxImageLoaded, setLightboxImageLoaded] = useState(false); + const [isLightboxImageLoading, setLightboxImageLoading] = useState(false); + const isLightboxImageLoaded = useRef(false); const [isFallbackVisible, setFallbackVisible] = useState(!isLightboxVisible); - const [isFallbackImageLoaded, setFallbackImageLoaded] = useState(false); + const [isFallbackImageLoading, setFallbackImageLoading] = useState(false); + const isFallbackImageLoaded = useRef(false); const fallbackSize = useMemo(() => { if (!hasSiblingCarouselItems || !contentSize || isCanvasLoading) { return undefined; @@ -154,18 +156,19 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan // until the fallback gets hidden so that we don't see two overlapping images at the same time. // If there the Lightbox is not used within a carousel, we don't need to hide the Lightbox, // because it's only going to be rendered after the fallback image is hidden. - const shouldShowLightbox = isLightboxImageLoaded && !isFallbackVisible; + const shouldShowLightbox = isLightboxImageLoaded.current && !isFallbackVisible; - const isFallbackStillLoading = isFallbackVisible && !isFallbackImageLoaded; - const isLightboxStillLoading = isLightboxVisible && !isLightboxImageLoaded; - const isLoading = isActive && (isCanvasLoading || isFallbackStillLoading || isLightboxStillLoading); + const isFallbackStillLoading = isFallbackVisible && isFallbackImageLoading; + const isLightboxStillLoading = isLightboxVisible && isLightboxImageLoading; + const isLoading = isActive && (isFallbackStillLoading || isLightboxStillLoading); // Resets the lightbox when it becomes inactive useEffect(() => { if (isLightboxVisible) { return; } - setLightboxImageLoaded(false); + isLightboxImageLoaded.current = false; + setLightboxImageLoading(false); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); @@ -177,9 +180,10 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan } // When the carousel item is active and the lightbox has finished loading, we want to hide the fallback image - if (isActive && isFallbackVisible && isLightboxVisible && isLightboxImageLoaded) { + if (isActive && isFallbackVisible && isLightboxVisible && isLightboxImageLoaded.current) { setFallbackVisible(false); - setFallbackImageLoaded(false); + setFallbackImageLoading(false); + isFallbackImageLoaded.current = false; return; } @@ -187,7 +191,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan if (!isActive && !isLightboxVisible) { setFallbackVisible(true); } - }, [hasSiblingCarouselItems, isActive, isFallbackVisible, isLightboxImageLoaded, isLightboxVisible]); + }, [hasSiblingCarouselItems, isActive, isFallbackVisible, isLightboxImageLoading, isLightboxVisible]); const scaleChange = useCallback( (scale: number) => { @@ -223,9 +227,18 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan isAuthTokenRequired={isAuthTokenRequired} onError={onError} onLoad={(e) => { - setLightboxImageLoaded(true); + isLightboxImageLoaded.current = true; + setLightboxImageLoading(false); updateContentSize(e); }} + onLoadStart={() => { + setTimeout(() => { + if (isLightboxImageLoaded.current) { + return; + } + setLightboxImageLoading(true); + }, 200); + }} /> @@ -240,22 +253,31 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan style={[fallbackSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} onLoad={(e) => { - setFallbackImageLoaded(true); + setFallbackImageLoading(false); + isFallbackImageLoaded.current = true; updateContentSize(e); }} + onLoadStart={() => { + setTimeout(() => { + if (isFallbackImageLoaded.current) { + return; + } + setFallbackImageLoading(true); + }, 200); + }} /> )} + {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} + {isLoading && !isOffline && ( + + )} + {isLoading && } )} - {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} - {isLoading && !isOffline && ( - - )} - {isLoading && } ); } From ae46fdf8eb05d8ed6084cba1c66b521379b275d0 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 15 Apr 2024 11:23:57 +0200 Subject: [PATCH 0132/1525] Add warning to useActiveWorkspaceFromNavigationState --- src/hooks/useActiveWorkspaceFromNavigationState.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/hooks/useActiveWorkspaceFromNavigationState.ts b/src/hooks/useActiveWorkspaceFromNavigationState.ts index 63a6865a516b..5b117816d40f 100644 --- a/src/hooks/useActiveWorkspaceFromNavigationState.ts +++ b/src/hooks/useActiveWorkspaceFromNavigationState.ts @@ -1,12 +1,21 @@ import {useNavigationState} from '@react-navigation/native'; +import Log from '@libs/Log'; import type {BottomTabNavigatorParamList} from '@libs/Navigation/types'; +import SCREENS from '@src/SCREENS'; /** * Get the currently selected policy ID stored in the navigation state. This hook should only be called only from screens in BottomTab. */ function useActiveWorkspaceFromNavigationState() { // The last policyID value is always stored in the last route in BottomTabNavigator. - const activeWorkpsaceID = useNavigationState((state) => state.routes.at(-1)?.params?.policyID); + const activeWorkpsaceID = useNavigationState((state) => { + // SCREENS.HOME is a screen located in the BottomTabNavigator, if it's not in state.routeNames it means that this hook was called from a screen in another navigator. + if (!state.routeNames.includes(SCREENS.HOME)) { + Log.warn('useActiveWorkspaceFromNavigationState should be called only from BottomTab screens'); + } + + return state.routes.at(-1)?.params?.policyID; + }); return activeWorkpsaceID; } From fea7ada105b4d1cbc82e9ff5ebf723e9b40c07b9 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 15 Apr 2024 11:56:37 +0200 Subject: [PATCH 0133/1525] fix typography --- src/components/MoneyRequestHeader.tsx | 8 ++++---- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 8 ++++++-- src/components/ReportActionItem/ReportPreview.tsx | 8 ++++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 5ea46f339d46..7557799472e1 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -188,8 +188,8 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, title={ } @@ -202,8 +202,8 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, title={ } diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 08d693dd109a..63950e3fe8d0 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -313,7 +313,9 @@ function MoneyRequestPreviewContent({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - {translate('iou.receiptScanInProgress')} + + {translate('iou.receiptScanInProgress')} + )} {isPending && ( @@ -324,7 +326,9 @@ function MoneyRequestPreviewContent({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - {translate('iou.transactionPending')} + + {translate('iou.transactionPending')} + )} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 231b1176423b..2b5034708dac 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -330,7 +330,9 @@ function ReportPreview({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - {translate('iou.receiptScanInProgress')} + + {translate('iou.receiptScanInProgress')} + )} {shouldShowPendingSubtitle && ( @@ -341,7 +343,9 @@ function ReportPreview({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - {translate('iou.transactionPending')} + + {translate('iou.transactionPending')} + )} From f0835125dda095f8088b9c5df5d75141658e6118 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 13:39:56 +0300 Subject: [PATCH 0134/1525] fix android loading indicator case --- src/components/Lightbox/index.tsx | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index a6b30f1e6815..e35dc1703b36 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -201,6 +201,19 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan [onScaleChangedContext, onScaleChangedProp], ); + useEffect(() => { + // To avoid showing loading or offline indicator for cached images we set loading + // states after a 200 ms delay based on whether the image is loaded or not by then. + setTimeout(() => { + if (!isFallbackImageLoaded.current) { + setFallbackImageLoading(true); + } + if (!isLightboxImageLoaded.current) { + setLightboxImageLoading(true); + } + }, 200); + }, []); + return ( { - setTimeout(() => { - if (isLightboxImageLoaded.current) { - return; - } - setLightboxImageLoading(true); - }, 200); - }} /> @@ -257,14 +262,6 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan isFallbackImageLoaded.current = true; updateContentSize(e); }} - onLoadStart={() => { - setTimeout(() => { - if (isFallbackImageLoaded.current) { - return; - } - setFallbackImageLoading(true); - }, 200); - }} /> )} From 36ccc936df58fd041f097c2e2fe4290ab339d0af Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 14:09:27 +0300 Subject: [PATCH 0135/1525] minor fix --- src/components/Lightbox/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index e35dc1703b36..42467569a904 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -168,7 +168,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan return; } isLightboxImageLoaded.current = false; - setLightboxImageLoading(false); + setLightboxImageLoading(true); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); From c6111876d7a99b59db83cd2e8fe48403c2708b91 Mon Sep 17 00:00:00 2001 From: Monil Bhavsar Date: Mon, 15 Apr 2024 19:43:16 +0530 Subject: [PATCH 0136/1525] Pass taxCode and taxAmount params for split bill request --- src/libs/API/parameters/SplitBillParams.ts | 2 ++ src/libs/actions/IOU.ts | 13 ++++++++++++- .../iou/request/step/IOURequestStepConfirmation.tsx | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/libs/API/parameters/SplitBillParams.ts b/src/libs/API/parameters/SplitBillParams.ts index 310923093d5e..0f121da76025 100644 --- a/src/libs/API/parameters/SplitBillParams.ts +++ b/src/libs/API/parameters/SplitBillParams.ts @@ -14,6 +14,8 @@ type SplitBillParams = { createdReportActionID?: string; policyID: string | undefined; chatType: string | undefined; + taxCode: string; + taxAmount: number; }; export default SplitBillParams; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index cd0264ddb6ea..8601b37b979a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2543,6 +2543,7 @@ function createSplitsAndOnyxData( existingSplitChatReportID = '', billable = false, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL, + taxAmount: number, ): SplitsAndOnyxData { const currentUserEmailForIOUSplit = PhoneNumber.addSMSDomainIfPhoneNumber(currentUserLogin); const participantAccountIDs = participants.map((participant) => Number(participant.accountID)); @@ -2709,7 +2710,8 @@ function createSplitsAndOnyxData( // Loop through participants creating individual chats, iouReports and reportActionIDs as needed const splitAmount = IOUUtils.calculateAmount(participants.length, amount, currency, false); - const splits: Split[] = [{email: currentUserEmailForIOUSplit, accountID: currentUserAccountID, amount: IOUUtils.calculateAmount(participants.length, amount, currency, true)}]; + const splitTaxAmount = IOUUtils.calculateAmount(participants.length, taxAmount, currency, false); + const splits: Split[] = [{email: currentUserEmailForIOUSplit, accountID: currentUserAccountID, amount: IOUUtils.calculateAmount(participants.length, amount, currency, true), taxAmount: IOUUtils.calculateAmount(participants.length, taxAmount, currency, true)}]; const hasMultipleParticipants = participants.length > 1; participants.forEach((participant) => { @@ -2863,6 +2865,7 @@ function createSplitsAndOnyxData( reportPreviewReportActionID: oneOnOneReportPreviewAction.reportActionID, transactionThreadReportID: optimisticTransactionThread.reportID, createdReportActionIDForThread: optimisticCreatedActionForTransactionThread.reportActionID, + taxAmount: splitTaxAmount, }; splits.push(individualSplit); @@ -2904,6 +2907,8 @@ type SplitBillActionsParams = { billable?: boolean; iouRequestType?: IOURequestType; existingSplitChatReportID?: string; + taxCode?: string; + taxAmount?: number; }; /** @@ -2924,7 +2929,10 @@ function splitBill({ billable = false, iouRequestType = CONST.IOU.REQUEST_TYPE.MANUAL, existingSplitChatReportID = '', + taxCode = '', + taxAmount = 0, }: SplitBillActionsParams) { + console.debug(taxCode); const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created); const {splitData, splits, onyxData} = createSplitsAndOnyxData( participants, @@ -2940,6 +2948,7 @@ function splitBill({ existingSplitChatReportID, billable, iouRequestType, + taxAmount, ); const parameters: SplitBillParams = { @@ -2958,6 +2967,8 @@ function splitBill({ createdReportActionID: splitData.createdReportActionID, policyID: splitData.policyID, chatType: splitData.chatType, + taxCode, + taxAmount, }; API.write(WRITE_COMMANDS.SPLIT_BILL, parameters, onyxData); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 83f831708799..1e1ab4da3092 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -307,6 +307,8 @@ function IOURequestStepConfirmation({ existingSplitChatReportID: report?.reportID, billable: transaction.billable, iouRequestType: transaction.iouRequestType, + taxCode: transaction.taxCode, + taxAmount: transaction.taxAmount, }); } return; @@ -328,6 +330,8 @@ function IOURequestStepConfirmation({ tag: transaction.tag, billable: !!transaction.billable, iouRequestType: transaction.iouRequestType, + taxCode: transaction.taxCode, + taxAmount: transaction.taxAmount, }); } return; From dd4ae04abcefd6e6768ca036bbf32744a02c493c Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 15 Apr 2024 17:16:42 +0200 Subject: [PATCH 0137/1525] style fixes --- src/components/MoneyRequestHeader.tsx | 4 ++-- src/components/MoneyRequestHeaderStatusBar.tsx | 2 +- .../MoneyRequestPreviewContent.tsx | 8 ++------ src/components/ReportActionItem/ReportPreview.tsx | 12 ++++-------- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 7557799472e1..c50913bc2d31 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -190,7 +190,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, src={Expensicons.CreditCardHourglass} height={variables.iconSizeSmall} width={variables.iconSizeSmall} - fill={theme.textSupporting} + fill={theme.icon} /> } description={translate('iou.transactionPendingDescription')} @@ -204,7 +204,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, src={Expensicons.ReceiptScan} height={variables.iconSizeSmall} width={variables.iconSizeSmall} - fill={theme.textSupporting} + fill={theme.icon} /> } description={translate('iou.receiptScanInProgressDescription')} diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index d21e66ba39eb..0052768a4cf0 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -25,7 +25,7 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom {title} ) : ( - {title} + {title} )} {description} diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 63950e3fe8d0..c234eb749653 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -313,9 +313,7 @@ function MoneyRequestPreviewContent({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - - {translate('iou.receiptScanInProgress')} - + {translate('iou.receiptScanInProgress')} )} {isPending && ( @@ -326,9 +324,7 @@ function MoneyRequestPreviewContent({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - - {translate('iou.transactionPending')} - + {translate('iou.transactionPending')} )} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 2b5034708dac..766680fd378b 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -328,11 +328,9 @@ function ReportPreview({ src={Expensicons.ReceiptScan} height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} - fill={theme.textSupporting} + fill={theme.icon} /> - - {translate('iou.receiptScanInProgress')} - + {translate('iou.receiptScanInProgress')} )} {shouldShowPendingSubtitle && ( @@ -341,11 +339,9 @@ function ReportPreview({ src={Expensicons.CreditCardHourglass} height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} - fill={theme.textSupporting} + fill={theme.icon} /> - - {translate('iou.transactionPending')} - + {translate('iou.transactionPending')} )} From 75a772eb376f562b9ef16c3ea1b36801344785e5 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 20:38:35 +0300 Subject: [PATCH 0138/1525] fix receipt offline indicator case --- src/components/AttachmentModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index 7d13524b78df..3938c2de8559 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -463,7 +463,7 @@ function AttachmentModal({ } const context = useMemo( () => ({ - pagerItems: [], + pagerItems: [{source: sourceForAttachmentView, index: 0, isActive: true}], activePage: 0, pagerRef: undefined, isPagerScrolling: nope, @@ -472,7 +472,7 @@ function AttachmentModal({ onScaleChanged: () => {}, onSwipeDown: closeModal, }), - [closeModal, nope], + [closeModal, nope, sourceForAttachmentView], ); return ( From bd00d41ed94ef5ab1ffc1bb31b4a312cab204351 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 22:40:59 +0300 Subject: [PATCH 0139/1525] minor fix --- src/components/Lightbox/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 42467569a904..bf5067e81bbb 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -168,7 +168,6 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan return; } isLightboxImageLoaded.current = false; - setLightboxImageLoading(true); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); From 92a626574ba95b9dd448df13967533aa41318b6c Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:01:51 +0100 Subject: [PATCH 0140/1525] Participants onyx migration --- src/libs/migrateOnyx.ts | 2 + src/libs/migrations/Participants.ts | 60 +++++++++++++++++++++++++++++ src/types/onyx/Report.ts | 2 - 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/libs/migrations/Participants.ts diff --git a/src/libs/migrateOnyx.ts b/src/libs/migrateOnyx.ts index 412a8e00f052..674b5b8242dc 100644 --- a/src/libs/migrateOnyx.ts +++ b/src/libs/migrateOnyx.ts @@ -2,6 +2,7 @@ import Log from './Log'; import CheckForPreviousReportActionID from './migrations/CheckForPreviousReportActionID'; import KeyReportActionsDraftByReportActionID from './migrations/KeyReportActionsDraftByReportActionID'; import NVPMigration from './migrations/NVPMigration'; +import Participants from './migrations/Participants'; import PronounsMigration from './migrations/PronounsMigration'; import RemoveEmptyReportActionsDrafts from './migrations/RemoveEmptyReportActionsDrafts'; import RenameReceiptFilename from './migrations/RenameReceiptFilename'; @@ -21,6 +22,7 @@ export default function () { RemoveEmptyReportActionsDrafts, NVPMigration, PronounsMigration, + Participants, ]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the diff --git a/src/libs/migrations/Participants.ts b/src/libs/migrations/Participants.ts new file mode 100644 index 000000000000..0e0cd8722348 --- /dev/null +++ b/src/libs/migrations/Participants.ts @@ -0,0 +1,60 @@ +import Onyx from 'react-native-onyx'; +import type {NullishDeep, OnyxCollection} from 'react-native-onyx'; +import Log from '@libs/Log'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; +import type {Participant, Participants} from '@src/types/onyx/Report'; + +type ReportKey = `${typeof ONYXKEYS.COLLECTION.REPORT}${string}`; +type OldReport = Report & {participantAccountIDs?: number[]; visibleChatMemberAccountIDs?: number[]}; +type OldReportCollection = Record>; + +function getReports(): Promise> { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (reports) => { + Onyx.disconnect(connectionID); + return resolve(reports); + }, + }); + }); +} + +export default function (): Promise { + return getReports().then((reports) => { + if (!reports) { + Log.info('[Migrate Onyx] Skipped Participants migration because there are no reports'); + return; + } + + const collection = Object.entries(reports).reduce((reportsCollection, [onyxKey, report]) => { + // If we have participantAccountIDs then this report is eligible for migration + if (report?.participantAccountIDs) { + const visibleParticipants = new Set(report.visibleChatMemberAccountIDs); + const participants = report.participantAccountIDs.reduce((reportParticipants, accountID) => { + const participant: Participant = { + hidden: !visibleParticipants.has(accountID), + }; + + // eslint-disable-next-line no-param-reassign + reportParticipants[accountID] = participant; + return reportParticipants; + }, {}); + + // eslint-disable-next-line no-param-reassign + reportsCollection[onyxKey as ReportKey] = { + participants, + participantAccountIDs: null, + visibleChatMemberAccountIDs: null, + }; + } + + return reportsCollection; + }, {}); + + // eslint-disable-next-line rulesdir/prefer-actions-set-data + return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, collection).then(() => Log.info('[Migrate Onyx] Ran migration Participants successfully')); + }); +} diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index fe3eec6dc11e..b271255b06a6 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -140,8 +140,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< ownerAccountID?: number; ownerEmail?: string; participants?: Participants; - participantAccountIDs?: number[]; - visibleChatMemberAccountIDs?: number[]; total?: number; unheldTotal?: number; currency?: string; From d99170a61a4d44c2b750123bf14d374b82869214 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 16 Apr 2024 00:35:57 +0100 Subject: [PATCH 0141/1525] Add TS definitions --- src/types/onyx/Transaction.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 281b6b4228ce..46b60ee8b6ec 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -98,6 +98,13 @@ type TaxRate = { data?: TaxRateData; }; +type SplitShare = { + amount: number; + isModified?: boolean; +}; + +type SplitShares = Record; + type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< { /** The original transaction amount */ @@ -215,6 +222,12 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< /** Indicates transaction loading */ isLoading?: boolean; + + /** Holds individual shares of a split keyed by accountID, only used locally */ + splitShares?: SplitShares; + + /** Holds the accountIDs of accounts who paid the split, for now only supports a single payer */ + splitPayerAccountIDs?: number[]; }, keyof Comment >; @@ -245,4 +258,6 @@ export type { TaxRate, ReceiptSource, TransactionCollectionDataSet, + SplitShare, + SplitShares, }; From 99efbb6a82e1955db820d69106ddf7c311d5bad9 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 16 Apr 2024 00:47:20 +0100 Subject: [PATCH 0142/1525] Set split shares when first creating the split --- src/libs/actions/IOU.ts | 22 ++++++++++++++++++- .../iou/request/step/IOURequestStepAmount.tsx | 4 ++++ .../step/IOURequestStepParticipants.tsx | 8 ++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index cd0264ddb6ea..70f611ac65e9 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -56,7 +56,7 @@ import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type ReportAction from '@src/types/onyx/ReportAction'; import type {OnyxData} from '@src/types/onyx/Request'; -import type {Comment, Receipt, ReceiptSource, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction'; +import type {Comment, Receipt, ReceiptSource, SplitShares, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import * as CachedPDFPaths from './CachedPDFPaths'; @@ -5388,6 +5388,25 @@ function setShownHoldUseExplanation() { Onyx.set(ONYXKEYS.NVP_HOLD_USE_EXPLAINED, true); } +function resetSplitShares(transactionID: string, participantAccountIDs: number[], amount: number, currency: string) { + const participantAccountIDsWithoutCurrentUser = participantAccountIDs.filter((accountID) => accountID !== userAccountID); + const splitShares: SplitShares = [userAccountID, ...participantAccountIDsWithoutCurrentUser].reduce((result: SplitShares, accountID): SplitShares => { + const isPayer = accountID === userAccountID; + const splitAmount = IOUUtils.calculateAmount(participantAccountIDsWithoutCurrentUser.length, amount, currency, isPayer); + return { + ...result, + [accountID]: { + amount: splitAmount, + }, + }; + }, {}); + + // TODO: figure out why this needs `then` + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {splitShares: null}).then(() => { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {splitShares}); + }); +} + /** * Put money request on HOLD */ @@ -5582,6 +5601,7 @@ export { setMoneyRequestTaxAmount, setMoneyRequestTaxRate, setShownHoldUseExplanation, + resetSplitShares, updateMoneyRequestDate, updateMoneyRequestBillable, updateMoneyRequestMerchant, diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 1936a132c665..f722c9e2b17e 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -127,6 +127,10 @@ function IOURequestStepAmount({ // to the confirm step. if (report?.reportID) { IOU.setMoneyRequestParticipantsFromReport(transactionID, report); + if (isSplitBill && !report.isOwnPolicyExpenseChat && report.participants) { + const participantAccountIDs = Object.keys(report.participants).map((accountID) => Number(accountID)); + IOU.resetSplitShares(transactionID, participantAccountIDs, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD); + } Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); return; } diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index cebb000b2121..320cfb97e8f1 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -133,11 +133,17 @@ function IOURequestStepParticipants({ nextStepIOUType = CONST.IOU.TYPE.SEND; } + const isPolicyExpenseChat = participants?.some((participant) => participant.isPolicyExpenseChat); + if (nextStepIOUType === CONST.IOU.TYPE.SPLIT && !isPolicyExpenseChat) { + const participantAccountIDs = participants?.map((participant) => participant.accountID) as number[]; + IOU.resetSplitShares(transactionID, participantAccountIDs, transaction?.amount ?? 0, transaction?.currency ?? CONST.CURRENCY.USD); + } + IOU.setMoneyRequestTag(transactionID, ''); IOU.setMoneyRequestCategory(transactionID, ''); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, nextStepIOUType, transactionID, selectedReportID.current || reportID)); }, - [iouType, transactionID, reportID], + [iouType, transactionID, reportID, participants, transaction?.amount, transaction?.currency], ); const navigateBack = useCallback(() => { From cf6504400af7bf79d8464faabf42e99072db5b22 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 16 Apr 2024 03:01:04 +0100 Subject: [PATCH 0143/1525] Add IOU functions to set split shares and adjust them --- src/libs/actions/IOU.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 70f611ac65e9..2b7d08b1346a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5407,6 +5407,27 @@ function resetSplitShares(transactionID: string, participantAccountIDs: number[] }); } +function setSplitShare(transactionID: string, participantAccountID: number, participantShare: number) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { + splitShares: { + [participantAccountID]: {amount: participantShare, isModified: true}, + }, + }); +} + +function adjustRemainingSplitShares(transactionID: string, remainingAccountIDs: number[], remainingAmount: number, currency: string) { + const splitShares: SplitShares = remainingAccountIDs.reduce((result: SplitShares, accountID: number, index: number): SplitShares => { + const splitAmount = IOUUtils.calculateAmount(remainingAccountIDs.length - 1, remainingAmount, currency, index === 0); + return { + ...result, + [accountID]: { + amount: splitAmount, + }, + }; + }, {}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {splitShares}); +} + /** * Put money request on HOLD */ @@ -5602,6 +5623,8 @@ export { setMoneyRequestTaxRate, setShownHoldUseExplanation, resetSplitShares, + setSplitShare, + adjustRemainingSplitShares, updateMoneyRequestDate, updateMoneyRequestBillable, updateMoneyRequestMerchant, From 1a8bd9efc629eaa27375b7752bdfd1a4507f546d Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 16 Apr 2024 19:27:49 +0530 Subject: [PATCH 0144/1525] revert changes in WorkspaceMembersPage & BaseSelectionList. Signed-off-by: Krishna Gupta --- .../SelectionList/BaseSelectionList.tsx | 292 +++++++++--------- src/pages/workspace/WorkspaceMembersPage.tsx | 136 ++++---- 2 files changed, 196 insertions(+), 232 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 1d9a0a64d599..62f098e76228 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -4,7 +4,6 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import type {LayoutChangeEvent, SectionList as RNSectionList, TextInput as RNTextInput, SectionListRenderItemInfo} from 'react-native'; import {View} from 'react-native'; -import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager'; import Button from '@components/Button'; import Checkbox from '@components/Checkbox'; import FixedFooter from '@components/FixedFooter'; @@ -16,7 +15,9 @@ import ShowMoreButton from '@components/ShowMoreButton'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useActiveElementRole from '@hooks/useActiveElementRole'; +import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; +import useKeyboardState from '@hooks/useKeyboardState'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -56,7 +57,6 @@ function BaseSelectionList( showConfirmButton = false, shouldPreventDefaultFocusOnSelectRow = false, containerStyle, - isKeyboardShown = false, disableKeyboardShortcuts = false, children, shouldStopPropagation = false, @@ -69,7 +69,6 @@ function BaseSelectionList( listHeaderWrapperStyle, isRowMultilineSupported = false, textInputRef, - ListHeaderComponent, headerMessageStyle, shouldHideListOnInitialRender = true, textInputIconLeft, @@ -89,6 +88,7 @@ function BaseSelectionList( const isFocused = useIsFocused(); const [maxToRenderPerBatch, setMaxToRenderPerBatch] = useState(shouldUseDynamicMaxToRenderPerBatch ? 0 : CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT); const [isInitialSectionListRender, setIsInitialSectionListRender] = useState(true); + const {isKeyboardShown} = useKeyboardState(); const [itemsToHighlight, setItemsToHighlight] = useState | null>(null); const itemFocusTimeoutRef = useRef(null); const [currentPage, setCurrentPage] = useState(1); @@ -167,9 +167,6 @@ function BaseSelectionList( }; }, [canSelectMultiple, sections]); - // If `initiallyFocusedOptionKey` is not passed, we fall back to `-1`, to avoid showing the highlight on the first member - const [focusedIndex, setFocusedIndex] = useState(() => flattenedSections.allOptions.findIndex((option) => option.keyForList === initiallyFocusedOptionKey)); - const [slicedSections, ShowMoreButtonInstance] = useMemo(() => { let remainingOptionsLimit = CONST.MAX_OPTIONS_SELECTOR_PAGE_LENGTH * currentPage; const processedSections = getSectionsWithIndexOffset( @@ -226,6 +223,17 @@ function BaseSelectionList( [flattenedSections.allOptions], ); + // If `initiallyFocusedOptionKey` is not passed, we fall back to `-1`, to avoid showing the highlight on the first member + const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({ + initialFocusedIndex: flattenedSections.allOptions.findIndex((option) => option.keyForList === initiallyFocusedOptionKey), + maxIndex: flattenedSections.allOptions.length - 1, + isActive: true, + onFocusedIndexChange: (index: number) => { + scrollToIndex(index, true); + }, + isFocused, + }); + /** * Logic to run when a row is selected, either with click/press or keyboard hotkeys. * @@ -305,7 +313,7 @@ function BaseSelectionList( }; const renderSectionHeader = ({section}: {section: SectionListDataType}) => { - if (!section.title || isEmptyObject(section.data) || ListHeaderComponent) { + if (!section.title || isEmptyObject(section.data)) { return null; } @@ -341,43 +349,11 @@ function BaseSelectionList( rightHandSideComponent={rightHandSideComponent} keyForList={item.keyForList ?? ''} isMultilineSupported={isRowMultilineSupported} + onFocus={() => setFocusedIndex(index)} /> ); }; - const header = () => ( - <> - {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( - - - - {!customListHeader && ( - e.preventDefault() : undefined} - > - {translate('workspace.people.selectAll')} - - )} - - {customListHeader} - - )} - {!headerMessage && !canSelectMultiple && customListHeader} - - ); - const scrollToFocusedIndexOnFirstRender = useCallback( (nativeEvent: LayoutChangeEvent) => { if (shouldUseDynamicMaxToRenderPerBatch) { @@ -408,7 +384,7 @@ function BaseSelectionList( setFocusedIndex(newFocusedIndex); scrollToIndex(newFocusedIndex, true); }, - [scrollToIndex], + [scrollToIndex, setFocusedIndex], ); /** Focuses the text input when the component comes into focus and after any navigation animations finish. */ @@ -494,7 +470,7 @@ function BaseSelectionList( setItemsToHighlight(null); }, timeout); }, - [flattenedSections.allOptions, updateAndScrollToFocusedIndex], + [flattenedSections.allOptions, setFocusedIndex, updateAndScrollToFocusedIndex], ); useImperativeHandle(ref, () => ({scrollAndHighlightItem}), [scrollAndHighlightItem]); @@ -526,115 +502,129 @@ function BaseSelectionList( ); return ( - section.data).length - 1} - onFocusedIndexChanged={updateAndScrollToFocusedIndex} - > - - {({safeAreaPaddingBottomStyle}) => ( - - {shouldShowTextInput && ( - - { - innerTextInputRef.current = element as RNTextInput; - - if (!textInputRef) { - return; - } - - if (typeof textInputRef === 'function') { - textInputRef(element as RNTextInput); - } else { - // eslint-disable-next-line no-param-reassign - textInputRef.current = element as RNTextInput; - } - }} - label={textInputLabel} - accessibilityLabel={textInputLabel} - hint={textInputHint} - role={CONST.ROLE.PRESENTATION} - value={textInputValue} - placeholder={textInputPlaceholder} - maxLength={textInputMaxLength} - onChangeText={onChangeText} - inputMode={inputMode} - selectTextOnFocus - spellCheck={false} - iconLeft={textInputIconLeft} - onSubmitEditing={selectFocusedOption} - blurOnSubmit={!!flattenedSections.allOptions.length} - isLoading={isLoadingNewOptions} - testID="selection-list-text-input" - /> - - )} - {/* If we are loading new options we will avoid showing any header message. This is mostly because one of the header messages says there are no options. */} - {/* This is misleading because we might be in the process of loading fresh options from the server. */} - {!isLoadingNewOptions && !!headerMessage && ( - - {headerMessage} - - )} - {!!headerContent && headerContent} - {flattenedSections.allOptions.length === 0 && showLoadingPlaceholder ? ( - - ) : ( - <> - {!ListHeaderComponent && header()} - ( - <> - {renderSectionHeader(arg)} - {ListHeaderComponent && header()} - - )} - renderItem={renderItem} - getItemLayout={getItemLayout} - onScroll={onScroll} - onScrollBeginDrag={onScrollBeginDrag} - keyExtractor={(item, index) => item.keyForList ?? `${index}`} - extraData={focusedIndex} - // the only valid values on the new arch are "white", "black", and "default", other values will cause a crash - indicatorStyle="white" - keyboardShouldPersistTaps="always" - showsVerticalScrollIndicator={showScrollIndicator} - initialNumToRender={12} - maxToRenderPerBatch={maxToRenderPerBatch} - windowSize={5} - viewabilityConfig={{viewAreaCoveragePercentThreshold: 95}} - testID="selection-list" - onLayout={onSectionListLayout} - style={(!maxToRenderPerBatch || (shouldHideListOnInitialRender && isInitialSectionListRender)) && styles.opacity0} - ListFooterComponent={ShowMoreButtonInstance} - ListHeaderComponent={ListHeaderComponent && ListHeaderComponent} - stickySectionHeadersEnabled={!!ListHeaderComponent && true} - /> - {children} - - )} - {showConfirmButton && ( - -