More generic way to refresh lists on the welcome screen

This commit is contained in:
zbettenbuk 2018-04-16 20:07:38 +02:00 committed by Lyubo Marinov
parent 374e3ccf2c
commit 63c165ee8b
4 changed files with 131 additions and 124 deletions

View File

@ -35,14 +35,6 @@ type Props = {
*/ */
dispatch: Function, dispatch: Function,
/**
* Tells the component if it's being displayed at the moment, or not. Note:
* as an example, on Android it can happen that the component is rendered
* but not displayed, because components like ViewPagerAndroid render their
* children even if they are not visible at the moment.
*/
displayed: boolean,
/** /**
* The translate function. * The translate function.
*/ */
@ -85,12 +77,6 @@ class MeetingList extends Component<Props> {
constructor(props) { constructor(props) {
super(props); super(props);
const { dispatch, displayed } = props;
if (displayed) {
dispatch(refreshCalendar());
}
this._getRenderListEmptyComponent this._getRenderListEmptyComponent
= this._getRenderListEmptyComponent.bind(this); = this._getRenderListEmptyComponent.bind(this);
this._onPress = this._onPress.bind(this); this._onPress = this._onPress.bind(this);

View File

@ -2,7 +2,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { isCalendarEnabled } from '../../calendar-sync';
import { MeetingList } from '../../calendar-sync';
import { RecentList } from '../../recent-list'; import { RecentList } from '../../recent-list';
import styles from './styles'; import styles from './styles';
@ -28,7 +29,7 @@ type Props = {
* The i18n translate function * The i18n translate function
*/ */
t: Function t: Function
} };
type State = { type State = {
@ -36,16 +37,16 @@ type State = {
* The currently selected page. * The currently selected page.
*/ */
pageIndex: number pageIndex: number
} };
/** /**
* Abstract class for the platform specific paged lists. * Abstract class for the platform specific paged lists.
*/ */
export default class AbstractPagedList extends Component<Props, State> { export default class AbstractPagedList extends Component<Props, State> {
/** /**
* True if the calendar feature is enabled on the platform, false otherwise. * The list of pages displayed in the component, referenced by page index.
*/ */
_calendarEnabled: boolean _pages: Array<Object>;
/** /**
* Constructor of the component. * Constructor of the component.
@ -55,7 +56,13 @@ export default class AbstractPagedList extends Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this._calendarEnabled = isCalendarEnabled(); this._pages = [];
for (const component of [ RecentList, MeetingList ]) {
// XXX Certain pages may be contributed by optional features. For
// example, MeetingList is contributed by the calendar feature and
// apps i.e. SDK consumers may not enable the calendar feature.
component && this._pages.push(component);
}
this.state = { this.state = {
pageIndex: DEFAULT_PAGE pageIndex: DEFAULT_PAGE
@ -77,15 +84,43 @@ export default class AbstractPagedList extends Component<Props, State> {
disabled ? styles.pagedListContainerDisabled : null disabled ? styles.pagedListContainerDisabled : null
] }> ] }>
{ {
(this._calendarEnabled && this._renderPagedList(disabled)) this._pages.length > 1
|| <RecentList ? this._renderPagedList(disabled)
disabled = { disabled } : React.createElement(
style = { styles.pagedList } /> /* type */ this._pages[0],
/* props */ {
disabled,
style: styles.pagedList
})
} }
</View> </View>
); );
} }
_renderPagedList: boolean => Object _renderPagedList: boolean => React$Node;
_selectPage: number => void;
/**
* Sets the selected page.
*
* @param {number} pageIndex - The index of the active page.
* @protected
* @returns {void}
*/
_selectPage(pageIndex: number) {
this.setState({
pageIndex
});
// The page's Component may have a refresh(dispatch) function which we
// invoke when the page is selected.
const selectedPageComponent = this._pages[pageIndex];
if (selectedPageComponent) {
const { refresh } = selectedPageComponent;
typeof refresh === 'function' && refresh(this.props.dispatch);
}
}
} }

View File

@ -1,6 +1,8 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { Text, TouchableOpacity, View, ViewPagerAndroid } from 'react-native'; import { Text, TouchableOpacity, View, ViewPagerAndroid } from 'react-native';
import { connect } from 'react-redux';
import { Icon } from '../../base/font-icons'; import { Icon } from '../../base/font-icons';
import { MeetingList } from '../../calendar-sync'; import { MeetingList } from '../../calendar-sync';
@ -14,24 +16,74 @@ import styles from './styles';
* *
* @extends PagedList * @extends PagedList
*/ */
export default class PagedList extends AbstractPagedList { class PagedList extends AbstractPagedList {
/** /**
* A reference to the viewpager. * A reference to the viewpager.
*/ */
_viewPager: Object; _viewPager: Object;
/** /**
* Constructor of the PagedList Component. * Initializes a new {@code PagedList} instance.
* *
* @inheritdoc * @inheritdoc
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
// Bind event handlers so they are only bound once per instance.
this._getIndicatorStyle = this._getIndicatorStyle.bind(this); this._getIndicatorStyle = this._getIndicatorStyle.bind(this);
this._onPageSelected = this._onPageSelected.bind(this); this._onPageSelected = this._onPageSelected.bind(this);
this._onSelectPage = this._onSelectPage.bind(this); this._onSelectPage = this._onSelectPage.bind(this);
this._setPagerReference = this._setPagerReference.bind(this); this._setViewPager = this._setViewPager.bind(this);
}
_getIndicatorStyle: number => Object;
/**
* Constructs the style of an indicator.
*
* @param {number} indicatorIndex - The index of the indicator.
* @private
* @returns {Object}
*/
_getIndicatorStyle(indicatorIndex) {
if (this.state.pageIndex === indicatorIndex) {
return styles.pageIndicatorTextActive;
}
return null;
}
_onPageSelected: Object => void;
/**
* Updates the index of the currently selected page.
*
* @param {Object} event - The native event of the callback.
* @private
* @returns {void}
*/
_onPageSelected({ nativeEvent: { position } }) {
if (this.state.pageIndex !== position) {
this._selectPage(position);
}
}
_onSelectPage: number => Function;
/**
* Constructs a function to be used as a callback for the tab bar.
*
* @param {number} pageIndex - The index of the page to activate via the
* callback.
* @private
* @returns {Function}
*/
_onSelectPage(pageIndex) {
return () => {
this._viewPager.setPage(pageIndex);
this._selectPage(pageIndex);
};
} }
/** /**
@ -42,23 +94,19 @@ export default class PagedList extends AbstractPagedList {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderPagedList(disabled) { _renderPagedList(disabled) {
const { pageIndex } = this.state;
return ( return (
<View style = { styles.pagedListContainer }> <View style = { styles.pagedListContainer }>
<ViewPagerAndroid <ViewPagerAndroid
initialPage = { DEFAULT_PAGE } initialPage = { DEFAULT_PAGE }
onPageSelected = { this._onPageSelected } onPageSelected = { this._onPageSelected }
peekEnabled = { true } peekEnabled = { true }
ref = { this._setPagerReference } ref = { this._setViewPager }
style = { styles.pagedList }> style = { styles.pagedList }>
<View key = { 0 }> <View key = { 0 }>
<RecentList disabled = { disabled } /> <RecentList disabled = { disabled } />
</View> </View>
<View key = { 1 }> <View key = { 1 }>
<MeetingList <MeetingList disabled = { disabled } />
disabled = { disabled }
displayed = { pageIndex === 1 } />
</View> </View>
</ViewPagerAndroid> </ViewPagerAndroid>
<View style = { styles.pageIndicatorContainer }> <View style = { styles.pageIndicatorContainer }>
@ -107,69 +155,19 @@ export default class PagedList extends AbstractPagedList {
); );
} }
_getIndicatorStyle: number => Object; _setViewPager: Object => void;
/** /**
* Constructs the style of an indicator. * Sets the {@link ViewPagerAndroid} instance.
* *
* @param {ViewPagerAndroid} viewPager - The {@code ViewPagerAndroid}
* instance.
* @private * @private
* @param {number} indicatorIndex - The index of the indicator.
* @returns {Object}
*/
_getIndicatorStyle(indicatorIndex) {
if (this.state.pageIndex === indicatorIndex) {
return styles.pageIndicatorTextActive;
}
return null;
}
_onPageSelected: Object => void;
/**
* Updates the index of the currently selected page.
*
* @private
* @param {Object} event - The native event of the callback.
* @returns {void} * @returns {void}
*/ */
_onPageSelected({ nativeEvent: { position } }) { _setViewPager(viewPager) {
if (this.state.pageIndex !== position) { this._viewPager = viewPager;
this.setState({
pageIndex: position
});
} }
} }
_onSelectPage: number => Function export default connect()(PagedList);
/**
* Constructs a function to be used as a callback for the tab bar.
*
* @private
* @param {number} pageIndex - The index of the page to activate via the
* callback.
* @returns {Function}
*/
_onSelectPage(pageIndex) {
return () => {
this._viewPager.setPage(pageIndex);
this.setState({
pageIndex
});
};
}
_setPagerReference: Object => void
/**
* Sets the pager's reference for direct modification.
*
* @private
* @param {React@Node} component - The pager component.
* @returns {void}
*/
_setPagerReference(component) {
this._viewPager = component;
}
}

View File

@ -5,7 +5,7 @@ import { TabBarIOS } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
import { MeetingList, refreshCalendar } from '../../calendar-sync'; import { MeetingList } from '../../calendar-sync';
import { RecentList } from '../../recent-list'; import { RecentList } from '../../recent-list';
import AbstractPagedList from './AbstractPagedList'; import AbstractPagedList from './AbstractPagedList';
@ -21,15 +21,30 @@ const CALENDAR_ICON = require('../../../../images/calendar.png');
class PagedList extends AbstractPagedList { class PagedList extends AbstractPagedList {
/** /**
* Constructor of the PagedList Component. * Initializes a new {@code PagedList} instance.
* *
* @inheritdoc * @inheritdoc
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
// Bind event handlers so they are only bound once per instance.
this._onTabSelected = this._onTabSelected.bind(this); this._onTabSelected = this._onTabSelected.bind(this);
} }
_onTabSelected: number => Function;
/**
* Constructs a callback to update the selected tab.
*
* @param {number} tabIndex - The selected tab.
* @private
* @returns {Function}
*/
_onTabSelected(tabIndex) {
return () => super._selectPage(tabIndex);
}
/** /**
* Renders the entire paged list if calendar is enabled. * Renders the entire paged list if calendar is enabled.
* *
@ -56,38 +71,11 @@ class PagedList extends AbstractPagedList {
onPress = { this._onTabSelected(1) } onPress = { this._onTabSelected(1) }
selected = { pageIndex === 1 } selected = { pageIndex === 1 }
title = { t('welcomepage.calendar') }> title = { t('welcomepage.calendar') }>
<MeetingList <MeetingList disabled = { disabled } />
disabled = { disabled } />
</TabBarIOS.Item> </TabBarIOS.Item>
</TabBarIOS> </TabBarIOS>
); );
} }
_onTabSelected: number => Function;
/**
* Constructs a callback to update the selected tab.
*
* @private
* @param {number} tabIndex - The selected tab.
* @returns {Function}
*/
_onTabSelected(tabIndex) {
return () => {
this.setState({
pageIndex: tabIndex
});
if (tabIndex === 1) {
/**
* This is a workaround as TabBarIOS doesn't invoke
* componentWillReciveProps on prop change of the MeetingList
* component.
*/
this.props.dispatch(refreshCalendar());
}
};
}
} }
export default translate(connect()(PagedList)); export default translate(connect()(PagedList));