Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New skeleton loader for LHN #14456

Merged
merged 37 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0c38b1b
show skeleton instead of activity indicator while loading the app
bernhardoj Jan 21, 2023
a430364
only show back button on small screen
bernhardoj Jan 21, 2023
ea87754
move around the loading state
bernhardoj Jan 21, 2023
5a93cec
fix lint
bernhardoj Jan 21, 2023
3574120
fix lint
bernhardoj Jan 21, 2023
60011ae
update skeleton line position
bernhardoj Jan 22, 2023
f2f5c25
set the params to the specific route
bernhardoj Jan 23, 2023
27c99ef
fix lint
bernhardoj Jan 23, 2023
efbfd4d
undisable header buttons
bernhardoj Jan 24, 2023
9ba9aee
hide new workspace menu while loading
bernhardoj Jan 26, 2023
5fc5921
add animation for popover menu item
bernhardoj Jan 26, 2023
18775b1
remove key
bernhardoj Jan 26, 2023
daa31dc
Merge branch 'main' into feat/12698
bernhardoj Feb 1, 2023
e7cee56
Merge branch 'main' into feat/12698
bernhardoj Feb 2, 2023
10f2708
Merge branch 'main' into feat/12698
bernhardoj Feb 3, 2023
fb4487d
Merge branch 'main' into feat/12698
bernhardoj Feb 8, 2023
e396b1b
Merge branch 'main' into feat/12698
bernhardoj Feb 9, 2023
36a6181
fix open report is called on existing report
bernhardoj Feb 11, 2023
f07c457
Merge branch 'main' into feat/12698
bernhardoj Feb 11, 2023
733c0f8
add comment
bernhardoj Feb 11, 2023
322d280
Merge branch 'main' into feat/12698
bernhardoj Feb 18, 2023
1e2ff6c
revert animation
bernhardoj Feb 20, 2023
33140fb
fix lint
bernhardoj Feb 20, 2023
d8f540c
Merge branch 'main' into feat/12698
bernhardoj Feb 24, 2023
4abf178
fix inconsistent skeleton height
bernhardoj Mar 2, 2023
9f7df79
fix lint
bernhardoj Mar 2, 2023
cea7062
Merge branch 'main' into feat/12698
bernhardoj Mar 2, 2023
e2f63db
Revert "fix inconsistent skeleton height"
bernhardoj Mar 2, 2023
4b0dbef
fix inconsistent skeleton height
bernhardoj Mar 2, 2023
d080b57
use ceil to get the number of skeleton
bernhardoj Mar 2, 2023
22249d2
move the height calculation to the skeleton component
bernhardoj Mar 3, 2023
bbc9a66
fix lint
bernhardoj Mar 3, 2023
3a660a5
add comment
bernhardoj Mar 4, 2023
829b20e
change variable name
bernhardoj Mar 7, 2023
ad77e83
Merge branch 'main' into feat/12698
bernhardoj Mar 14, 2023
5a8363c
change variable name
bernhardoj Mar 14, 2023
45c2706
Merge branch 'main' into feat/12698
bernhardoj Mar 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ const CONST = {
3: 100,
},
},
LHN_SKELETON_VIEW_ITEM_HEIGHT: 64,
EXPENSIFY_PARTNER_NAME: 'expensify.com',
EMAIL: {
CONCIERGE: 'concierge@expensify.com',
Expand Down
97 changes: 97 additions & 0 deletions src/components/LHNSkeletonView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import {Rect, Circle} from 'react-native-svg';
import SkeletonViewContentLoader from 'react-content-loader/native';
import CONST from '../CONST';
import themeColors from '../styles/themes/default';
import styles from '../styles/styles';

const propTypes = {
/** Whether to animate the skeleton view */
shouldAnimate: PropTypes.bool,
};

const defaultTypes = {
shouldAnimate: true,
};

class LHNSkeletonView extends React.Component {
constructor(props) {
super(props);
this.state = {
skeletonViewItems: [],
};
}

/**
* Generate the skeleton view items.
*
* @param {Number} numItems
*/
generateSkeletonViewItems(numItems) {
if (this.state.skeletonViewItems.length === numItems) {
return;
}

if (this.state.skeletonViewItems.length > numItems) {
this.setState(prevState => ({
skeletonViewItems: prevState.skeletonViewItems.slice(0, numItems),
}));
return;
}

const skeletonViewItems = [];
for (let i = this.state.skeletonViewItems.length; i < numItems; i++) {
const step = i % 3;
let lineWidth;
switch (step) {
case 0:
lineWidth = '100%';
break;
case 1:
lineWidth = '50%';
break;
default:
lineWidth = '25%';
}
skeletonViewItems.push(
<SkeletonViewContentLoader
key={`skeletonViewItems${i}`}
animate={this.props.shouldAnimate}
height={CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT}
backgroundColor={themeColors.borderLighter}
foregroundColor={themeColors.border}
style={styles.mr5}
>
<Circle cx="40" cy="32" r="20" />
<Rect x="72" y="18" width="20%" height="8" />
<Rect x="72" y="38" width={lineWidth} height="8" />
</SkeletonViewContentLoader>,
);
}

this.setState(prevState => ({
skeletonViewItems: [...prevState.skeletonViewItems, ...skeletonViewItems],
}));
}

render() {
return (
<View
style={styles.flex1}
onLayout={(event) => {
const numItems = Math.ceil(event.nativeEvent.layout.height / CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT);
this.generateSkeletonViewItems(numItems);
}}
>
<View>{this.state.skeletonViewItems}</View>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @bernhardoj, coming from #33163, Is there any specific reason that we didn't render SkeletonViewContentLoader directly via an array map function here, but pre-calculate and stored in this.state.skeletonViewItems in onLayout callback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it based on this suggestion. I think it's just for optimization.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bernhardoj

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @roryabraham, do you mind sharing the reason why you prefer to generate skeletonViewItems and store it in the component's state? In a first glance, I think it's because of optimization as @bernhardoj said above but I usually see the pattern where we generate a list/array of items (with a key) in component render function, leaving the rendering optimization part for React. Thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ just in case you missed this message @roryabraham

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answered here

</View>
);
}
}

LHNSkeletonView.propTypes = propTypes;
LHNSkeletonView.defaultProps = defaultTypes;

export default LHNSkeletonView;
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import styles from '../../styles/styles';
const propTypes = {
/** Number of rows to show in Skeleton UI block */
numberOfRows: PropTypes.number.isRequired,
animate: PropTypes.bool,
shouldAnimate: PropTypes.bool,
};

const defaultTypes = {
animate: true,
shouldAnimate: true,
};

const SkeletonViewLines = props => (
<SkeletonViewContentLoader
animate={props.animate}
animate={props.shouldAnimate}
height={CONST.CHAT_SKELETON_VIEW.HEIGHT_FOR_ROW_COUNT[props.numberOfRows]}
backgroundColor={themeColors.highlightBG}
foregroundColor={themeColors.border}
Expand Down
10 changes: 5 additions & 5 deletions src/components/ReportActionsSkeletonView/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ const propTypes = {
containerHeight: PropTypes.number.isRequired,

/** Whether to animate the skeleton view */
animate: PropTypes.bool,
shouldAnimate: PropTypes.bool,
};

const defaultProps = {
animate: true,
shouldAnimate: true,
};

const ReportActionsSkeletonView = (props) => {
Expand All @@ -23,13 +23,13 @@ const ReportActionsSkeletonView = (props) => {
const iconIndex = (index + 1) % 4;
switch (iconIndex) {
case 2:
skeletonViewLines.push(<SkeletonViewLines animate={props.animate} numberOfRows={2} key={`skeletonViewLines${index}`} />);
skeletonViewLines.push(<SkeletonViewLines shouldAnimate={props.shouldAnimate} numberOfRows={2} key={`skeletonViewLines${index}`} />);
break;
case 0:
skeletonViewLines.push(<SkeletonViewLines animate={props.animate} numberOfRows={3} key={`skeletonViewLines${index}`} />);
skeletonViewLines.push(<SkeletonViewLines shouldAnimate={props.shouldAnimate} numberOfRows={3} key={`skeletonViewLines${index}`} />);
break;
default:
skeletonViewLines.push(<SkeletonViewLines animate={props.animate} numberOfRows={1} key={`skeletonViewLines${index}`} />);
skeletonViewLines.push(<SkeletonViewLines shouldAnimate={props.shouldAnimate} numberOfRows={1} key={`skeletonViewLines${index}`} />);
}
}
return <>{skeletonViewLines}</>;
Expand Down
20 changes: 11 additions & 9 deletions src/components/ReportHeaderSkeletonView.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,26 @@ import themeColors from '../styles/themes/default';

const propTypes = {
...windowDimensionsPropTypes,
animate: PropTypes.bool,
shouldAnimate: PropTypes.bool,
};

const defaultProps = {
animate: true,
shouldAnimate: true,
};

const ReportHeaderSkeletonView = props => (
<View style={[styles.appContentHeader]}>
<View style={[styles.appContentHeaderTitle, !props.isSmallScreenWidth && styles.pl5]}>
<Pressable
onPress={() => {}}
style={[styles.LHNToggle]}
>
<Icon src={Expensicons.BackArrow} />
</Pressable>
{props.isSmallScreenWidth && (
<Pressable
onPress={() => {}}
bernhardoj marked this conversation as resolved.
Show resolved Hide resolved
style={[styles.LHNToggle]}
>
<Icon src={Expensicons.BackArrow} />
</Pressable>
)}
<SkeletonViewContentLoader
animate={props.animate}
animate={props.shouldAnimate}
width={styles.w100.width}
height={variables.contentHeaderHeight}
backgroundColor={themeColors.highlightBG}
Expand Down
21 changes: 12 additions & 9 deletions src/libs/Navigation/AppNavigator/MainDrawerNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';

import FullScreenLoadingIndicator from '../../../components/FullscreenLoadingIndicator';
import ONYXKEYS from '../../../ONYXKEYS';
import SCREENS from '../../../SCREENS';
import Permissions from '../../Permissions';
Expand All @@ -17,6 +16,8 @@ import SidebarScreen from '../../../pages/home/sidebar/SidebarScreen';
import BaseDrawerNavigator from './BaseDrawerNavigator';
import * as ReportUtils from '../../ReportUtils';
import reportPropTypes from '../../../pages/reportPropTypes';
import Navigation from '../Navigation';
import {withNavigationPropTypes} from '../../../components/withNavigation';

const propTypes = {
/** Available reports that would be displayed in this navigator */
Expand All @@ -39,6 +40,8 @@ const propTypes = {
openOnAdminRoom: PropTypes.bool,
}),
}).isRequired,

...withNavigationPropTypes,
};

const defaultProps = {
Expand Down Expand Up @@ -91,6 +94,14 @@ class MainDrawerNavigator extends Component {
return false;
}

// Update the report screen initial params after the reports are available
// to show the correct report instead of the "no access" report.
// https://github.com/Expensify/App/issues/12698#issuecomment-1352632883
if (!this.initialParams.reportID) {
const state = this.props.navigation.getState();
const reportScreenKey = lodashGet(state, 'routes[0].state.routes[0].key', '');
Navigation.setParams(initialNextParams, reportScreenKey);
bernhardoj marked this conversation as resolved.
Show resolved Hide resolved
}
this.initialParams = initialNextParams;
return true;
}
Expand All @@ -105,14 +116,6 @@ class MainDrawerNavigator extends Component {
}

render() {
// Wait until reports are fetched and there is a reportID in initialParams
if (!this.initialParams.reportID) {
return <FullScreenLoadingIndicator />;
}

// After the app initializes and reports are available the home navigation is mounted
// This way routing information is updated (if needed) based on the initial report ID resolved.
// This is usually needed after login/create account and re-launches
return (
<BaseDrawerNavigator
drawerContent={({navigation, state}) => {
Expand Down
14 changes: 14 additions & 0 deletions src/libs/Navigation/Navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,19 @@ function navigate(route = ROUTES.HOME) {
linkTo(navigationRef.current, route);
}

/**
* Update route params for the specified route.
*
* @param {Object} params
* @param {String} routeKey
*/
function setParams(params, routeKey) {
navigationRef.current.dispatch({
...CommonActions.setParams(params),
source: routeKey,
});
}

/**
* Dismisses a screen presented modally and returns us back to the previous view.
*
Expand Down Expand Up @@ -296,6 +309,7 @@ function setIsReportScreenIsReady() {
export default {
canNavigate,
navigate,
setParams,
dismissModal,
isActiveRoute,
getActiveRoute,
Expand Down
Loading
Loading