Skip to content

Commit

Permalink
Merge pull request #26772 from margelo/osp/further-report-screen-opti…
Browse files Browse the repository at this point in the history
…mizations

Use initialValue in withOnyx and other optimizations for ReportScreen
  • Loading branch information
mountiny authored Sep 28, 2023
2 parents 01142a7 + 3aaef2f commit 86c0658
Show file tree
Hide file tree
Showing 30 changed files with 508 additions and 372 deletions.
2 changes: 2 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,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_',
Expand Down Expand Up @@ -380,6 +381,7 @@ type OnyxValues = {
[ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember;
[ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record<string, number>;
[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;
Expand Down
1 change: 1 addition & 0 deletions src/components/ExceededCommentLength.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ ExceededCommentLength.displayName = 'ExceededCommentLength';
export default withOnyx({
comment: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`,
initialValue: '',
},
})(ExceededCommentLength);
5 changes: 3 additions & 2 deletions src/components/Reactions/ReportActionItemEmojiReactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/components/ReportActionItem/TaskPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ export default compose(
withOnyx({
taskReport: {
key: ({taskReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
initialValue: {},
},
personalDetailsList: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
initialValue: {},
},
}),
)(TaskPreview);
7 changes: 2 additions & 5 deletions src/components/ReportActionsSkeletonView/index.js
Original file line number Diff line number Diff line change
@@ -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,
};
Expand All @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions src/components/withLocalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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}
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useReportScrollManager/index.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useReportScrollManager/index.native.js
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/libs/DateUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ const DateUtils = {
subtractMillisecondsFromDateTime,
getDateStringFromISOTimestamp,
getStatusUntilDate,
setLocale,
isToday,
isTomorrow,
isYesterday,
Expand Down
121 changes: 121 additions & 0 deletions src/libs/Navigation/AppNavigator/ReportScreenIDSetter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {useEffect} from 'react';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';
import ONYXKEYS from '../../../ONYXKEYS';
import * as ReportUtils from '../../ReportUtils';
import reportPropTypes from '../../../pages/reportPropTypes';
import {withNavigationPropTypes} from '../../../components/withNavigation';
import * as App from '../../actions/App';
import usePermissions from '../../../hooks/usePermissions';
import CONST from '../../../CONST';
import Navigation from '../Navigation';

const propTypes = {
/** Available reports that would be displayed in this navigator */
reports: PropTypes.objectOf(reportPropTypes),

/** The policies which the user has access to */
policies: PropTypes.objectOf(
PropTypes.shape({
/** The policy name */
name: PropTypes.string,

/** The type of the policy */
type: PropTypes.string,
}),
),

isFirstTimeNewExpensifyUser: PropTypes.bool,

/** Navigation route context info provided by react navigation */
route: PropTypes.shape({
/** Route specific parameters used on this screen */
params: PropTypes.shape({
/** If the admin room should be opened */
openOnAdminRoom: PropTypes.bool,

/** The ID of the report this screen should display */
reportID: PropTypes.string,
}),
}).isRequired,

...withNavigationPropTypes,
};

const defaultProps = {
reports: {},
policies: {},
isFirstTimeNewExpensifyUser: false,
};

/**
* Get the most recently accessed report for the user
*
* @param {Object} reports
* @param {Boolean} ignoreDefaultRooms
* @param {Object} policies
* @param {Boolean} isFirstTimeNewExpensifyUser
* @param {Boolean} openOnAdminRoom
* @returns {Number}
*/
const getLastAccessedReportID = (reports, ignoreDefaultRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom) => {
// If deeplink url is of an attachment, we should show the report that the attachment comes from.
const currentRoute = Navigation.getActiveRoute();
const matches = CONST.REGEX.ATTACHMENT_ROUTE.exec(currentRoute);
const reportID = lodashGet(matches, 1, null);
if (reportID) {
return reportID;
}

const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom);

return lodashGet(lastReport, 'reportID');
};

// This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params
function ReportScreenIDSetter({route, reports, policies, isFirstTimeNewExpensifyUser, navigation}) {
const {canUseDefaultRooms} = usePermissions();

useEffect(() => {
// Don't update if there is a reportID in the params already
if (lodashGet(route, 'params.reportID', null)) {
App.confirmReadyToOpenApp();
return;
}

// If there is no reportID in route, try to find last accessed and use it for setParams
const reportID = getLastAccessedReportID(reports, !canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, lodashGet(route, 'params.openOnAdminRoom', false));

// It's possible that reports aren't fully loaded yet
// in that case the reportID is undefined
if (reportID) {
navigation.setParams({reportID: String(reportID)});
} else {
App.confirmReadyToOpenApp();
}
}, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser]);

// The ReportScreen without the reportID set will display a skeleton
// until the reportID is loaded and set in the route param
return null;
}

ReportScreenIDSetter.propTypes = propTypes;
ReportScreenIDSetter.defaultProps = defaultProps;
ReportScreenIDSetter.displayName = 'ReportScreenIDSetter';

export default withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
allowStaleData: true,
},
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
allowStaleData: true,
},
isFirstTimeNewExpensifyUser: {
key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER,
initialValue: false,
},
})(ReportScreenIDSetter);
112 changes: 14 additions & 98 deletions src/libs/Navigation/AppNavigator/ReportScreenWrapper.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,10 @@
import React, {useEffect} from 'react';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';

import ONYXKEYS from '../../../ONYXKEYS';

import ReportScreen from '../../../pages/home/ReportScreen';
import * as ReportUtils from '../../ReportUtils';
import reportPropTypes from '../../../pages/reportPropTypes';
import React from 'react';
import {withNavigationPropTypes} from '../../../components/withNavigation';
import * as App from '../../actions/App';
import usePermissions from '../../../hooks/usePermissions';
import CONST from '../../../CONST';
import Navigation from '../Navigation';
import ReportScreen from '../../../pages/home/ReportScreen';
import ReportScreenIDSetter from './ReportScreenIDSetter';

const propTypes = {
/** Available reports that would be displayed in this navigator */
reports: PropTypes.objectOf(reportPropTypes),

/** The policies which the user has access to */
policies: PropTypes.objectOf(
PropTypes.shape({
/** The policy name */
name: PropTypes.string,

/** The type of the policy */
type: PropTypes.string,
}),
),

isFirstTimeNewExpensifyUser: PropTypes.bool,

/** Navigation route context info provided by react navigation */
route: PropTypes.shape({
/** Route specific parameters used on this screen */
Expand All @@ -46,82 +20,24 @@ const propTypes = {
...withNavigationPropTypes,
};

const defaultProps = {
reports: {},
policies: {},
isFirstTimeNewExpensifyUser: false,
};

/**
* Get the most recently accessed report for the user
*
* @param {Object} reports
* @param {Boolean} [ignoreDefaultRooms]
* @param {Object} policies
* @param {Boolean} isFirstTimeNewExpensifyUser
* @param {Boolean} openOnAdminRoom
* @returns {Number}
*/
const getLastAccessedReportID = (reports, ignoreDefaultRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom) => {
// If deeplink url is of an attachment, we should show the report that the attachment comes from.
const currentRoute = Navigation.getActiveRoute();
const matches = CONST.REGEX.ATTACHMENT_ROUTE.exec(currentRoute);
const reportID = lodashGet(matches, 1, null);
if (reportID) {
return reportID;
}

const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom);

return lodashGet(lastReport, 'reportID');
};
const defaultProps = {};

// This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params
function ReportScreenWrapper(props) {
const {canUseDefaultRooms} = usePermissions();

useEffect(() => {
// Don't update if there is a reportID in the params already
if (lodashGet(props.route, 'params.reportID', null)) {
App.confirmReadyToOpenApp();
return;
}

// If there is no reportID in route, try to find last accessed and use it for setParams
const reportID = getLastAccessedReportID(
props.reports,
!canUseDefaultRooms,
props.policies,
props.isFirstTimeNewExpensifyUser,
lodashGet(props.route, 'params.openOnAdminRoom', false),
);

// It's possible that props.reports aren't fully loaded yet
// in that case the reportID is undefined
if (reportID) {
props.navigation.setParams({reportID: String(reportID)});
} else {
App.confirmReadyToOpenApp();
}
}, [props.route, props.navigation, props.reports, canUseDefaultRooms, props.policies, props.isFirstTimeNewExpensifyUser]);

// The ReportScreen without the reportID set will display a skeleton
// until the reportID is loaded and set in the route param
return <ReportScreen route={props.route} />;
return (
<>
<ReportScreen route={props.route} />
<ReportScreenIDSetter
route={props.route}
navigation={props.navigation}
/>
</>
);
}

ReportScreenWrapper.propTypes = propTypes;
ReportScreenWrapper.defaultProps = defaultProps;
ReportScreenWrapper.displayName = 'ReportScreenWrapper';

export default withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
},
isFirstTimeNewExpensifyUser: {
key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER,
},
})(ReportScreenWrapper);
export default ReportScreenWrapper;
Loading

0 comments on commit 86c0658

Please sign in to comment.