diff --git a/App.js b/App.js index 5de5714..7c5ac2d 100644 --- a/App.js +++ b/App.js @@ -31,9 +31,16 @@ export default class App extends React.Component<Props, State> { currentTheme: null, }; + onIntroDone: Function; + loadAssetsAsync: Function; + onLoadFinished: Function; + constructor(props: Object) { super(props); LocaleManager.initTranslations(); + this.onIntroDone = this.onIntroDone.bind(this); + this.loadAssetsAsync = this.loadAssetsAsync.bind(this); + this.onLoadFinished = this.onLoadFinished.bind(this); } /** @@ -102,14 +109,14 @@ export default class App extends React.Component<Props, State> { if (this.state.isLoading) { return ( <AppLoading - startAsync={() => this.loadAssetsAsync()} - onFinish={() => this.onLoadFinished()} + startAsync={this.loadAssetsAsync} + onFinish={this.onLoadFinished} onError={console.warn} /> ); } if (this.state.showIntro || this.state.showUpdate) { - return <CustomIntroSlider onDone={() => this.onIntroDone()} + return <CustomIntroSlider onDone={this.onIntroDone} isUpdate={this.state.showUpdate && !this.state.showIntro}/>; } else { const AppNavigator = createAppContainerWithInitialRoute(AsyncStorageManager.getInstance().preferences.defaultStartScreen.current); diff --git a/components/BaseContainer.js b/components/BaseContainer.js index 1e4fa85..c0b5bd0 100644 --- a/components/BaseContainer.js +++ b/components/BaseContainer.js @@ -30,7 +30,6 @@ type State = { export default class BaseContainer extends React.Component<Props, State> { - static defaultProps = { headerRightButton: <View/>, hasTabs: false, @@ -46,40 +45,61 @@ export default class BaseContainer extends React.Component<Props, State> { isHeaderVisible: true, }; - toggle() { + onDrawerPress: Function; + onWillFocus: Function; + onWillBlur: Function; + onChangeOrientation: Function; + + constructor() { + super(); + this.onDrawerPress = this.onDrawerPress.bind(this); + this.onWillFocus = this.onWillFocus.bind(this); + this.onWillBlur = this.onWillBlur.bind(this); + this.onChangeOrientation = this.onChangeOrientation.bind(this); + } + + onDrawerPress() { this.props.navigation.toggleDrawer(); } + + onWillFocus() { + if (this.props.enableRotation) { + ScreenOrientation.unlockAsync(); + ScreenOrientation.addOrientationChangeListener(this.onChangeOrientation); + } + } + + onWillBlur() { + if (this.props.enableRotation) + ScreenOrientation.lockAsync(ScreenOrientation.Orientation.PORTRAIT); + } + + onChangeOrientation(OrientationChangeEvent) { + if (this.props.hideHeaderOnLandscape) { + let isLandscape = OrientationChangeEvent.orientationInfo.orientation === ScreenOrientation.Orientation.LANDSCAPE || + OrientationChangeEvent.orientationInfo.orientation === ScreenOrientation.Orientation.LANDSCAPE_LEFT || + OrientationChangeEvent.orientationInfo.orientation === ScreenOrientation.Orientation.LANDSCAPE_RIGHT; + this.setState({isHeaderVisible: !isLandscape}); + const setParamsAction = NavigationActions.setParams({ + params: {showTabBar: !isLandscape}, + key: this.props.navigation.state.key, + }); + this.props.navigation.dispatch(setParamsAction); + StatusBar.setHidden(isLandscape); + } + } + /** * Register for blur event to close side menu on screen change */ componentDidMount() { this.willFocusSubscription = this.props.navigation.addListener( 'willFocus', - () => { - if (this.props.enableRotation) { - ScreenOrientation.unlockAsync(); - ScreenOrientation.addOrientationChangeListener((OrientationChangeEvent) => { - if (this.props.hideHeaderOnLandscape) { - let isLandscape = OrientationChangeEvent.orientationInfo.orientation === ScreenOrientation.Orientation.LANDSCAPE || - OrientationChangeEvent.orientationInfo.orientation === ScreenOrientation.Orientation.LANDSCAPE_LEFT || - OrientationChangeEvent.orientationInfo.orientation === ScreenOrientation.Orientation.LANDSCAPE_RIGHT; - this.setState({isHeaderVisible: !isLandscape}); - const setParamsAction = NavigationActions.setParams({ - params: {showTabBar: !isLandscape}, - key: this.props.navigation.state.key, - }); - this.props.navigation.dispatch(setParamsAction); - StatusBar.setHidden(isLandscape); - } - }); - } - }); + this.onWillFocus + ); this.willBlurSubscription = this.props.navigation.addListener( 'willBlur', - () => { - if (this.props.enableRotation) - ScreenOrientation.lockAsync(ScreenOrientation.Orientation.PORTRAIT); - } + this.onWillBlur ); } @@ -93,7 +113,9 @@ export default class BaseContainer extends React.Component<Props, State> { this.willFocusSubscription.remove(); } - getMainContainer() { + + render() { + // console.log("rendering BaseContainer"); return ( <Container> {this.state.isHeaderVisible ? @@ -104,7 +126,7 @@ export default class BaseContainer extends React.Component<Props, State> { leftButton={ <Touchable style={{padding: 6}} - onPress={() => this.toggle()}> + onPress={this.onDrawerPress}> <CustomMaterialIcon color={Platform.OS === 'ios' ? ThemeManager.getCurrentThemeVariables().brandPrimary : "#fff"} icon="menu"/> @@ -118,9 +140,4 @@ export default class BaseContainer extends React.Component<Props, State> { </Container> ); } - - - render() { - return (this.getMainContainer()); - } } diff --git a/components/CustomHeader.js b/components/CustomHeader.js index 0290c89..c72e46f 100644 --- a/components/CustomHeader.js +++ b/components/CustomHeader.js @@ -32,11 +32,10 @@ type Props = { * @prop navigation {Object} The navigation object from react navigation */ export default class CustomHeader extends React.Component<Props> { - static defaultProps = { hasBackButton: false, hasSearchField: false, - searchCallback: () => null, + searchCallback: null, shouldFocusSearchBar: false, title: '', subtitle: '', @@ -45,10 +44,28 @@ export default class CustomHeader extends React.Component<Props> { hasTabs: false, }; + onPressBack: Function; + + constructor() { + super(); + this.onPressBack = this.onPressBack.bind(this); + } + + shouldComponentUpdate(nextProps: Props): boolean { + return nextProps.title !== this.props.title || + nextProps.subtitle !== this.props.subtitle || + nextProps.hasBackButton !== this.props.hasBackButton || + nextProps.hasSearchField !== this.props.hasSearchField || + nextProps.shouldFocusSearchBar !== this.props.shouldFocusSearchBar || + nextProps.hasTabs !== this.props.hasTabs || + nextProps.rightButton !== this.props.rightButton || + nextProps.leftButton !== this.props.leftButton; + } + componentDidMount() { if (this.refs.searchInput !== undefined && this.refs.searchInput._root !== undefined && this.props.shouldFocusSearchBar) { - // does not work if called to early for some reason... - setTimeout(() => this.refs.searchInput._root.focus(), 500); + // does not work if called too early for some reason... + setTimeout(this.refs.searchInput._root.focus, 500); } } @@ -67,7 +84,7 @@ export default class CustomHeader extends React.Component<Props> { ref="searchInput" placeholder={i18n.t('proximoScreen.search')} placeholderTextColor={ThemeManager.getCurrentThemeVariables().toolbarPlaceholderColor} - onChangeText={(text) => this.props.searchCallback(text)}/> + onChangeText={this.props.searchCallback}/> </Item> </Body> ); @@ -87,17 +104,20 @@ export default class CustomHeader extends React.Component<Props> { } + onPressBack() { + const backAction = NavigationActions.back(); + this.props.navigation.dispatch(backAction); + } + render() { + // console.log("rendering CustomHeader"); let button; // Does the app have a back button or a burger menu ? if (this.props.hasBackButton) button = <Touchable style={{padding: 6}} - onPress={() => { - const backAction = NavigationActions.back(); - this.props.navigation.dispatch(backAction); - }}> + onPress={this.onPressBack}> <CustomMaterialIcon color={Platform.OS === 'ios' ? ThemeManager.getCurrentThemeVariables().brandPrimary : "#fff"} icon={Platform.OS === 'ios' ? 'chevron-left' : "arrow-left"}/> diff --git a/components/CustomIntroSlider.js b/components/CustomIntroSlider.js index 6e430e3..1ad4221 100644 --- a/components/CustomIntroSlider.js +++ b/components/CustomIntroSlider.js @@ -117,7 +117,7 @@ export default class CustomIntroSlider extends React.Component<Props> { * @param item * @param dimensions */ - static getIntroRenderItem(item: Object, dimensions: Object) { + static getIntroRenderItem({item, dimensions}: Object) { return ( <LinearGradient @@ -143,9 +143,9 @@ export default class CustomIntroSlider extends React.Component<Props> { render() { return ( <AppIntroSlider - renderItem={({item, dimensions}) => CustomIntroSlider.getIntroRenderItem(item, dimensions)} + renderItem={CustomIntroSlider.getIntroRenderItem} slides={this.props.isUpdate ? this.updateSlides : this.introSlides} - onDone={() => this.props.onDone()} + onDone={this.props.onDone} bottomButton showSkipButton skipLabel={i18n.t('intro.buttons.skip')} diff --git a/components/CustomMaterialIcon.js b/components/CustomMaterialIcon.js index e6919f2..4792b81 100644 --- a/components/CustomMaterialIcon.js +++ b/components/CustomMaterialIcon.js @@ -30,7 +30,16 @@ export default class CustomMaterialIcon extends React.Component<Props> { width: 30, }; + shouldComponentUpdate(nextProps: Props): boolean { + return nextProps.icon !== this.props.icon || + nextProps.active !== this.props.active || + nextProps.width !== this.props.width || + nextProps.fontSize !== this.props.fontSize || + nextProps.color !== this.props.color; + } + render() { + // console.log("rendering icon " + this.props.icon); return ( <Icon active diff --git a/components/DashboardItem.js b/components/DashboardItem.js index 716fb33..7695cae 100644 --- a/components/DashboardItem.js +++ b/components/DashboardItem.js @@ -25,13 +25,18 @@ type Props = { } export default class DashboardItem extends React.Component<Props> { - static defaultProps = { isSquare: false, isSquareLeft: true, displayEvent: undefined, }; + shouldComponentUpdate(nextProps: Props): boolean { + return nextProps.isAvailable !== this.props.isAvailable || + nextProps.subtitle !== this.props.subtitle; + } + + /** * Convert the date string given by in the event list json to a date object * @param dateString @@ -203,6 +208,7 @@ export default class DashboardItem extends React.Component<Props> { render() { + // console.log("rendering DashboardItem " + this.props.title); let marginRight = 10; if (this.props.isSquare) { if (this.props.isSquareLeft) diff --git a/components/FetchedDataSectionList.js b/components/FetchedDataSectionList.js index 65f6e7a..413eb25 100644 --- a/components/FetchedDataSectionList.js +++ b/components/FetchedDataSectionList.js @@ -25,7 +25,6 @@ type State = { * Used by inheriting from it and redefining getters. */ export default class FetchedDataSectionList extends React.Component<Props, State> { - webDataManager: WebDataManager; willFocusSubscription: function; @@ -42,12 +41,36 @@ export default class FetchedDataSectionList extends React.Component<Props, State machinesWatched: [], }; + onRefresh: Function; + onFetchSuccess: Function; + onFetchError: Function; + renderSectionHeaderEmpty: Function; + renderSectionHeaderNotEmpty: Function; + renderItemEmpty: Function; + renderItemNotEmpty: Function; + constructor(fetchUrl: string, refreshTime: number) { super(); this.webDataManager = new WebDataManager(fetchUrl); this.refreshTime = refreshTime; + // creating references to functions used in render() + this.onRefresh = this.onRefresh.bind(this); + this.onFetchSuccess = this.onFetchSuccess.bind(this); + this.onFetchError = this.onFetchError.bind(this); + this.renderSectionHeaderEmpty = this.renderSectionHeader.bind(this, true); + this.renderSectionHeaderNotEmpty = this.renderSectionHeader.bind(this, false); + this.renderItemEmpty = this.renderItem.bind(this, true); + this.renderItemNotEmpty = this.renderItem.bind(this, false); } + shouldComponentUpdate(nextProps: Props, nextState: State): boolean { + return this.state.refreshing !== nextState.refreshing || + nextState.firstLoading !== this.state.firstLoading || + nextState.machinesWatched.length !== this.state.machinesWatched.length || + nextState.fetchedData.len !== this.state.fetchedData.len; + } + + /** * Get the translation for the header in the current language * @return {string} @@ -74,26 +97,18 @@ export default class FetchedDataSectionList extends React.Component<Props, State */ componentDidMount() { this.willFocusSubscription = this.props.navigation.addListener( - 'willFocus', - () => { - this.onScreenFocus(); - } - ); + 'willFocus', this.onScreenFocus.bind(this)); this.willBlurSubscription = this.props.navigation.addListener( - 'willBlur', - () => { - this.onScreenBlur(); - } - ); + 'willBlur', this.onScreenBlur.bind(this)); } /** * Refresh data when focusing the screen and setup a refresh interval if asked to */ onScreenFocus() { - this._onRefresh(); + this.onRefresh(); if (this.refreshTime > 0) - this.refreshInterval = setInterval(() => this._onRefresh(), this.refreshTime) + this.refreshInterval = setInterval(this.onRefresh.bind(this), this.refreshTime) } /** @@ -113,11 +128,29 @@ export default class FetchedDataSectionList extends React.Component<Props, State this.willFocusSubscription.remove(); } + onFetchSuccess(fetchedData: Object) { + this.setState({ + fetchedData: fetchedData, + refreshing: false, + firstLoading: false + }); + this.lastRefresh = new Date(); + } + + onFetchError() { + this.setState({ + fetchedData: {}, + refreshing: false, + firstLoading: false + }); + this.webDataManager.showUpdateToast(this.getUpdateToastTranslations()[0], this.getUpdateToastTranslations()[1]); + } + /** * Refresh data and show a toast if any error occurred * @private */ - _onRefresh = () => { + onRefresh() { let canRefresh; if (this.lastRefresh !== undefined) canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) / 1000 > this.minTimeBetweenRefresh; @@ -127,25 +160,10 @@ export default class FetchedDataSectionList extends React.Component<Props, State if (canRefresh) { this.setState({refreshing: true}); this.webDataManager.readData() - .then((fetchedData) => { - this.setState({ - fetchedData: fetchedData, - refreshing: false, - firstLoading: false - }); - this.lastRefresh = new Date(); - }) - .catch(() => { - this.setState({ - fetchedData: {}, - refreshing: false, - firstLoading: false - }); - this.webDataManager.showUpdateToast(this.getUpdateToastTranslations()[0], this.getUpdateToastTranslations()[1]); - }); + .then(this.onFetchSuccess) + .catch(this.onFetchError); } - - }; + } /** * Get the render item to be used for display in the list. @@ -153,10 +171,9 @@ export default class FetchedDataSectionList extends React.Component<Props, State * * @param item * @param section - * @param data * @return {*} */ - getRenderItem(item: Object, section: Object, data: Object) { + getRenderItem(item: Object, section: Object) { return <View/>; } @@ -222,6 +239,11 @@ export default class FetchedDataSectionList extends React.Component<Props, State return []; } + + datasetKeyExtractor(item: Object) { + return item.text + } + /** * Create the dataset when no fetched data is available. * No need to be overridden, has good defaults. @@ -243,7 +265,7 @@ export default class FetchedDataSectionList extends React.Component<Props, State 'access-point-network-off' } ], - keyExtractor: (item: Object) => item.text, + keyExtractor: this.datasetKeyExtractor, } ]; } @@ -275,6 +297,19 @@ export default class FetchedDataSectionList extends React.Component<Props, State return true; } + + renderSectionHeader(isEmpty: boolean, {section: {title}} : Object) { + return isEmpty ? + <View/> : + this.getRenderSectionHeader(title) + } + + renderItem(isEmpty: boolean, {item, section}: Object) { + return isEmpty ? + this.getEmptyRenderItem(item.text, item.isSpinner, item.icon) : + this.getRenderItem(item, section) + } + /** * Get the section list render using the generated dataset * @@ -292,19 +327,11 @@ export default class FetchedDataSectionList extends React.Component<Props, State refreshControl={ <RefreshControl refreshing={this.state.refreshing} - onRefresh={this._onRefresh} + onRefresh={this.onRefresh} /> } - renderSectionHeader={({section: {title}}) => - isEmpty ? - <View/> : - this.getRenderSectionHeader(title) - } - renderItem={({item, section}) => - isEmpty ? - this.getEmptyRenderItem(item.text, item.isSpinner, item.icon) : - this.getRenderItem(item, section, dataset) - } + renderSectionHeader={isEmpty ? this.renderSectionHeaderEmpty : this.renderSectionHeaderNotEmpty} + renderItem={isEmpty ? this.renderItemEmpty : this.renderItemNotEmpty} style={{minHeight: 300, width: '100%'}} contentContainerStyle={ isEmpty ? @@ -351,11 +378,12 @@ export default class FetchedDataSectionList extends React.Component<Props, State } render() { - const nav = this.props.navigation; + // console.log("rendering FetchedDataSectionList"); const dataset = this.createDataset(this.state.fetchedData); return ( <BaseContainer - navigation={nav} headerTitle={this.getHeaderTranslation()} + navigation={this.props.navigation} + headerTitle={this.getHeaderTranslation()} headerRightButton={this.getRightButton()} hasTabs={this.hasTabs()} hasBackButton={this.hasBackButton()} @@ -375,5 +403,4 @@ export default class FetchedDataSectionList extends React.Component<Props, State </BaseContainer> ); } - } diff --git a/components/Sidebar.js b/components/Sidebar.js index 2edcc0c..cac9553 100644 --- a/components/Sidebar.js +++ b/components/Sidebar.js @@ -30,6 +30,8 @@ export default class SideBar extends React.Component<Props, State> { active: 'Home', }; + getRenderItem: Function; + /** * Generate the datasets * @@ -38,7 +40,6 @@ export default class SideBar extends React.Component<Props, State> { constructor(props: Props) { super(props); // Dataset used to render the drawer - // If the link field is defined, clicking on the item will open the link this.dataSet = [ { name: i18n.t('sidenav.divider1'), @@ -103,21 +104,34 @@ export default class SideBar extends React.Component<Props, State> { icon: "information", }, ]; + this.getRenderItem = this.getRenderItem.bind(this); } - getRenderItem(item: Object) { + shouldComponentUpdate(nextProps: Props, nextState: State): boolean { + return nextState.active !== this.state.active; + } + + + onListItemPress(route: string) { + this.props.navigation.navigate(route); + } + + + listKeyExtractor(item: Object) { + return item.route; + } + + + getRenderItem({item}: Object) { + const onListItemPress = this.onListItemPress.bind(this, item.route); + if (item.icon !== undefined) { return ( <ListItem button noBorder selected={this.state.active === item.route} - onPress={() => { - if (item.link !== undefined) - Linking.openURL(item.link).catch((err) => console.error('Error opening link', err)); - else - this.navigateToScreen(item.route); - }} + onPress={onListItemPress} > <Left> <CustomMaterialIcon @@ -155,15 +169,8 @@ export default class SideBar extends React.Component<Props, State> { } - /** - * Navigate to the selected route - * @param route {string} The route name to navigate to - */ - navigateToScreen(route: string) { - this.props.navigation.navigate(route); - }; - render() { + // console.log("rendering SideBar"); return ( <Container style={{ backgroundColor: ThemeManager.getCurrentThemeVariables().sideMenuBgColor, @@ -172,8 +179,8 @@ export default class SideBar extends React.Component<Props, State> { <FlatList data={this.dataSet} extraData={this.state} - keyExtractor={(item) => item.route} - renderItem={({item}) => this.getRenderItem(item)} + keyExtractor={this.listKeyExtractor} + renderItem={this.getRenderItem} /> </Container> ); diff --git a/components/WebViewScreen.js b/components/WebViewScreen.js index 480c425..69aa465 100644 --- a/components/WebViewScreen.js +++ b/components/WebViewScreen.js @@ -35,6 +35,21 @@ export default class WebViewScreen extends React.Component<Props> { }; webviewArray: Array<WebView> = []; + onRefreshClicked: Function; + onWebviewRef: Function; + onGoBackWebview: Function; + onGoForwardWebview: Function; + onOpenWebLink: Function; + + constructor() { + super(); + this.onRefreshClicked = this.onRefreshClicked.bind(this); + this.onWebviewRef = this.onWebviewRef.bind(this); + this.onGoBackWebview = this.onGoBackWebview.bind(this); + this.onGoForwardWebview = this.onGoForwardWebview.bind(this); + this.onOpenWebLink = this.onOpenWebLink.bind(this); + } + openWebLink(url: string) { Linking.openURL(url).catch((err) => console.error('Error opening link', err)); } @@ -43,7 +58,7 @@ export default class WebViewScreen extends React.Component<Props> { return ( <Touchable style={{padding: 6}} - onPress={() => clickAction()}> + onPress={clickAction}> <CustomMaterialIcon color={Platform.OS === 'ios' ? ThemeManager.getCurrentThemeVariables().brandPrimary : "#fff"} icon={icon}/> @@ -54,36 +69,62 @@ export default class WebViewScreen extends React.Component<Props> { getRefreshButton() { return ( <View style={{flexDirection: 'row'}}> - {this.getHeaderButton(() => this.refreshWebview(), 'refresh')} + {this.getHeaderButton(this.onRefreshClicked, 'refresh')} </View> ); }; - refreshWebview() { + onRefreshClicked() { for (let view of this.webviewArray) { if (view !== null) view.reload(); } } - goBackWebview() { + onGoBackWebview() { for (let view of this.webviewArray) { if (view !== null) view.goBack(); } } - goForwardWebview() { + onGoForwardWebview() { for (let view of this.webviewArray) { if (view !== null) view.goForward(); } } + onOpenWebLink() { + this.openWebLink(this.props.data[0]['url']) + } + + onWebviewRef(ref: WebView) { + this.webviewArray.push(ref) + } + + getRenderLoading() { + return ( + <View style={{ + backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor, + position: 'absolute', + top: 0, + right: 0, + width: '100%', + height: '100%', + flex: 1, + alignItems: 'center', + justifyContent: 'center' + }}> + <Spinner/> + </View> + ); + } + getWebview(obj: Object) { return ( <WebView - ref={ref => (this.webviewArray.push(ref))} + ref={this.onWebviewRef} source={{uri: obj['url']}} style={{ width: '100%', @@ -92,21 +133,7 @@ export default class WebViewScreen extends React.Component<Props> { startInLoadingState={true} injectedJavaScript={obj['customJS']} javaScriptEnabled={true} - renderLoading={() => - <View style={{ - backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor, - position: 'absolute', - top: 0, - right: 0, - width: '100%', - height: '100%', - flex: 1, - alignItems: 'center', - justifyContent: 'center' - }}> - <Spinner/> - </View> - } + renderLoading={this.getRenderLoading} /> ); } @@ -133,6 +160,7 @@ export default class WebViewScreen extends React.Component<Props> { } render() { + // console.log("rendering WebViewScreen"); const nav = this.props.navigation; this.webviewArray = []; return ( @@ -166,7 +194,7 @@ export default class WebViewScreen extends React.Component<Props> { <Left style={{ paddingLeft: 6, }}> - {this.getHeaderButton(() => this.openWebLink(this.props.data[0]['url']), 'open-in-new')} + {this.getHeaderButton(this.onOpenWebLink, 'open-in-new')} </Left> <Body/> <Right style={{ @@ -179,8 +207,8 @@ export default class WebViewScreen extends React.Component<Props> { marginRight: 0, marginLeft: 'auto' }}> - {this.getHeaderButton(() => this.goBackWebview(), 'chevron-left')} - {this.getHeaderButton(() => this.goForwardWebview(), 'chevron-right')} + {this.getHeaderButton(this.onGoBackWebview, 'chevron-left')} + {this.getHeaderButton(this.onGoForwardWebview, 'chevron-right')} </View> </Right> </Footer> : <View/>} diff --git a/screens/About/AboutScreen.js b/screens/About/AboutScreen.js index 0f2a8f1..098d4a5 100644 --- a/screens/About/AboutScreen.js +++ b/screens/About/AboutScreen.js @@ -187,9 +187,14 @@ export default class AboutScreen extends React.Component<Props, State> { }, ]; + getCardItem: Function; + getMainCard: Function; + constructor(props: any) { super(props); this.modalRef = React.createRef(); + this.getCardItem = this.getCardItem.bind(this); + this.getMainCard = this.getMainCard.bind(this); } getAppCard() { @@ -210,10 +215,8 @@ export default class AboutScreen extends React.Component<Props, State> { data={this.appData} extraData={this.state} keyExtractor={(item) => item.icon} - listKey={(item) => "app"} - renderItem={({item}) => - this.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron, item.showOnlyDebug) - } + listKey={"app"} + renderItem={this.getCardItem} /> </Card> ); @@ -241,10 +244,8 @@ export default class AboutScreen extends React.Component<Props, State> { data={this.authorData} extraData={this.state} keyExtractor={(item) => item.icon} - listKey={(item) => "team1"} - renderItem={({item}) => - this.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron, item.showOnlyDebug) - } + listKey={"team1"} + renderItem={this.getCardItem} /> <CardItem header> <Text>{i18n.t('aboutScreen.additionalDev')}</Text> @@ -253,10 +254,8 @@ export default class AboutScreen extends React.Component<Props, State> { data={this.additionalDevData} extraData={this.state} keyExtractor={(item) => item.icon} - listKey={(item) => "team2"} - renderItem={({item}) => - this.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron, item.showOnlyDebug) - } + listKey={"team2"} + renderItem={this.getCardItem} /> </Card> ); @@ -272,10 +271,8 @@ export default class AboutScreen extends React.Component<Props, State> { data={this.technoData} extraData={this.state} keyExtractor={(item) => item.icon} - listKey={(item) => "techno"} - renderItem={({item}) => - this.getCardItem(item.onPressCallback, item.icon, item.text, item.showChevron, item.showOnlyDebug) - } + listKey={"techno"} + renderItem={this.getCardItem} /> </Card> ); @@ -284,24 +281,19 @@ export default class AboutScreen extends React.Component<Props, State> { /** * Get a clickable card item to be rendered inside a card. * - * @param onPressCallback The callback to use when the item is clicked - * @param icon The icon name to use from MaterialCommunityIcons - * @param text The text to show - * @param showChevron Whether to show a chevron indicating this button will change screen - * @param showOnlyInDebug Should we show te current item only in debug mode? * @returns {React.Node} */ - getCardItem(onPressCallback: Function, icon: string, text: string, showChevron: boolean, showOnlyInDebug: boolean) { - let shouldShow = !showOnlyInDebug || (showOnlyInDebug && this.state.isDebugUnlocked); + getCardItem({item}: Object) { + let shouldShow = !item.showOnlyInDebug || (item.showOnlyInDebug && this.state.isDebugUnlocked); if (shouldShow) { return ( <CardItem button - onPress={onPressCallback}> + onPress={item.onPressCallback}> <Left> - <CustomMaterialIcon icon={icon}/> - <Text>{text}</Text> + <CustomMaterialIcon icon={item.icon}/> + <Text>{item.text}</Text> </Left> - {showChevron ? + {item.showChevron ? <Right> <CustomMaterialIcon icon="chevron-right" fontSize={20}/> @@ -331,6 +323,8 @@ export default class AboutScreen extends React.Component<Props, State> { } getBugReportModal() { + const onPressMail = openWebLink.bind(this, links.bugsMail); + const onPressGit = openWebLink.bind(this, links.bugsGit); return ( <Modalize ref={this.modalRef} adjustToContentHeight @@ -349,7 +343,7 @@ export default class AboutScreen extends React.Component<Props, State> { marginLeft: 'auto', marginRight: 'auto', }} - onPress={() => openWebLink(links.bugsMail)}> + onPress={onPressMail}> <CustomMaterialIcon icon={'email'} color={'#fff'}/> @@ -361,7 +355,7 @@ export default class AboutScreen extends React.Component<Props, State> { marginLeft: 'auto', marginRight: 'auto', }} - onPress={() => openWebLink(links.bugsGit)}> + onPress={onPressGit}> <CustomMaterialIcon icon={'git'} color={'#fff'}/> @@ -378,7 +372,7 @@ export default class AboutScreen extends React.Component<Props, State> { } } - getMainCard(item: Object) { + getMainCard({item}: Object) { switch (item.id) { case 'app': return this.getAppCard(); @@ -401,9 +395,7 @@ export default class AboutScreen extends React.Component<Props, State> { data={this.dataOrder} extraData={this.state} keyExtractor={(item) => item.id} - renderItem={({item}) => - this.getMainCard(item) - } + renderItem={this.getMainCard} /> </Container> ); diff --git a/screens/HomeScreen.js b/screens/HomeScreen.js index 2980cf7..2640ef5 100644 --- a/screens/HomeScreen.js +++ b/screens/HomeScreen.js @@ -38,8 +38,33 @@ function openWebLink(link) { */ export default class HomeScreen extends FetchedDataSectionList { + onProxiwashClick: Function; + onTutorInsaClick: Function; + onMenuClick: Function; + onProximoClick: Function; + constructor() { super(DATA_URL, REFRESH_TIME); + this.onProxiwashClick = this.onProxiwashClick.bind(this); + this.onTutorInsaClick = this.onTutorInsaClick.bind(this); + this.onMenuClick = this.onMenuClick.bind(this); + this.onProximoClick = this.onProximoClick.bind(this); + } + + onProxiwashClick() { + this.props.navigation.navigate('Proxiwash'); + } + + onTutorInsaClick() { + this.props.navigation.navigate('TutorInsaScreen'); + } + + onProximoClick() { + this.props.navigation.navigate('Proximo'); + } + + onMenuClick() { + this.props.navigation.navigate('SelfMenuScreen'); } /** @@ -289,6 +314,14 @@ export default class HomeScreen extends FetchedDataSectionList { } + clickAction(isAvailable: boolean, displayEvent: Object) { + if (isAvailable) + this.props.navigation.navigate('PlanningDisplayScreen', {data: displayEvent}); + else + this.props.navigation.navigate('PlanningScreen'); + }; + + getDashboardEventItem(content: Array<Object>) { let icon = 'calendar-range'; let color = ThemeManager.getCurrentThemeVariables().planningColor; @@ -310,12 +343,6 @@ export default class HomeScreen extends FetchedDataSectionList { </Text>; } else subtitle = i18n.t('homeScreen.dashboard.todayEventsSubtitleNA'); - let clickAction = () => { - if (isAvailable) - this.props.navigation.navigate('PlanningDisplayScreen', {data: displayEvent}); - else - this.props.navigation.navigate('PlanningScreen'); - }; let displayEvent = this.getDisplayEvent(futureEvents); @@ -324,7 +351,7 @@ export default class HomeScreen extends FetchedDataSectionList { subtitle={subtitle} color={color} icon={icon} - clickAction={() => clickAction()} + clickAction={this.clickAction.bind(this, isAvailable, displayEvent)} title={title} isAvailable={isAvailable} displayEvent={displayEvent} @@ -355,7 +382,6 @@ export default class HomeScreen extends FetchedDataSectionList { </Text>; } else proximoSubtitle = i18n.t('homeScreen.dashboard.proximoSubtitleNA'); - let proximoClickAction = () => this.props.navigation.navigate('Proximo'); let menuIcon = 'silverware-fork-knife'; @@ -367,7 +393,6 @@ export default class HomeScreen extends FetchedDataSectionList { menuSubtitle = i18n.t('homeScreen.dashboard.menuSubtitle'); } else menuSubtitle = i18n.t('homeScreen.dashboard.menuSubtitleNA'); - let menuClickAction = () => this.props.navigation.navigate('SelfMenuScreen'); return ( <View style={{ flexDirection: 'row', @@ -379,7 +404,7 @@ export default class HomeScreen extends FetchedDataSectionList { subtitle={menuSubtitle} color={menuColor} icon={menuIcon} - clickAction={() => menuClickAction()} + clickAction={this.onMenuClick} title={menuTitle} isAvailable={isMenuAvailable} isSquareLeft={true}/> @@ -388,13 +413,14 @@ export default class HomeScreen extends FetchedDataSectionList { subtitle={proximoSubtitle} color={proximoColor} icon={proximoIcon} - clickAction={() => proximoClickAction()} + clickAction={this.onProximoClick} title={proximoTitle} isAvailable={isProximoAvailable}/> </View> ); } + getDashboardMiddleItem(content: Array<Object>) { let proxiwashData = content[0]['data']; let tutorinsaData = content[1]['data']; @@ -449,7 +475,6 @@ export default class HomeScreen extends FetchedDataSectionList { </Text>; } else proxiwashSubtitle = i18n.t('homeScreen.dashboard.proxiwashSubtitleNA'); - let proxiwashClickAction = () => this.props.navigation.navigate('Proxiwash'); let tutorinsaIcon = 'school'; let tutorinsaColor = ThemeManager.getCurrentThemeVariables().tutorinsaColor; @@ -470,7 +495,6 @@ export default class HomeScreen extends FetchedDataSectionList { </Text>; } else tutorinsaSubtitle = i18n.t('homeScreen.dashboard.tutorinsaSubtitleNA'); - let tutorinsaClickAction = () => this.props.navigation.navigate('TutorInsaScreen'); return ( <View style={{ @@ -483,7 +507,7 @@ export default class HomeScreen extends FetchedDataSectionList { subtitle={proxiwashSubtitle} color={proxiwashColor} icon={proxiwashIcon} - clickAction={() => proxiwashClickAction()} + clickAction={this.onProxiwashClick} title={proxiwashTitle} isAvailable={proxiwashIsAvailable} isSquareLeft={true}/> @@ -492,7 +516,7 @@ export default class HomeScreen extends FetchedDataSectionList { subtitle={tutorinsaSubtitle} color={tutorinsaColor} icon={tutorinsaIcon} - clickAction={() => tutorinsaClickAction()} + clickAction={this.onTutorInsaClick} title={tutorinsaTitle} isAvailable={tutorinsaIsAvailable}/> </View> @@ -500,7 +524,7 @@ export default class HomeScreen extends FetchedDataSectionList { } - getRenderItem(item: Object, section: Object, data: Object) { + getRenderItem(item: Object, section: Object) { return ( section['id'] === SECTIONS_ID[0] ? this.getDashboardItem(item) : <Card style={{ @@ -525,7 +549,7 @@ export default class HomeScreen extends FetchedDataSectionList { }}> <Body> {item.full_picture !== '' && item.full_picture !== undefined ? - <TouchableOpacity onPress={() => openWebLink(item.full_picture)} + <TouchableOpacity onPress={openWebLink.bind(null, item.full_picture)} style={{width: '100%', height: 250, marginBottom: 5}}> <Image source={{uri: item.full_picture}} style={{flex: 1, resizeMode: "contain"}} @@ -547,7 +571,7 @@ export default class HomeScreen extends FetchedDataSectionList { }}> <Left> <Button transparent - onPress={() => openWebLink(item.permalink_url)}> + onPress={openWebLink.bind(null, item.permalink_url)}> <CustomMaterialIcon icon="facebook" color="#57aeff" diff --git a/screens/PlanningDisplayScreen.js b/screens/PlanningDisplayScreen.js index be07370..0203f5f 100644 --- a/screens/PlanningDisplayScreen.js +++ b/screens/PlanningDisplayScreen.js @@ -8,13 +8,12 @@ import ThemeManager from "../utils/ThemeManager"; import HTML from "react-native-render-html"; import {Linking} from "expo"; import PlanningEventManager from '../utils/PlanningEventManager'; -import i18n from 'i18n-js'; type Props = { navigation: Object, }; -function openWebLink(link) { +function openWebLink(event, link) { Linking.openURL(link).catch((err) => console.error('Error opening link', err)); } @@ -22,8 +21,8 @@ function openWebLink(link) { * Class defining an about screen. This screen shows the user information about the app and it's author. */ export default class PlanningDisplayScreen extends React.Component<Props> { - render() { + // console.log("rendering planningDisplayScreen"); const nav = this.props.navigation; const displayData = nav.getParam('data', []); return ( @@ -60,7 +59,7 @@ export default class PlanningDisplayScreen extends React.Component<Props> { }, div: {color: ThemeManager.getCurrentThemeVariables().textColor} }} - onLinkPress={(event, link) => openWebLink(link)}/> + onLinkPress={openWebLink}/> : <View/>} </Content> </Container> diff --git a/screens/PlanningScreen.js b/screens/PlanningScreen.js index d683cd8..b5aa6a0 100644 --- a/screens/PlanningScreen.js +++ b/screens/PlanningScreen.js @@ -5,7 +5,6 @@ import {BackHandler, Image} from 'react-native'; import {H3, Text, View} from 'native-base'; import i18n from "i18n-js"; import ThemeManager from "../utils/ThemeManager"; -import {Linking} from "expo"; import BaseContainer from "../components/BaseContainer"; import {Agenda, LocaleConfig} from 'react-native-calendars'; import Touchable from 'react-native-platform-touchable'; @@ -35,14 +34,6 @@ const FETCH_URL = "https://amicale-insat.fr/event/json/list"; const AGENDA_MONTH_SPAN = 6; -/** - * Opens a link in the device's browser - * @param link The link to open - */ -function openWebLink(link) { - Linking.openURL(link).catch((err) => console.error('Error opening link', err)); -} - /** * Class defining the app's planning screen */ @@ -56,12 +47,21 @@ export default class PlanningScreen extends React.Component<Props, State> { didFocusSubscription: Function; willBlurSubscription: Function; + state = { refreshing: false, agendaItems: {}, calendarShowing: false, }; + onRefresh: Function; + onCalendarToggled: Function; + getRenderItem: Function; + getRenderEmptyDate: Function; + onAgendaRef: Function; + onCalendarToggled: Function; + onBackButtonPressAndroid: Function; + constructor(props: any) { super(props); this.webDataManager = new WebDataManager(FETCH_URL); @@ -76,10 +76,25 @@ export default class PlanningScreen extends React.Component<Props, State> { if (i18n.currentLocale().startsWith("fr")) { LocaleConfig.defaultLocale = 'fr'; } + + // Create references for functions required in the render function + this.onRefresh = this.onRefresh.bind(this); + this.onCalendarToggled = this.onCalendarToggled.bind(this); + this.getRenderItem = this.getRenderItem.bind(this); + this.getRenderEmptyDate = this.getRenderEmptyDate.bind(this); + this.onAgendaRef = this.onAgendaRef.bind(this); + this.onCalendarToggled = this.onCalendarToggled.bind(this); + this.onBackButtonPressAndroid = this.onBackButtonPressAndroid.bind(this); + } + + shouldComponentUpdate(nextProps: Props, nextState: State): boolean { + return nextState.refreshing === false && this.state.refreshing === true || + nextState.agendaItems !== this.state.agendaItems || + nextState.calendarShowing !== this.state.calendarShowing; } componentDidMount() { - this._onRefresh(); + this.onRefresh(); this.willBlurSubscription = this.props.navigation.addListener( 'willBlur', () => @@ -90,7 +105,7 @@ export default class PlanningScreen extends React.Component<Props, State> { ); } - onBackButtonPressAndroid = () => { + onBackButtonPressAndroid() { if (this.state.calendarShowing) { this.agendaRef.chooseDay(this.agendaRef.state.selectedDay); return true; @@ -126,10 +141,6 @@ export default class PlanningScreen extends React.Component<Props, State> { } getRenderItem(item: Object) { - let navData = { - data: item - }; - const nav = this.props.navigation; return ( <Touchable style={{ @@ -138,7 +149,7 @@ export default class PlanningScreen extends React.Component<Props, State> { marginRight: 10, marginTop: 17, }} - onPress={() => nav.navigate('PlanningDisplayScreen', navData)}> + onPress={() => this.props.navigation.navigate('PlanningDisplayScreen', {data: item})}> <View style={{ padding: 10, flex: 1, @@ -200,7 +211,7 @@ export default class PlanningScreen extends React.Component<Props, State> { * Refresh data and show a toast if any error occurred * @private */ - _onRefresh = () => { + onRefresh = () => { let canRefresh; if (this.lastRefresh !== undefined) canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) / 1000 > this.minTimeBetweenRefresh; @@ -252,38 +263,46 @@ export default class PlanningScreen extends React.Component<Props, State> { } } + onAgendaRef(ref: Agenda) { + this.agendaRef = ref; + } + + onCalendarToggled(isCalendarOpened: boolean) { + this.setState({calendarShowing: isCalendarOpened}); + } + + currentDate = this.getCurrentDate(); + render() { - const nav = this.props.navigation; + // console.log("rendering PlanningScreen"); return ( - <BaseContainer navigation={nav} headerTitle={i18n.t('screens.planning')}> + <BaseContainer navigation={this.props.navigation} headerTitle={i18n.t('screens.planning')}> <Agenda // the list of items that have to be displayed in agenda. If you want to render item as empty date // the value of date key kas to be an empty array []. If there exists no value for date key it is // considered that the date in question is not yet loaded items={this.state.agendaItems} // initially selected day - selected={this.getCurrentDate()} + selected={this.currentDate} // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined - minDate={this.getCurrentDate()} + minDate={this.currentDate} // Max amount of months allowed to scroll to the past. Default = 50 pastScrollRange={1} // Max amount of months allowed to scroll to the future. Default = 50 futureScrollRange={AGENDA_MONTH_SPAN} // If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly. - onRefresh={() => this._onRefresh()} + onRefresh={this.onRefresh} // callback that fires when the calendar is opened or closed - onCalendarToggled={(calendarOpened) => { - this.setState({calendarShowing: calendarOpened}) - }} + onCalendarToggled={this.onCalendarToggled} // Set this true while waiting for new data from a refresh refreshing={this.state.refreshing} - renderItem={(item) => this.getRenderItem(item)} - renderEmptyDate={() => this.getRenderEmptyDate()} - rowHasChanged={() => this.rowHasChanged()} + renderItem={this.getRenderItem} + renderEmptyDate={this.getRenderEmptyDate} + rowHasChanged={this.rowHasChanged} // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday. firstDay={1} // ref to this agenda in order to handle back button event - ref={(ref) => this.agendaRef = ref} + ref={this.onAgendaRef} // agenda theme theme={{ backgroundColor: ThemeManager.getCurrentThemeVariables().agendaBackgroundColor, diff --git a/screens/Proximo/ProximoAboutScreen.js b/screens/Proximo/ProximoAboutScreen.js index 269a5f6..65a19cf 100644 --- a/screens/Proximo/ProximoAboutScreen.js +++ b/screens/Proximo/ProximoAboutScreen.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react'; -import {Image, Linking, View} from 'react-native'; +import {Image, View} from 'react-native'; import {Card, CardItem, Container, Content, H2, Left, Text} from 'native-base'; import CustomHeader from "../../components/CustomHeader"; import i18n from "i18n-js"; diff --git a/screens/Proximo/ProximoListScreen.js b/screens/Proximo/ProximoListScreen.js index 446e17b..3df22a6 100644 --- a/screens/Proximo/ProximoListScreen.js +++ b/screens/Proximo/ProximoListScreen.js @@ -70,12 +70,28 @@ export default class ProximoListScreen extends React.Component<Props, State> { sortNameIcon: '', modalCurrentDisplayItem: {}, }; - _menu: Menu; + sortMenuRef: Menu; + + onMenuRef: Function; + onSearchStringChange: Function; + onSelectSortModeName: Function; + onSelectSortModePrice: Function; + onSortMenuPress: Function; + renderItem: Function; + onListItemPress: Function; constructor(props: any) { super(props); this.modalRef = React.createRef(); this.originalData = this.navData['data']; + + this.onMenuRef = this.onMenuRef.bind(this); + this.onSearchStringChange = this.onSearchStringChange.bind(this); + this.onSelectSortModeName = this.onSelectSortModeName.bind(this); + this.onSelectSortModePrice = this.onSelectSortModePrice.bind(this); + this.onSortMenuPress = this.onSortMenuPress.bind(this); + this.renderItem = this.renderItem.bind(this); + this.onListItemPress = this.onListItemPress.bind(this); } /** @@ -83,8 +99,8 @@ export default class ProximoListScreen extends React.Component<Props, State> { * * @param ref The menu reference */ - setMenuRef = (ref: Menu) => { - this._menu = ref; + onMenuRef(ref: Menu) { + this.sortMenuRef = ref; }; /** @@ -131,7 +147,7 @@ export default class ProximoListScreen extends React.Component<Props, State> { break; } this.setupSortIcons(mode, isReverse); - this._menu.hide(); + this.sortMenuRef.hide(); } /** @@ -214,7 +230,7 @@ export default class ProximoListScreen extends React.Component<Props, State> { return filteredData; } - search(str: string) { + onSearchStringChange(str: string) { this.setState({ currentlyDisplayedData: this.filterData(str) }) @@ -251,7 +267,7 @@ export default class ProximoListScreen extends React.Component<Props, State> { ); } - showItemDetails(item: Object) { + onListItemPress(item: Object) { this.setState({ modalCurrentDisplayItem: item }); @@ -260,16 +276,27 @@ export default class ProximoListScreen extends React.Component<Props, State> { } } + onSelectSortModeName() { + this.sortModeSelected(sortMode.name); + } + + onSelectSortModePrice() { + this.sortModeSelected(sortMode.price); + } + + onSortMenuPress() { + this.sortMenuRef.show(); + } + + getSortMenu() { return ( <Menu - ref={this.setMenuRef} + ref={this.onMenuRef} button={ <Touchable style={{padding: 6}} - onPress={() => - this._menu.show() - }> + onPress={this.onSortMenuPress}> <CustomMaterialIcon color={Platform.OS === 'ios' ? ThemeManager.getCurrentThemeVariables().brandPrimary : "#fff"} icon={'sort'}/> @@ -277,12 +304,12 @@ export default class ProximoListScreen extends React.Component<Props, State> { } > <MenuItem - onPress={() => this.sortModeSelected(sortMode.name)}> + onPress={this.onSelectSortModeName}> {this.state.sortNameIcon} {i18n.t('proximoScreen.sortName')} </MenuItem> <MenuItem - onPress={() => this.sortModeSelected(sortMode.price)}> + onPress={this.onSelectSortModePrice}> {this.state.sortPriceIcon} {i18n.t('proximoScreen.sortPrice')} </MenuItem> @@ -290,7 +317,39 @@ export default class ProximoListScreen extends React.Component<Props, State> { ); } + renderItem({item}: Object) { + return (<ListItem + thumbnail + onPress={this.onListItemPress} + > + <Left> + <Thumbnail square source={{uri: item.image}}/> + </Left> + <Body> + <Text style={{marginLeft: 20}}> + {item.name} + </Text> + <Text note style={{ + marginLeft: 20, + color: this.getStockColor(parseInt(item.quantity)) + }}> + {item.quantity + ' ' + i18n.t('proximoScreen.inStock')} + </Text> + </Body> + <Right> + <Text style={{fontWeight: "bold"}}> + {item.price}€ + </Text> + </Right> + </ListItem>); + } + + keyExtractor(item: Object) { + return item.name + item.code; + } + render() { + // console.log("rendering ProximoListScreen"); const nav = this.props.navigation; return ( <Container> @@ -303,7 +362,7 @@ export default class ProximoListScreen extends React.Component<Props, State> { hasBackButton={true} navigation={nav} hasSearchField={true} - searchCallback={(text) => this.search(text)} + searchCallback={this.onSearchStringChange} shouldFocusSearchBar={this.shouldFocusSearchBar} rightButton={this.getSortMenu()} /> @@ -311,35 +370,9 @@ export default class ProximoListScreen extends React.Component<Props, State> { <FlatList data={this.state.currentlyDisplayedData} extraData={this.state.currentlyDisplayedData} - keyExtractor={(item) => item.name + item.code} + keyExtractor={this.keyExtractor} style={{minHeight: 300, width: '100%'}} - renderItem={({item}) => - <ListItem - thumbnail - onPress={() => { - this.showItemDetails(item); - }} - > - <Left> - <Thumbnail square source={{uri: item.image}}/> - </Left> - <Body> - <Text style={{marginLeft: 20}}> - {item.name} - </Text> - <Text note style={{ - marginLeft: 20, - color: this.getStockColor(parseInt(item.quantity)) - }}> - {item.quantity + ' ' + i18n.t('proximoScreen.inStock')} - </Text> - </Body> - <Right> - <Text style={{fontWeight: "bold"}}> - {item.price}€ - </Text> - </Right> - </ListItem>} + renderItem={this.renderItem} /> </Container> ); diff --git a/screens/Proximo/ProximoMainScreen.js b/screens/Proximo/ProximoMainScreen.js index b8bf269..06995b4 100644 --- a/screens/Proximo/ProximoMainScreen.js +++ b/screens/Proximo/ProximoMainScreen.js @@ -18,8 +18,13 @@ const DATA_URL = "https://etud.insa-toulouse.fr/~proximo/data/stock-v2.json"; */ export default class ProximoMainScreen extends FetchedDataSectionList { + onPressSearchBtn: Function; + onPressAboutBtn: Function; + constructor() { super(DATA_URL, 0); + this.onPressSearchBtn = this.onPressSearchBtn.bind(this); + this.onPressAboutBtn = this.onPressAboutBtn.bind(this); } static sortFinalData(a: Object, b: Object) { @@ -114,7 +119,7 @@ export default class ProximoMainScreen extends FetchedDataSectionList { return availableArticles; } - getRightButton() { + onPressSearchBtn() { let searchScreenData = { shouldFocusSearchBar: true, data: { @@ -127,8 +132,14 @@ export default class ProximoMainScreen extends FetchedDataSectionList { this.getAvailableArticles(this.state.fetchedData.articles, undefined) : [] }, }; + this.props.navigation.navigate('ProximoListScreen', searchScreenData); + } + onPressAboutBtn() { + this.props.navigation.navigate('ProximoAboutScreen'); + } + getRightButton() { return ( <View style={{ @@ -136,14 +147,14 @@ export default class ProximoMainScreen extends FetchedDataSectionList { }}> <Touchable style={{padding: 6}} - onPress={() => this.props.navigation.navigate('ProximoListScreen', searchScreenData)}> + onPress={this.onPressSearchBtn}> <CustomMaterialIcon color={Platform.OS === 'ios' ? ThemeManager.getCurrentThemeVariables().brandPrimary : "#fff"} icon="magnify"/> </Touchable> <Touchable style={{padding: 6}} - onPress={() => this.props.navigation.navigate('ProximoAboutScreen')}> + onPress={this.onPressAboutBtn}> <CustomMaterialIcon color={Platform.OS === 'ios' ? ThemeManager.getCurrentThemeVariables().brandPrimary : "#fff"} icon="information"/> @@ -152,19 +163,18 @@ export default class ProximoMainScreen extends FetchedDataSectionList { ); } - getRenderItem(item: Object, section: Object, data: Object) { + getRenderItem(item: Object, section: Object) { let dataToSend = { shouldFocusSearchBar: false, data: item, }; + const onPress = this.props.navigation.navigate.bind(this, 'ProximoListScreen', dataToSend); if (item.data.length > 0) { return ( <ListItem button thumbnail - onPress={() => { - this.props.navigation.navigate('ProximoListScreen', dataToSend); - }} + onPress={onPress} > <Left> <CustomMaterialIcon diff --git a/screens/Proxiwash/ProxiwashScreen.js b/screens/Proxiwash/ProxiwashScreen.js index 6a3b075..ae7e7cc 100644 --- a/screens/Proxiwash/ProxiwashScreen.js +++ b/screens/Proxiwash/ProxiwashScreen.js @@ -36,6 +36,8 @@ const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds */ export default class ProxiwashScreen extends FetchedDataSectionList { + onAboutPress: Function; + /** * Creates machine state parameters using current theme and translations */ @@ -75,6 +77,8 @@ export default class ProxiwashScreen extends FetchedDataSectionList { machinesWatched: [], }; this.setMinTimeRefresh(30); + + this.onAboutPress = this.onAboutPress.bind(this); } /** @@ -82,7 +86,6 @@ export default class ProxiwashScreen extends FetchedDataSectionList { */ componentDidMount() { super.componentDidMount(); - if (AsyncStorageManager.getInstance().preferences.expoToken.current !== '') { // Get latest watchlist from server NotificationsManager.getMachineNotificationWatchlist((fetchedList) => { @@ -241,13 +244,14 @@ export default class ProxiwashScreen extends FetchedDataSectionList { showAlert(title: string, item: Object, isDryer: boolean) { let buttons = [{text: i18n.t("proxiwashScreen.modal.ok")}]; let message = modalStateStrings[MACHINE_STATES[item.state]]; + const onPress = this.setupNotifications.bind(this, item.number); if (MACHINE_STATES[item.state] === MACHINE_STATES["EN COURS"]) { buttons = [ { text: this.isMachineWatched(item.number) ? i18n.t("proxiwashScreen.modal.disableNotifications") : i18n.t("proxiwashScreen.modal.enableNotifications"), - onPress: () => this.setupNotifications(item.number) + onPress: onPress }, { text: i18n.t("proxiwashScreen.modal.cancel") @@ -272,11 +276,15 @@ export default class ProxiwashScreen extends FetchedDataSectionList { ); } + onAboutPress() { + this.props.navigation.navigate('ProxiwashAboutScreen'); + } + getRightButton(): * { return ( <Touchable style={{padding: 6}} - onPress={() => this.props.navigation.navigate('ProxiwashAboutScreen')}> + onPress={this.onAboutPress}> <CustomMaterialIcon color={Platform.OS === 'ios' ? ThemeManager.getCurrentThemeVariables().brandPrimary : "#fff"} icon="information"/> @@ -289,13 +297,13 @@ export default class ProxiwashScreen extends FetchedDataSectionList { * * @param item The object containing the item's FetchedData * @param section The object describing the current SectionList section - * @param data The full FetchedData used by the SectionList * @returns {React.Node} */ - getRenderItem(item: Object, section: Object, data: Object) { + getRenderItem(item: Object, section: Object) { let isMachineRunning = MACHINE_STATES[item.state] === MACHINE_STATES["EN COURS"]; let machineName = (section.title === i18n.t('proxiwashScreen.dryers') ? i18n.t('proxiwashScreen.dryer') : i18n.t('proxiwashScreen.washer')) + ' n°' + item.number; let isDryer = section.title === i18n.t('proxiwashScreen.dryers'); + const onPress = this.showAlert.bind(this, machineName, item, isDryer); return ( <Card style={{ flex: 0, @@ -320,7 +328,7 @@ export default class ProxiwashScreen extends FetchedDataSectionList { backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor }}/> <PlatformTouchable - onPress={() => this.showAlert(machineName, item, isDryer)} + onPress={onPress} style={{ height: 64, position: 'absolute', diff --git a/screens/SelfMenuScreen.js b/screens/SelfMenuScreen.js index b9226bd..98e01f8 100644 --- a/screens/SelfMenuScreen.js +++ b/screens/SelfMenuScreen.js @@ -119,7 +119,7 @@ export default class SelfMenuScreen extends FetchedDataSectionList { ); } - getRenderItem(item: Object, section: Object, data: Object) { + getRenderItem(item: Object, section: Object) { return ( <Card style={{ flex: 0, diff --git a/screens/SettingsScreen.js b/screens/SettingsScreen.js index 02047a0..53b7d68 100644 --- a/screens/SettingsScreen.js +++ b/screens/SettingsScreen.js @@ -43,6 +43,17 @@ export default class SettingsScreen extends React.Component<Props, State> { startScreenPickerSelected: AsyncStorageManager.getInstance().preferences.defaultStartScreen.current, }; + onProxiwashNotifPickerValueChange: Function; + onStartScreenPickerValueChange: Function; + onToggleNightMode: Function; + + constructor() { + super(); + this.onProxiwashNotifPickerValueChange = this.onProxiwashNotifPickerValueChange.bind(this); + this.onStartScreenPickerValueChange = this.onStartScreenPickerValueChange.bind(this); + this.onToggleNightMode = this.onToggleNightMode.bind(this); + } + /** * Get a list item using the specified control * @@ -118,7 +129,7 @@ export default class SettingsScreen extends React.Component<Props, State> { mode="dropdown" style={{width: 120}} selectedValue={this.state.proxiwashNotifPickerSelected} - onValueChange={(value) => this.onProxiwashNotifPickerValueChange(value)} + onValueChange={this.onProxiwashNotifPickerValueChange} > <Picker.Item label={i18n.t('settingsScreen.proxiwashNotifReminderPicker.never')} value="never"/> <Picker.Item label={i18n.t('settingsScreen.proxiwashNotifReminderPicker.5')} value="5"/> @@ -141,7 +152,7 @@ export default class SettingsScreen extends React.Component<Props, State> { mode="dropdown" style={{width: 120}} selectedValue={this.state.startScreenPickerSelected} - onValueChange={(value) => this.onStartScreenPickerValueChange(value)} + onValueChange={this.onStartScreenPickerValueChange} > <Picker.Item label={i18n.t('screens.home')} value="Home"/> <Picker.Item label={i18n.t('screens.planning')} value="Planning"/> @@ -155,7 +166,7 @@ export default class SettingsScreen extends React.Component<Props, State> { /** * Toggle night mode and save it to preferences */ - toggleNightMode() { + onToggleNightMode() { ThemeManager.getInstance().setNightMode(!this.state.nightMode); this.setState({nightMode: !this.state.nightMode}); this.resetStack(); @@ -203,7 +214,7 @@ export default class SettingsScreen extends React.Component<Props, State> { <Right> <CheckBox checked={this.state.nightMode} - onPress={() => this.toggleNightMode()} + onPress={onPressCallback} style={{marginRight: 20}}/> </Right> </ListItem> @@ -221,7 +232,7 @@ export default class SettingsScreen extends React.Component<Props, State> { <Text>{i18n.t('settingsScreen.generalCard')}</Text> </CardItem> <List> - {this.getToggleItem(() => this.toggleNightMode(), 'theme-light-dark', i18n.t('settingsScreen.nightMode'), i18n.t('settingsScreen.nightModeSub'))} + {this.getToggleItem(this.onToggleNightMode, 'theme-light-dark', i18n.t('settingsScreen.nightMode'), i18n.t('settingsScreen.nightModeSub'))} {SettingsScreen.getGeneralItem(this.getStartScreenPicker(), 'power', i18n.t('settingsScreen.startScreen'), i18n.t('settingsScreen.startScreenSub'))} </List> </Card>