From 47fd8b747491f82cb7926b1efff3aa9a6477ff14 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Sun, 12 Apr 2020 17:05:38 +0200 Subject: [PATCH] Improved list update performance --- src/components/Lists/ProxiwashListItem.js | 222 +++++++++++------- .../Lists/ProxiwashSectionHeader.js | 69 ++++++ src/constants/ProxiwashConstants.js | 8 +- src/screens/Proxiwash/ProxiwashScreen.js | 130 ++-------- src/screens/Websites/AmicaleWebsiteScreen.js | 1 - src/utils/Notifications.js | 4 +- 6 files changed, 244 insertions(+), 190 deletions(-) create mode 100644 src/components/Lists/ProxiwashSectionHeader.js diff --git a/src/components/Lists/ProxiwashListItem.js b/src/components/Lists/ProxiwashListItem.js index 0c79f58..4b78cfd 100644 --- a/src/components/Lists/ProxiwashListItem.js +++ b/src/components/Lists/ProxiwashListItem.js @@ -1,107 +1,169 @@ import * as React from 'react'; -import {Avatar, Card, Text, withTheme} from 'react-native-paper'; +import {Avatar, List, ProgressBar, Surface, Text, withTheme} from 'react-native-paper'; import {StyleSheet, View} from "react-native"; import ProxiwashConstants from "../../constants/ProxiwashConstants"; +import i18n from "i18n-js"; +import AprilFoolsManager from "../../managers/AprilFoolsManager"; + +type Props = { + item: Object, + onPress: Function, + isWatched: boolean, + isDryer: boolean, + height: number, +} /** * Component used to display a proxiwash item, showing machine progression and state - * - * @param props Props to pass to the component - * @return {*} */ -function ProxiwashListItem(props) { - const {colors} = props.theme; - let stateColors = {}; - stateColors[ProxiwashConstants.machineStates.TERMINE] = colors.proxiwashFinishedColor; - stateColors[ProxiwashConstants.machineStates.DISPONIBLE] = colors.proxiwashReadyColor; - stateColors[ProxiwashConstants.machineStates["EN COURS"]] = colors.proxiwashRunningColor; - stateColors[ProxiwashConstants.machineStates.HS] = colors.proxiwashBrokenColor; - stateColors[ProxiwashConstants.machineStates.ERREUR] = colors.proxiwashErrorColor; - const icon = ( - props.isWatched ? - { + + stateColors: Object; + stateStrings: Object; + + title: string; + + constructor(props) { + super(props); + this.stateColors = {}; + this.stateStrings = {}; + + this.updateStateStrings(); + + let displayNumber = props.item.number; + if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) + displayNumber = AprilFoolsManager.getProxiwashMachineDisplayNumber(parseInt(props.item.number)); + + this.title = props.isDryer + ? i18n.t('proxiwashScreen.dryer') + : i18n.t('proxiwashScreen.washer'); + this.title += ' n°' + displayNumber; + } + + shouldComponentUpdate(nextProps: Props): boolean { + const props = this.props; + return (nextProps.theme.dark !== props.theme.dark) + || (nextProps.item.state !== props.item.state) + || (nextProps.item.donePercent !== props.item.donePercent) + || (nextProps.isWatched !== props.isWatched); + } + + updateStateStrings() { + this.stateStrings[ProxiwashConstants.machineStates.TERMINE] = i18n.t('proxiwashScreen.states.finished'); + this.stateStrings[ProxiwashConstants.machineStates.DISPONIBLE] = i18n.t('proxiwashScreen.states.ready'); + this.stateStrings[ProxiwashConstants.machineStates["EN COURS"]] = i18n.t('proxiwashScreen.states.running'); + this.stateStrings[ProxiwashConstants.machineStates.HS] = i18n.t('proxiwashScreen.states.broken'); + this.stateStrings[ProxiwashConstants.machineStates.ERREUR] = i18n.t('proxiwashScreen.states.error'); + } + + updateStateColors() { + const colors = this.props.theme.colors; + this.stateColors[ProxiwashConstants.machineStates.TERMINE] = colors.proxiwashFinishedColor; + this.stateColors[ProxiwashConstants.machineStates.DISPONIBLE] = colors.proxiwashReadyColor; + this.stateColors[ProxiwashConstants.machineStates["EN COURS"]] = colors.proxiwashRunningColor; + this.stateColors[ProxiwashConstants.machineStates.HS] = colors.proxiwashBrokenColor; + this.stateColors[ProxiwashConstants.machineStates.ERREUR] = colors.proxiwashErrorColor; + } + + onListItemPress = () => this.props.onPress(this.title, this.props.item, this.props.isDryer); + + render() { + const props = this.props; + const colors = props.theme.colors; + const machineState = props.item.state; + const isRunning = ProxiwashConstants.machineStates[machineState] === ProxiwashConstants.machineStates["EN COURS"]; + const isReady = ProxiwashConstants.machineStates[machineState] === ProxiwashConstants.machineStates.DISPONIBLE; + const description = isRunning ? props.item.startTime + '/' + props.item.endTime : ''; + const stateIcon = ProxiwashConstants.stateIcons[machineState]; + const stateString = this.stateStrings[ProxiwashConstants.machineStates[machineState]]; + const progress = isRunning + ? props.item.donePercent !== '' + ? parseInt(props.item.donePercent) / 100 + : 0 + : 1; + + const icon = props.isWatched + ? : - + : - ); - return ( - - {ProxiwashConstants.machineStates[props.state] === ProxiwashConstants.machineStates["EN COURS"] ? - : null - } - - - icon} - right={() => ( - - - - {props.statusText} - - - ; + this.updateStateColors(); + return ( + + { + !isReady + ? - )} - /> - - ); + : null + } + icon} + right={() => ( + + + + {stateString} + + + + + + )} + /> + + ); + } } const styles = StyleSheet.create({ + container: { + margin: 5, + justifyContent: 'center', + elevation: 1 + }, icon: { backgroundColor: 'transparent' }, - backgroundCard: { - height: '100%', + progressBar: { position: 'absolute', left: 0, - width: '100%', - elevation: 0, + borderRadius: 4, }, - progressionCard: { - height: '100%', - position: 'absolute', - left: 0, - elevation: 0, - }, - title: { - backgroundColor: 'transparent', - } }); export default withTheme(ProxiwashListItem); diff --git a/src/components/Lists/ProxiwashSectionHeader.js b/src/components/Lists/ProxiwashSectionHeader.js new file mode 100644 index 0000000..3eaaea5 --- /dev/null +++ b/src/components/Lists/ProxiwashSectionHeader.js @@ -0,0 +1,69 @@ +import * as React from 'react'; +import {Avatar, Text, withTheme} from 'react-native-paper'; +import {StyleSheet, View} from "react-native"; +import i18n from "i18n-js"; + +type Props = { + title: string, + isDryer: boolean, + nbAvailable: number, +} + +/** + * Component used to display a proxiwash item, showing machine progression and state + */ +class ProxiwashListItem extends React.Component { + + constructor(props) { + super(props); + } + + shouldComponentUpdate(nextProps: Props) { + return (nextProps.theme.dark !== this.props.theme.dark) + || (nextProps.nbAvailable !== this.props.nbAvailable) + } + + render() { + const props = this.props; + const subtitle = props.nbAvailable + ' ' + ( + (props.nbAvailable <= 1) + ? i18n.t('proxiwashScreen.numAvailable') + : i18n.t('proxiwashScreen.numAvailablePlural')); + return ( + + + + + {props.title} + + + {subtitle} + + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + marginLeft: 5, + marginRight: 5, + marginBottom: 10, + marginTop: 20, + }, + icon: { + backgroundColor: 'transparent' + }, + text: { + fontSize: 20, + fontWeight: 'bold', + } +}); + +export default withTheme(ProxiwashListItem); diff --git a/src/constants/ProxiwashConstants.js b/src/constants/ProxiwashConstants.js index ae2599f..799e956 100644 --- a/src/constants/ProxiwashConstants.js +++ b/src/constants/ProxiwashConstants.js @@ -1,4 +1,3 @@ - export default { machineStates: { "TERMINE": "0", @@ -7,4 +6,11 @@ export default { "HS": "3", "ERREUR": "4" }, + stateIcons: { + "TERMINE": 'check-circle', + "DISPONIBLE": 'radiobox-blank', + "EN COURS": 'progress-check', + "HS": 'alert-octagram-outline', + "ERREUR": 'alert' + } }; diff --git a/src/screens/Proxiwash/ProxiwashScreen.js b/src/screens/Proxiwash/ProxiwashScreen.js index 535fab1..e57f526 100644 --- a/src/screens/Proxiwash/ProxiwashScreen.js +++ b/src/screens/Proxiwash/ProxiwashScreen.js @@ -13,12 +13,11 @@ import ProxiwashConstants from "../../constants/ProxiwashConstants"; import CustomModal from "../../components/Custom/CustomModal"; import AprilFoolsManager from "../../managers/AprilFoolsManager"; import MaterialHeaderButtons, {Item} from "../../components/Custom/HeaderButton"; +import ProxiwashSectionHeader from "../../components/Lists/ProxiwashSectionHeader"; const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json"; -let stateStrings = {}; let modalStateStrings = {}; -let stateIcons = {}; const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds const LIST_ITEM_HEIGHT = 64; @@ -30,7 +29,6 @@ type Props = { type State = { refreshing: boolean, - firstLoading: boolean, modalCurrentDisplayItem: React.Node, machinesWatched: Array, bannerVisible: boolean, @@ -45,22 +43,12 @@ class ProxiwashScreen extends React.Component { modalRef: Object; - onAboutPress: Function; - getRenderItem: Function; - getRenderSectionHeader: Function; - createDataset: Function; - onHideBanner: Function; - onModalRef: Function; - fetchedData: Object; - colors: Object; state = { refreshing: false, - firstLoading: true, - fetchedData: {}, - machinesWatched: [], modalCurrentDisplayItem: null, + machinesWatched: [], bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === '1', }; @@ -69,53 +57,31 @@ class ProxiwashScreen extends React.Component { */ constructor(props) { super(props); - stateStrings[ProxiwashConstants.machineStates.TERMINE] = i18n.t('proxiwashScreen.states.finished'); - stateStrings[ProxiwashConstants.machineStates.DISPONIBLE] = i18n.t('proxiwashScreen.states.ready'); - stateStrings[ProxiwashConstants.machineStates["EN COURS"]] = i18n.t('proxiwashScreen.states.running'); - stateStrings[ProxiwashConstants.machineStates.HS] = i18n.t('proxiwashScreen.states.broken'); - stateStrings[ProxiwashConstants.machineStates.ERREUR] = i18n.t('proxiwashScreen.states.error'); - modalStateStrings[ProxiwashConstants.machineStates.TERMINE] = i18n.t('proxiwashScreen.modal.finished'); modalStateStrings[ProxiwashConstants.machineStates.DISPONIBLE] = i18n.t('proxiwashScreen.modal.ready'); modalStateStrings[ProxiwashConstants.machineStates["EN COURS"]] = i18n.t('proxiwashScreen.modal.running'); modalStateStrings[ProxiwashConstants.machineStates.HS] = i18n.t('proxiwashScreen.modal.broken'); modalStateStrings[ProxiwashConstants.machineStates.ERREUR] = i18n.t('proxiwashScreen.modal.error'); - - stateIcons[ProxiwashConstants.machineStates.TERMINE] = 'check-circle'; - stateIcons[ProxiwashConstants.machineStates.DISPONIBLE] = 'radiobox-blank'; - stateIcons[ProxiwashConstants.machineStates["EN COURS"]] = 'progress-check'; - stateIcons[ProxiwashConstants.machineStates.HS] = 'alert-octagram-outline'; - stateIcons[ProxiwashConstants.machineStates.ERREUR] = 'alert'; - - // let dataString = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current; - this.onAboutPress = this.onAboutPress.bind(this); - this.getRenderItem = this.getRenderItem.bind(this); - this.getRenderSectionHeader = this.getRenderSectionHeader.bind(this); - this.createDataset = this.createDataset.bind(this); - this.onHideBanner = this.onHideBanner.bind(this); - this.onModalRef = this.onModalRef.bind(this); - this.colors = props.theme.colors; } /** * Callback used when closing the banner. * This hides the banner and saves to preferences to prevent it from reopening */ - onHideBanner() { + onHideBanner = () => { this.setState({bannerVisible: false}); AsyncStorageManager.getInstance().savePref( AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.key, '0' ); - } + }; /** * Setup notification channel for android and add listeners to detect notifications fired */ componentDidMount() { - const rightButton = this.getAboutButton.bind(this); this.props.navigation.setOptions({ - headerRight: rightButton, + headerRight: this.getAboutButton, }); if (AsyncStorageManager.getInstance().preferences.expoToken.current !== '') { // Get latest watchlist from server @@ -142,20 +108,17 @@ class ProxiwashScreen extends React.Component { * Callback used when pressing the about button. * This will open the ProxiwashAboutScreen. */ - onAboutPress() { - this.props.navigation.navigate('proxiwash-about'); - } + onAboutPress = () => this.props.navigation.navigate('proxiwash-about'); /** * Gets the about header button * * @return {*} */ - getAboutButton() { - return + getAboutButton = () => + ; - } /** * Extracts the key for the given item @@ -261,7 +224,7 @@ class ProxiwashScreen extends React.Component { * @param fetchedData * @return {*} */ - createDataset(fetchedData: Object) { + createDataset = (fetchedData: Object) => { let data = fetchedData; if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) { data = JSON.parse(JSON.stringify(fetchedData)); // Deep copy @@ -284,7 +247,7 @@ class ProxiwashScreen extends React.Component { keyExtractor: this.getKeyExtractor }, ]; - } + }; /** * Shows a modal for the given item @@ -293,14 +256,14 @@ class ProxiwashScreen extends React.Component { * @param item The item to display information for in the modal * @param isDryer True if the given item is a dryer */ - showModal(title: string, item: Object, isDryer: boolean) { + showModal = (title: string, item: Object, isDryer: boolean) => { this.setState({ modalCurrentDisplayItem: this.getModalContent(title, item, isDryer) }); if (this.modalRef) { this.modalRef.open(); } - } + }; /** * Callback used when the user clicks on enable notifications for a machine @@ -362,7 +325,7 @@ class ProxiwashScreen extends React.Component { title={title} left={() => } /> @@ -390,9 +353,9 @@ class ProxiwashScreen extends React.Component { * * @param ref */ - onModalRef(ref: Object) { + onModalRef = (ref: Object) => { this.modalRef = ref; - } + }; /** * Gets the number of machines available @@ -420,43 +383,16 @@ class ProxiwashScreen extends React.Component { * @param section The section to render * @return {*} */ - getRenderSectionHeader({section}: Object) { + getRenderSectionHeader = ({section}: Object) => { const isDryer = section.title === i18n.t('proxiwashScreen.dryers'); const nbAvailable = this.getMachineAvailableNumber(isDryer); - const subtitle = nbAvailable + ' ' + ((nbAvailable <= 1) ? i18n.t('proxiwashScreen.numAvailable') - : i18n.t('proxiwashScreen.numAvailablePlural')); return ( - - - - - {section.title} - - - - {subtitle} - - - + ); - } + }; /** * Gets the list item to be rendered @@ -465,34 +401,18 @@ class ProxiwashScreen extends React.Component { * @param section The object describing the current SectionList section * @returns {React.Node} */ - getRenderItem({item, section}: Object) { - const isMachineRunning = ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates["EN COURS"]; - let displayNumber = item.number; - if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) - displayNumber = AprilFoolsManager.getProxiwashMachineDisplayNumber(parseInt(item.number)); - const machineName = (section.title === i18n.t('proxiwashScreen.dryers') ? - i18n.t('proxiwashScreen.dryer') : - i18n.t('proxiwashScreen.washer')) + ' n°' + displayNumber; + getRenderItem = ({item, section}: Object) => { const isDryer = section.title === i18n.t('proxiwashScreen.dryers'); - const onPress = this.showModal.bind(this, machineName, item, isDryer); - let width = item.donePercent !== '' ? (parseInt(item.donePercent)).toString() + '%' : 0; - if (ProxiwashConstants.machineStates[item.state] === '0') - width = '100%'; return ( ); - } + }; render() { const nav = this.props.navigation; diff --git a/src/screens/Websites/AmicaleWebsiteScreen.js b/src/screens/Websites/AmicaleWebsiteScreen.js index 591986e..6b13a34 100644 --- a/src/screens/Websites/AmicaleWebsiteScreen.js +++ b/src/screens/Websites/AmicaleWebsiteScreen.js @@ -19,7 +19,6 @@ export const AmicaleWebsiteScreen = (props: Object) => { props.navigation.dispatch(CommonActions.setParams({path: null})); } } - console.log(path); return (