diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 6eb795befe3c..f16f8129e86c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -241,6 +241,7 @@ const ONYXKEYS = { POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', REPORT: 'report_', + REPORT_METADATA: 'reportMetadata_', REPORT_ACTIONS: 'reportActions_', REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_', REPORT_ACTIONS_REACTIONS: 'reportActionsReactions_', @@ -381,6 +382,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; + [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportAction; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: string; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions; diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.js b/src/components/Reactions/ReportActionItemEmojiReactions.js index c1e1764ed9f1..e72c9d9381fa 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.js +++ b/src/components/Reactions/ReportActionItemEmojiReactions.js @@ -13,7 +13,7 @@ import EmojiReactionsPropTypes from './EmojiReactionsPropTypes'; import Tooltip from '../Tooltip'; import ReactionTooltipContent from './ReactionTooltipContent'; import * as EmojiUtils from '../../libs/EmojiUtils'; -import ReportScreenContext from '../../pages/home/ReportScreenContext'; +import {ReactionListContext} from '../../pages/home/ReportScreenContext'; const propTypes = { emojiReactions: EmojiReactionsPropTypes, @@ -41,8 +41,9 @@ const defaultProps = { }; function ReportActionItemEmojiReactions(props) { - const {reactionListRef} = useContext(ReportScreenContext); + const reactionListRef = useContext(ReactionListContext); const popoverReactionListAnchors = useRef({}); + let totalReactionCount = 0; // Each emoji is sorted by the oldest timestamp of user reactions so that they will always appear in the same order for everyone diff --git a/src/components/ReportActionsSkeletonView/index.js b/src/components/ReportActionsSkeletonView/index.js index 2fe7e590afef..6bdc993c2055 100644 --- a/src/components/ReportActionsSkeletonView/index.js +++ b/src/components/ReportActionsSkeletonView/index.js @@ -1,13 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {View} from 'react-native'; +import {View, Dimensions} from 'react-native'; import SkeletonViewLines from './SkeletonViewLines'; import CONST from '../../CONST'; const propTypes = { - /** Height of the container component */ - containerHeight: PropTypes.number.isRequired, - /** Whether to animate the skeleton view */ shouldAnimate: PropTypes.bool, }; @@ -18,7 +15,7 @@ const defaultProps = { function ReportActionsSkeletonView(props) { // Determines the number of content items based on container height - const possibleVisibleContentItems = Math.ceil(props.containerHeight / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT); + const possibleVisibleContentItems = Math.ceil(Dimensions.get('window').height / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT); const skeletonViewLines = []; for (let index = 0; index < possibleVisibleContentItems; index++) { const iconIndex = (index + 1) % 4; diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index 6c588698ce9d..5ce1b0bc6d74 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -28,6 +28,9 @@ const withLocalizePropTypes = { /** Formats a datetime to local date and time string */ datetimeToCalendarTime: PropTypes.func.isRequired, + /** Updates date-fns internal locale */ + updateLocale: PropTypes.func.isRequired, + /** Returns a locally converted phone number for numbers from the same region * and an internationally converted phone number with the country code for numbers from other regions */ formatPhoneNumber: PropTypes.func.isRequired, @@ -79,6 +82,7 @@ class LocaleContextProvider extends React.Component { numberFormat: this.numberFormat.bind(this), datetimeToRelative: this.datetimeToRelative.bind(this), datetimeToCalendarTime: this.datetimeToCalendarTime.bind(this), + updateLocale: this.updateLocale.bind(this), formatPhoneNumber: this.formatPhoneNumber.bind(this), fromLocaleDigit: this.fromLocaleDigit.bind(this), toLocaleDigit: this.toLocaleDigit.bind(this), @@ -122,6 +126,13 @@ class LocaleContextProvider extends React.Component { return DateUtils.datetimeToCalendarTime(this.props.preferredLocale, datetime, includeTimezone, lodashGet(this.props, 'currentUserPersonalDetails.timezone.selected'), isLowercase); } + /** + * Updates date-fns internal locale to the user preferredLocale + */ + updateLocale() { + DateUtils.setLocale(this.props.preferredLocale); + } + /** * @param {String} phoneNumber * @returns {String} diff --git a/src/hooks/useReportScrollManager/index.js b/src/hooks/useReportScrollManager/index.js index 0cf09146553c..9a3303504b92 100644 --- a/src/hooks/useReportScrollManager/index.js +++ b/src/hooks/useReportScrollManager/index.js @@ -1,8 +1,8 @@ import {useContext, useCallback} from 'react'; -import ReportScreenContext from '../../pages/home/ReportScreenContext'; +import {ActionListContext} from '../../pages/home/ReportScreenContext'; function useReportScrollManager() { - const {flatListRef} = useContext(ReportScreenContext); + const flatListRef = useContext(ActionListContext); /** * Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because diff --git a/src/hooks/useReportScrollManager/index.native.js b/src/hooks/useReportScrollManager/index.native.js index 35af064cb062..d44a40222ca5 100644 --- a/src/hooks/useReportScrollManager/index.native.js +++ b/src/hooks/useReportScrollManager/index.native.js @@ -1,8 +1,8 @@ import {useContext, useCallback} from 'react'; -import ReportScreenContext from '../../pages/home/ReportScreenContext'; +import {ActionListContext} from '../../pages/home/ReportScreenContext'; function useReportScrollManager() { - const {flatListRef} = useContext(ReportScreenContext); + const flatListRef = useContext(ActionListContext); /** * Scroll to the provided index. diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index b33a1b1b2a73..5c8cbd175082 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -344,6 +344,7 @@ const DateUtils = { subtractMillisecondsFromDateTime, getDateStringFromISOTimestamp, getStatusUntilDate, + setLocale, }; export default DateUtils; diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 87c77e722a5c..0bb0e40382cb 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -310,7 +310,7 @@ function createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs) { workspaceMembersChats.onyxFailureData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${optimisticReport.reportID}`, value: { isLoadingReportActions: false, }, diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 5cf0e51279a9..5b4c1ea56bd3 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -401,43 +401,63 @@ function reportActionsExist(reportID) { * @param {Array} participantAccountIDList The list of accountIDs that are included in a new chat, not including the user creating it */ function openReport(reportID, participantLoginList = [], newReportObject = {}, parentReportActionID = '0', isFromDeepLink = false, participantAccountIDList = []) { - const optimisticReportData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: reportActionsExist(reportID) - ? {} - : { - isLoadingReportActions: true, - isLoadingMoreReportActions: false, - reportName: lodashGet(allReports, [reportID, 'reportName'], CONST.REPORT.DEFAULT_REPORT_NAME), - }, - }; - const reportSuccessData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - isLoadingReportActions: false, - pendingFields: { - createChat: null, + const optimisticReportData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: reportActionsExist(reportID) + ? {} + : { + reportName: lodashGet(allReports, [reportID, 'reportName'], CONST.REPORT.DEFAULT_REPORT_NAME), + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, + value: { + isLoadingReportActions: true, + isLoadingMoreReportActions: false, }, - errorFields: { - createChat: null, + }, + ]; + + const reportSuccessData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + pendingFields: { + createChat: null, + }, + errorFields: { + createChat: null, + }, + isOptimisticReport: false, }, - isOptimisticReport: false, }, - }; - const reportFailureData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - isLoadingReportActions: false, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, + value: { + isLoadingReportActions: false, + }, }, - }; + ]; + + const reportFailureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, + value: { + isLoadingReportActions: false, + }, + }, + ]; const onyxData = { - optimisticData: [optimisticReportData], - successData: [reportSuccessData], - failureData: [reportFailureData], + optimisticData: optimisticReportData, + successData: reportSuccessData, + failureData: reportFailureData, }; const params = { @@ -454,17 +474,17 @@ function openReport(reportID, participantLoginList = [], newReportObject = {}, p // If we open an exist report, but it is not present in Onyx yet, we should change the method to set for this report // and we need data to be available when we navigate to the chat page if (_.isEmpty(ReportUtils.getReport(reportID))) { - optimisticReportData.onyxMethod = Onyx.METHOD.SET; + onyxData.optimisticData[0].onyxMethod = Onyx.METHOD.SET; } // If we are creating a new report, we need to add the optimistic report data and a report action if (!_.isEmpty(newReportObject)) { // Change the method to set for new reports because it doesn't exist yet, is faster, // and we need the data to be available when we navigate to the chat page - optimisticReportData.onyxMethod = Onyx.METHOD.SET; - optimisticReportData.value = { + onyxData.optimisticData[0].onyxMethod = Onyx.METHOD.SET; + onyxData.optimisticData[0].value = { reportName: CONST.REPORT.DEFAULT_REPORT_NAME, - ...optimisticReportData.value, + ...onyxData.optimisticData[0].value, ...newReportObject, pendingFields: { createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, @@ -646,17 +666,23 @@ function reconnect(reportID) { { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + reportName: lodashGet(allReports, [reportID, 'reportName'], CONST.REPORT.DEFAULT_REPORT_NAME), + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, value: { isLoadingReportActions: true, isLoadingMoreReportActions: false, - reportName: lodashGet(allReports, [reportID, 'reportName'], CONST.REPORT.DEFAULT_REPORT_NAME), }, }, ], successData: [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, value: { isLoadingReportActions: false, }, @@ -665,7 +691,7 @@ function reconnect(reportID) { failureData: [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, value: { isLoadingReportActions: false, }, diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index a4145843ab87..e030cd9adf7d 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -25,6 +25,7 @@ import ReportFooter from './report/ReportFooter'; import Banner from '../../components/Banner'; import withLocalize from '../../components/withLocalize'; import reportPropTypes from '../reportPropTypes'; +import reportMetadataPropTypes from '../reportMetadataPropTypes'; import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; import withViewportOffsetTop, {viewportOffsetTopPropTypes} from '../../components/withViewportOffsetTop'; import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; @@ -33,7 +34,7 @@ import getIsReportFullyVisible from '../../libs/getIsReportFullyVisible'; import MoneyRequestHeader from '../../components/MoneyRequestHeader'; import MoneyReportHeader from '../../components/MoneyReportHeader'; import * as ComposerActions from '../../libs/actions/Composer'; -import ReportScreenContext from './ReportScreenContext'; +import {ActionListContext, ReactionListContext} from './ReportScreenContext'; import TaskHeaderActionButton from '../../components/TaskHeaderActionButton'; import DragAndDropProvider from '../../components/DragAndDrop/Provider'; import usePrevious from '../../hooks/usePrevious'; @@ -59,6 +60,9 @@ const propTypes = { /** The report currently being looked at */ report: reportPropTypes, + /** The report metadata loading states */ + reportMetadata: reportMetadataPropTypes, + /** Array of report actions for this report */ reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)), @@ -98,7 +102,10 @@ const defaultProps = { reportActions: [], report: { hasOutstandingIOU: false, + }, + reportMetadata: { isLoadingReportActions: false, + isLoadingMoreReportActions: false, }, isComposerFullSize: false, betas: [], @@ -133,6 +140,7 @@ function ReportScreen({ betas, route, report, + reportMetadata, reportActions, accountManagerReportID, personalDetails, @@ -150,8 +158,6 @@ function ReportScreen({ const flatListRef = useRef(); const reactionListRef = useRef(); const prevReport = usePrevious(report); - - const [skeletonViewContainerHeight, setSkeletonViewContainerHeight] = useState(0); const [isBannerVisible, setIsBannerVisible] = useState(true); const reportID = getReportID(route); @@ -159,7 +165,7 @@ function ReportScreen({ const screenWrapperStyle = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}]; // There are no reportActions at all to display and we are still in the process of loading the next set of actions. - const isLoadingInitialReportActions = _.isEmpty(reportActions) && report.isLoadingReportActions; + const isLoadingInitialReportActions = _.isEmpty(reportActions) && reportMetadata.isLoadingReportActions; const isOptimisticDelete = lodashGet(report, 'statusNum') === CONST.REPORT.STATUS.CLOSED; @@ -310,82 +316,67 @@ function ReportScreen({ ); return ( - - - + + - - {headerView} - {ReportUtils.isTaskReport(report) && isSmallScreenWidth && ReportUtils.isOpenTaskReport(report) && ( - - - - + + {headerView} + {ReportUtils.isTaskReport(report) && isSmallScreenWidth && ReportUtils.isOpenTaskReport(report) && ( + + + + + - - )} - - {Boolean(accountManagerReportID) && ReportUtils.isConciergeChatReport(report) && isBannerVisible && ( - - )} - - { - // Rounding this value for comparison because they can look like this: 411.9999694824219 - const newSkeletonViewContainerHeight = Math.round(event.nativeEvent.layout.height); - - // The height can be 0 if the component unmounts - we are not interested in this value and want to know how much space it - // takes up so we can set the skeleton view container height. - if (newSkeletonViewContainerHeight === 0) { - return; - } - setSkeletonViewContainerHeight(newSkeletonViewContainerHeight); - }} - > - {isReportReadyForDisplay && !isLoadingInitialReportActions && !isLoading && ( - )} + + {Boolean(accountManagerReportID) && ReportUtils.isConciergeChatReport(report) && isBannerVisible && ( + + )} + + + {isReportReadyForDisplay && !isLoadingInitialReportActions && !isLoading && ( + + )} - {/* Note: The report should be allowed to mount even if the initial report actions are not loaded. If we prevent rendering the report while they are loading then - we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */} - {(!isReportReadyForDisplay || isLoadingInitialReportActions || isLoading) && } + {/* Note: The ReportActionsSkeletonView should be allowed to mount even if the initial report actions are not loaded. + If we prevent rendering the report while they are loading then + we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */} + {(!isReportReadyForDisplay || isLoadingInitialReportActions || isLoading) && } - {isReportReadyForDisplay && ( - <> + {isReportReadyForDisplay ? ( - - )} - - {!isReportReadyForDisplay && ( - - )} - - - - - + ) : ( + + )} + + + + + + ); } @@ -434,6 +423,9 @@ export default compose( report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${getReportID(route)}`, }, + reportMetadata: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_METADATA}${getReportID(route)}`, + }, isComposerFullSize: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE}${getReportID(route)}`, }, diff --git a/src/pages/home/ReportScreenContext.js b/src/pages/home/ReportScreenContext.js index 2f79d6ae9432..1e8d30cf7585 100644 --- a/src/pages/home/ReportScreenContext.js +++ b/src/pages/home/ReportScreenContext.js @@ -1,4 +1,6 @@ import {createContext} from 'react'; -const ReportScreenContext = createContext(); -export default ReportScreenContext; +const ActionListContext = createContext(); +const ReactionListContext = createContext(); + +export {ActionListContext, ReactionListContext}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index fae5c518bbfe..f169cb7e66d2 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -64,8 +64,8 @@ import * as PersonalDetailsUtils from '../../../libs/PersonalDetailsUtils'; import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import * as store from '../../../libs/actions/ReimbursementAccount/store'; import * as BankAccounts from '../../../libs/actions/BankAccounts'; +import {ReactionListContext} from '../ReportScreenContext'; import usePrevious from '../../../hooks/usePrevious'; -import ReportScreenContext from '../ReportScreenContext'; import Permissions from '../../../libs/Permissions'; import ReportAttachmentsContext from './ReportAttachmentsContext'; @@ -129,7 +129,7 @@ function ReportActionItem(props) { const [isContextMenuActive, setIsContextMenuActive] = useState(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); const [isHidden, setIsHidden] = useState(false); const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - const {reactionListRef} = useContext(ReportScreenContext); + const reactionListRef = useContext(ReactionListContext); const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); const textInputRef = useRef(); const popoverAnchorRef = useRef(); diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 7f897ee825fb..febf9d809d7f 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -34,6 +34,9 @@ const propTypes = { /** The ID of the most recent IOU report action connected with the shown report */ mostRecentIOUReportActionID: PropTypes.string, + /** The report metadata loading states */ + isLoadingReportActions: PropTypes.bool, + /** Are we loading more report actions? */ isLoadingMoreReportActions: PropTypes.bool, @@ -63,6 +66,7 @@ const defaultProps = { personalDetails: {}, onScroll: () => {}, mostRecentIOUReportActionID: '', + isLoadingReportActions: false, isLoadingMoreReportActions: false, ...withCurrentUserPersonalDetailsDefaultProps, }; @@ -92,6 +96,8 @@ function isMessageUnread(message, lastReadTime) { function ReportActionsList({ report, + isLoadingReportActions, + isLoadingMoreReportActions, sortedReportActions, windowHeight, onScroll, @@ -114,9 +120,9 @@ function ReportActionsList({ const readActionSkipped = useRef(false); const reportActionSize = useRef(sortedReportActions.length); - // Considering that renderItem is enclosed within a useCallback, marking it as "read" twice will retain the value as "true," preventing the useCallback from re-executing. - // However, if we create and listen to an object, it will lead to a new useCallback execution. - const [messageManuallyMarked, setMessageManuallyMarked] = useState({read: false}); + // This state is used to force a re-render when the user manually marks a message as unread + // by using a timestamp you can force re-renders without having to worry about if another message was marked as unread before + const [messageManuallyMarkedUnread, setMessageManuallyMarkedUnread] = useState(0); const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(false); const animatedStyles = useAnimatedStyle(() => ({ opacity: opacity.value, @@ -163,15 +169,14 @@ function ReportActionsList({ useEffect(() => { const didManuallyMarkReportAsUnread = report.lastReadTime < DateUtils.getDBTime() && ReportUtils.isUnread(report); - if (!didManuallyMarkReportAsUnread) { - setMessageManuallyMarked({read: false}); - return; + if (didManuallyMarkReportAsUnread) { + // Clearing the current unread marker so that it can be recalculated + currentUnreadMarker.current = null; + setMessageManuallyMarkedUnread(new Date().getTime()); + } else { + setMessageManuallyMarkedUnread(0); } - // Clearing the current unread marker so that it can be recalculated - currentUnreadMarker.current = null; - setMessageManuallyMarked({read: true}); - // We only care when a new lastReadTime is set in the report // eslint-disable-next-line react-hooks/exhaustive-deps }, [report.lastReadTime]); @@ -230,7 +235,7 @@ function ReportActionsList({ const isCurrentMessageUnread = isMessageUnread(reportAction, report.lastReadTime); shouldDisplayNewMarker = isCurrentMessageUnread && !isMessageUnread(nextMessage, report.lastReadTime); - if (!messageManuallyMarked.read) { + if (!messageManuallyMarkedUnread) { shouldDisplayNewMarker = shouldDisplayNewMarker && reportAction.actorAccountID !== Report.getCurrentUserAccountID(); } const canDisplayMarker = scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD ? reportAction.created < userActiveSince.current : true; @@ -272,7 +277,7 @@ function ReportActionsList({ /> ); }, - [report, hasOutstandingIOU, sortedReportActions, mostRecentIOUReportActionID, messageManuallyMarked], + [report, hasOutstandingIOU, sortedReportActions, mostRecentIOUReportActionID, messageManuallyMarkedUnread], ); // Native mobile does not render updates flatlist the changes even though component did update called. @@ -300,7 +305,7 @@ function ReportActionsList({ onEndReached={loadMoreChats} onEndReachedThreshold={0.75} ListFooterComponent={() => { - if (report.isLoadingMoreReportActions) { + if (isLoadingMoreReportActions) { return ; } @@ -308,7 +313,7 @@ function ReportActionsList({ // skeleton view above the created action in a newly generated optimistic chat or one with not // that many comments. const lastReportAction = _.last(sortedReportActions) || {}; - if (report.isLoadingReportActions && lastReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED) { + if (isLoadingReportActions && lastReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED) { return ( 0); @@ -138,7 +144,7 @@ function ReportActionsView(props) { */ const loadMoreChats = () => { // Only fetch more if we are not already fetching so that we don't initiate duplicate requests. - if (props.report.isLoadingMoreReportActions) { + if (props.isLoadingMoreReportActions) { return; } @@ -185,11 +191,12 @@ function ReportActionsView(props) { onLayout={recordTimeToMeasureItemLayout} sortedReportActions={props.reportActions} mostRecentIOUReportActionID={mostRecentIOUReportActionID.current} - isLoadingMoreReportActions={props.report.isLoadingMoreReportActions} + isLoadingReportActions={props.isLoadingReportActions} + isLoadingMoreReportActions={props.isLoadingMoreReportActions} loadMoreChats={loadMoreChats} policy={props.policy} /> - + ); } @@ -215,11 +222,11 @@ function arePropsEqual(oldProps, newProps) { return false; } - if (oldProps.report.isLoadingMoreReportActions !== newProps.report.isLoadingMoreReportActions) { + if (oldProps.isLoadingMoreReportActions !== newProps.isLoadingMoreReportActions) { return false; } - if (oldProps.report.isLoadingReportActions !== newProps.report.isLoadingReportActions) { + if (oldProps.isLoadingReportActions !== newProps.isLoadingReportActions) { return false; } diff --git a/src/pages/home/report/withReportAndReportActionOrNotFound.js b/src/pages/home/report/withReportAndReportActionOrNotFound.js index 9bf3e73e761c..b4346504b327 100644 --- a/src/pages/home/report/withReportAndReportActionOrNotFound.js +++ b/src/pages/home/report/withReportAndReportActionOrNotFound.js @@ -6,6 +6,7 @@ import getComponentDisplayName from '../../../libs/getComponentDisplayName'; import NotFoundPage from '../../ErrorPage/NotFoundPage'; import ONYXKEYS from '../../../ONYXKEYS'; import reportPropTypes from '../../reportPropTypes'; +import reportMetadataPropTypes from '../../reportMetadataPropTypes'; import reportActionPropTypes from './reportActionPropTypes'; import FullscreenLoadingIndicator from '../../../components/FullscreenLoadingIndicator'; import * as ReportUtils from '../../../libs/ReportUtils'; @@ -23,6 +24,9 @@ export default function (WrappedComponent) { /** The report currently being looked at */ report: reportPropTypes, + /** The report metadata */ + reportMetadata: reportMetadataPropTypes, + /** Array of report actions for this report */ reportActions: PropTypes.shape(reportActionPropTypes), @@ -62,6 +66,10 @@ export default function (WrappedComponent) { forwardedRef: () => {}, reportActions: {}, report: {}, + reportMetadata: { + isLoadingReportActions: false, + isLoadingMoreReportActions: false, + }, policies: {}, betas: [], isLoadingReportData: true, @@ -94,7 +102,7 @@ export default function (WrappedComponent) { // Perform all the loading checks const isLoadingReport = props.isLoadingReportData && (_.isEmpty(props.report) || !props.report.reportID); - const isLoadingReportAction = _.isEmpty(props.reportActions) || (props.report.isLoadingReportActions && _.isEmpty(getReportAction())); + const isLoadingReportAction = _.isEmpty(props.reportActions) || (props.reportMetadata.isLoadingReportActions && _.isEmpty(getReportAction())); const shouldHideReport = !isLoadingReport && (_.isEmpty(props.report) || !props.report.reportID || !ReportUtils.canAccessReport(props.report, props.policies, props.betas)); if ((isLoadingReport || isLoadingReportAction) && !shouldHideReport) { @@ -135,6 +143,9 @@ export default function (WrappedComponent) { report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, }, + reportMetadata: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_METADATA}${route.params.reportID}`, + }, isLoadingReportData: { key: ONYXKEYS.IS_LOADING_REPORT_DATA, }, diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index c38ac9e01ccb..5610ff6061ef 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -1,6 +1,6 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import React from 'react'; -import {View} from 'react-native'; +import {View, InteractionManager} from 'react-native'; import _ from 'underscore'; import PropTypes from 'prop-types'; import styles from '../../../styles/styles'; @@ -77,6 +77,13 @@ class SidebarLinks extends React.PureComponent { SidebarUtils.setIsSidebarLoadedReady(); this.isSidebarLoaded = true; + // Eagerly set the locale on date-fns, it helps navigating to the report screen faster + InteractionManager.runAfterInteractions(() => { + requestAnimationFrame(() => { + this.props.updateLocale(); + }); + }); + let modal = {}; this.unsubscribeOnyxModal = onyxSubscribe({ key: ONYXKEYS.MODAL, diff --git a/src/pages/reportMetadataPropTypes.js b/src/pages/reportMetadataPropTypes.js new file mode 100644 index 000000000000..a75d71aef7b3 --- /dev/null +++ b/src/pages/reportMetadataPropTypes.js @@ -0,0 +1,9 @@ +import PropTypes from 'prop-types'; + +export default PropTypes.shape({ + /** Are we loading more report actions? */ + isLoadingMoreReportActions: PropTypes.bool, + + /** Flag to check if the report actions data are loading */ + isLoadingReportActions: PropTypes.bool, +}); diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js index da90e0a4ac5c..a2c41b5a8147 100644 --- a/src/pages/reportPropTypes.js +++ b/src/pages/reportPropTypes.js @@ -13,12 +13,6 @@ export default PropTypes.shape({ /** List of icons for report participants */ icons: PropTypes.arrayOf(avatarPropTypes), - /** Are we loading more report actions? */ - isLoadingMoreReportActions: PropTypes.bool, - - /** Flag to check if the report actions data are loading */ - isLoadingReportActions: PropTypes.bool, - /** Whether the user is not an admin of policyExpenseChat chat */ isOwnPolicyExpenseChat: PropTypes.bool, diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 8660837ba874..7ba0f6c69621 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -12,12 +12,6 @@ type Report = { /** List of icons for report participants */ icons?: OnyxCommon.Icon[]; - /** Are we loading more report actions? */ - isLoadingMoreReportActions?: boolean; - - /** Flag to check if the report actions data are loading */ - isLoadingReportActions?: boolean; - /** Whether the user is not an admin of policyExpenseChat chat */ isOwnPolicyExpenseChat?: boolean; diff --git a/src/types/onyx/ReportMetadata.ts b/src/types/onyx/ReportMetadata.ts new file mode 100644 index 000000000000..3e389c8cff4f --- /dev/null +++ b/src/types/onyx/ReportMetadata.ts @@ -0,0 +1,9 @@ +type ReportMetadata = { + /** Are we loading more report actions? */ + isLoadingMoreReportActions?: boolean; + + /** Flag to check if the report actions data are loading */ + isLoadingReportActions?: boolean; +}; + +export default ReportMetadata; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index a7bbaf848265..01bd1135cf9c 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -39,6 +39,7 @@ import PolicyMember from './PolicyMember'; import Policy from './Policy'; import PolicyCategory from './PolicyCategory'; import Report from './Report'; +import ReportMetadata from './ReportMetadata'; import ReportAction from './ReportAction'; import ReportActionReactions from './ReportActionReactions'; import SecurityGroup from './SecurityGroup'; @@ -90,6 +91,7 @@ export type { Policy, PolicyCategory, Report, + ReportMetadata, ReportAction, ReportActionReactions, SecurityGroup,