Skip to content

Commit

Permalink
Merge pull request Expensify#8042 from mananjadhav/feat/ghost-screen
Browse files Browse the repository at this point in the history
feat: Added Skeleton UI to chats
  • Loading branch information
Luke9389 authored Jul 29, 2022
2 parents f0ead6f + ab2189f commit a3859da
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 137 deletions.
1 change: 1 addition & 0 deletions config/webpack/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({
alias: {
'react-native-config': 'react-web-config',
'react-native$': 'react-native-web',
'react-content-loader/native': 'react-content-loader',
},

// React Native libraries may have web-specific module implementations that appear with the extension `.web.js`
Expand Down
145 changes: 75 additions & 70 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"pusher-js": "^7.0.6",
"react": "^17.0.2",
"react-collapse": "^5.1.0",
"react-content-loader": "^6.1.0",
"react-dom": "^17.0.2",
"react-native": "0.66.4",
"react-native-collapsible": "^1.6.0",
Expand Down
10 changes: 8 additions & 2 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,15 @@ const CONST = {
NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT: 300,
EMOJI_PICKER_ITEM_HEIGHT: 40,
EMOJI_PICKER_HEADER_HEIGHT: 38,

COMPOSER_MAX_HEIGHT: 125,

CHAT_SKELETON_VIEW: {
AVERAGE_ROW_HEIGHT: 80,
HEIGHT_FOR_ROW_COUNT: {
1: 60,
2: 80,
3: 100,
},
},
EMAIL: {
CONCIERGE: 'concierge@expensify.com',
HELP: 'help@expensify.com',
Expand Down
3 changes: 2 additions & 1 deletion src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ export default {
POLICY: 'policy_',
REPORTS_WITH_DRAFT: 'reportWithDraft_',
REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_',
IS_LOADING_REPORT_ACTIONS: 'isLoadingReportActions_',
IS_LOADING_INITIAL_REPORT_ACTIONS: 'isLoadingInitialReportActions_',
IS_LOADING_MORE_REPORT_ACTIONS: 'isLoadingMoreReportActions_',
POLICY_MEMBER_LIST: 'policyMemberList_',
},

Expand Down
26 changes: 26 additions & 0 deletions src/components/ReportActionsSkeletonView/SkeletonViewLines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Rect, Circle} from 'react-native-svg';
import SkeletonViewContentLoader from 'react-content-loader/native';
import CONST from '../../CONST';

const propTypes = {
/** Number of rows to show in Skeleton UI block */
numberOfRows: PropTypes.number.isRequired,
};

const SkeletonViewLines = props => (
<SkeletonViewContentLoader
height={CONST.CHAT_SKELETON_VIEW.HEIGHT_FOR_ROW_COUNT[props.numberOfRows]}
>
<Circle cx="40" cy="26" r="20" />
<Rect x="67" y="11" width="20%" height="8" />
<Rect x="67" y="31" width="90%" height="8" />
{props.numberOfRows > 1 && <Rect x="67" y="51" width="50%" height="8" />}
{props.numberOfRows > 2 && <Rect x="67" y="71" width="50%" height="8" />}
</SkeletonViewContentLoader>
);

SkeletonViewLines.displayName = 'SkeletonViewLines';
SkeletonViewLines.propTypes = propTypes;
export default SkeletonViewLines;
33 changes: 33 additions & 0 deletions src/components/ReportActionsSkeletonView/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import SkeletonViewLines from './SkeletonViewLines';
import CONST from '../../CONST';

const propTypes = {
/** Height of the container component */
containerHeight: PropTypes.number.isRequired,
};

const ReportActionsSkeletonView = (props) => {
// Determines the number of content items based on container height
const possibleVisibleContentItems = Math.floor(props.containerHeight / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT);
const skeletonViewLines = [];
for (let index = 0; index < possibleVisibleContentItems; index++) {
const iconIndex = (index + 1) % 4;
switch (iconIndex) {
case 2:
skeletonViewLines.push(<SkeletonViewLines numberOfRows={2} key={`skeletonViewLines${index}`} />);
break;
case 0:
skeletonViewLines.push(<SkeletonViewLines numberOfRows={3} key={`skeletonViewLines${index}`} />);
break;
default:
skeletonViewLines.push(<SkeletonViewLines numberOfRows={1} key={`skeletonViewLines${index}`} />);
}
}
return <>{skeletonViewLines}</>;
};

ReportActionsSkeletonView.displayName = 'ReportActionsSkeletonView';
ReportActionsSkeletonView.propTypes = propTypes;
export default ReportActionsSkeletonView;
2 changes: 1 addition & 1 deletion src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ function subscribeToPolicyEvents() {
if (!_.isEmpty(policyExpenseChatIDs)) {
Report.fetchChatReportsByIDs(policyExpenseChatIDs);
_.each(policyExpenseChatIDs, (reportID) => {
Report.fetchActions(reportID);
Report.fetchInitialActions(reportID);
});
}

Expand Down
19 changes: 15 additions & 4 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,17 @@ function fetchActions(reportID) {
});
}

/**
* Get the initial actions of a report
*
* @param {Number} reportID
*/
function fetchInitialActions(reportID) {
Onyx.set(`${ONYXKEYS.COLLECTION.IS_LOADING_INITIAL_REPORT_ACTIONS}${reportID}`, true);
fetchActions(reportID)
.finally(() => Onyx.set(`${ONYXKEYS.COLLECTION.IS_LOADING_INITIAL_REPORT_ACTIONS}${reportID}`, false));
}

/**
* Get all of our reports
*
Expand Down Expand Up @@ -1073,17 +1084,17 @@ function readOldestAction(reportID, oldestActionSequenceNumber) {
{
optimisticData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.IS_LOADING_REPORT_ACTIONS}${reportID}`,
key: `${ONYXKEYS.COLLECTION.IS_LOADING_MORE_REPORT_ACTIONS}${reportID}`,
value: true,
}],
successData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.IS_LOADING_REPORT_ACTIONS}${reportID}`,
key: `${ONYXKEYS.COLLECTION.IS_LOADING_MORE_REPORT_ACTIONS}${reportID}`,
value: false,
}],
failureData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.IS_LOADING_REPORT_ACTIONS}${reportID}`,
key: `${ONYXKEYS.COLLECTION.IS_LOADING_MORE_REPORT_ACTIONS}${reportID}`,
value: false,
}],
});
Expand Down Expand Up @@ -1574,7 +1585,6 @@ Onyx.connect({

export {
fetchAllReports,
fetchActions,
fetchOrCreateChatReport,
fetchChatReportsByIDs,
fetchIOUReportByID,
Expand All @@ -1598,6 +1608,7 @@ export {
navigateToConciergeChat,
handleInaccessibleReport,
setReportWithDraft,
fetchInitialActions,
createPolicyRoom,
renameReport,
setIsComposerFullSize,
Expand Down
53 changes: 27 additions & 26 deletions src/pages/home/ReportScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import ReportActionCompose from './report/ReportActionCompose';
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
import SwipeableView from '../../components/SwipeableView';
import CONST from '../../CONST';
import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator';
import ReportActionsSkeletonView from '../../components/ReportActionsSkeletonView';
import reportActionPropTypes from './report/reportActionPropTypes';
import ArchivedReportFooter from '../../components/ArchivedReportFooter';
import toggleReportActionComposeView from '../../libs/toggleReportActionComposeView';
Expand Down Expand Up @@ -66,6 +66,9 @@ const propTypes = {
/** Beta features list */
betas: PropTypes.arrayOf(PropTypes.string),

/** Flag to check if the initial report actions data are loading */
isLoadingInitialReportActions: PropTypes.bool,

/** The policies which the user has access to */
policies: PropTypes.objectOf(PropTypes.shape({
/** The policy name */
Expand All @@ -89,6 +92,7 @@ const defaultProps = {
},
isComposerFullSize: false,
betas: [],
isLoadingInitialReportActions: false,
};

/**
Expand All @@ -112,13 +116,12 @@ class ReportScreen extends React.Component {
this.viewportOffsetTop = this.updateViewportOffsetTop.bind(this);

this.state = {
isLoading: true,
skeletonViewContainerHeight: 0,
viewportOffsetTop: 0,
};
}

componentDidMount() {
this.prepareTransition();
this.storeCurrentlyViewedReport();
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', this.viewportOffsetTop);
Expand All @@ -129,8 +132,6 @@ class ReportScreen extends React.Component {
if (this.props.route.params.reportID === prevProps.route.params.reportID) {
return;
}

this.prepareTransition();
this.storeCurrentlyViewedReport();
}

Expand Down Expand Up @@ -162,16 +163,7 @@ class ReportScreen extends React.Component {
* @returns {Boolean}
*/
shouldShowLoader() {
return this.state.isLoading || !getReportID(this.props.route);
}

/**
* Configures a small loading transition and proceeds with rendering available data
*/
prepareTransition() {
this.setState({isLoading: true});
clearTimeout(this.loadingTimerId);
this.loadingTimerId = setTimeout(() => this.setState({isLoading: false}), 0);
return !getReportID(this.props.route) || (_.isEmpty(this.props.reportActions) && this.props.isLoadingInitialReportActions);
}

/**
Expand Down Expand Up @@ -212,7 +204,6 @@ class ReportScreen extends React.Component {
if (isArchivedRoom) {
reportClosedAction = lodashFindLast(this.props.reportActions, action => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED);
}

return (
<ScreenWrapper style={[styles.appContent, styles.flex1, {marginTop: this.state.viewportOffsetTop}]}>
<KeyboardAvoidingView>
Expand All @@ -224,17 +215,23 @@ class ReportScreen extends React.Component {
<View
nativeID={CONST.REPORT.DROP_NATIVE_ID}
style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]}
onLayout={event => this.setState({skeletonViewContainerHeight: event.nativeEvent.layout.height})}
>
{this.shouldShowLoader() && <FullScreenLoadingIndicator />}
{!this.shouldShowLoader() && (
<ReportActionsView
reportID={reportID}
reportActions={this.props.reportActions}
report={this.props.report}
session={this.props.session}
isComposerFullSize={this.props.isComposerFullSize}
/>
)}
{this.shouldShowLoader()
? (
<ReportActionsSkeletonView
containerHeight={this.state.skeletonViewContainerHeight}
/>
)
: (
<ReportActionsView
reportID={reportID}
reportActions={this.props.reportActions}
report={this.props.report}
session={this.props.session}
isComposerFullSize={this.props.isComposerFullSize}
/>
)}
{(isArchivedRoom || this.props.session.shouldShowComposeInput) && (
<View style={[styles.chatFooter, this.props.isComposerFullSize && styles.chatFooterFullCompose]}>
{
Expand Down Expand Up @@ -288,6 +285,10 @@ export default withOnyx({
betas: {
key: ONYXKEYS.BETAS,
},
isLoadingInitialReportActions: {
key: ({route}) => `${ONYXKEYS.COLLECTION.IS_LOADING_INITIAL_REPORT_ACTIONS}${getReportID(route)}`,
initWithStoredValues: false,
},
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
},
Expand Down
Loading

0 comments on commit a3859da

Please sign in to comment.