[RN] Change default WelcomeScreen tab and persist user choice

This commit is contained in:
Bettenbuk Zoltan 2018-06-05 14:34:40 +02:00 committed by Lyubo Marinov
parent 4ab8d98cd1
commit dcfebf746f
7 changed files with 159 additions and 52 deletions

View File

@ -23,6 +23,11 @@ type Props = {
*/
dispatch: Function,
/**
* Callback to execute on page change.
*/
onSelectPage: ?Function,
/**
* The pages of the PagedList component to be rendered.
* Note: page.component may be undefined and then they don't need to be
@ -61,18 +66,12 @@ export default class AbstractPagedList extends Component<Props, State> {
}
/**
* Implements React {@code Component}'s componentWillReceiveProps.
* Implements React's {@code Component} componentDidMount.
*
* @inheritdoc
*/
componentWillReceiveProps(newProps: Props) {
const { defaultPage } = newProps;
if (defaultPage !== this.props.defaultPage) {
// Default page changed due to a redux update. This is likely to
// happen after APP_WILL_MOUNT. So we update the active tab.
this._platformSpecificPageSelect(defaultPage);
}
componentDidMount() {
this._maybeRefreshActivePage();
}
/**
@ -119,6 +118,26 @@ export default class AbstractPagedList extends Component<Props, State> {
this._selectPage(pageIndex);
}
_maybeRefreshActivePage: () => void
/**
* Components that this PagedList displays may have a refresh function to
* refresh its content when displayed (or based on custom logic). This
* function invokes this logic if it's present.
*
* @private
* @returns {void}
*/
_maybeRefreshActivePage() {
const selectedPage = this.props.pages[this.state.pageIndex];
if (selectedPage && selectedPage.component) {
const { refresh } = selectedPage.component;
typeof refresh === 'function' && refresh(this.props.dispatch);
}
}
_renderPagedList: boolean => React$Node;
_selectPage: number => void;
@ -133,19 +152,15 @@ export default class AbstractPagedList extends Component<Props, State> {
_selectPage(pageIndex: number) {
const validatedPageIndex = this._validatePageIndex(pageIndex);
const { onSelectPage } = this.props;
if (typeof onSelectPage === 'function') {
onSelectPage(validatedPageIndex);
}
this.setState({
pageIndex: validatedPageIndex
});
// The page's Component may have a refresh(dispatch) function which we
// invoke when the page is selected.
const selectedPage = this.props.pages[validatedPageIndex];
if (selectedPage && selectedPage.component) {
const { refresh } = selectedPage.component;
typeof refresh === 'function' && refresh(this.props.dispatch);
}
}, () => this._maybeRefreshActivePage());
}
_validatePageIndex: number => number

View File

@ -96,14 +96,21 @@ class MeetingList extends Component<Props> {
* @inheritdoc
*/
render() {
const { disabled } = this.props;
const { _authorization, disabled } = this.props;
return (
<NavigateSectionList
disabled = { disabled }
onPress = { this._onPress }
onRefresh = { this._onRefresh }
renderListEmptyComponent = { this._getRenderListEmptyComponent }
// If we don't provide a list specific renderListEmptyComponent,
// then the default empty component of the NavigateSectionList
// will be rendered, which (atm) is a simple "Pull to refresh"
// message.
renderListEmptyComponent
= { _authorization === 'denied'
? this._getRenderListEmptyComponent() : undefined }
sections = { this._toDisplayableList() } />
);
}
@ -115,29 +122,25 @@ class MeetingList extends Component<Props> {
* of the default one in the {@link NavigateSectionList}.
*
* @private
* @returns {Component}
* @returns {Function}
*/
_getRenderListEmptyComponent() {
const { _authorization, t } = this.props;
const { t } = this.props;
if (_authorization === 'denied') {
return (
<View style = { styles.noPermissionMessageView }>
<Text style = { styles.noPermissionMessageText }>
{ t('calendarSync.permissionMessage') }
return (
<View style = { styles.noPermissionMessageView }>
<Text style = { styles.noPermissionMessageText }>
{ t('calendarSync.permissionMessage') }
</Text>
<TouchableOpacity
onPress = { openSettings }
style = { styles.noPermissionMessageButton } >
<Text style = { styles.noPermissionMessageButtonText }>
{ t('calendarSync.permissionButton') }
</Text>
<TouchableOpacity
onPress = { openSettings }
style = { styles.noPermissionMessageButton } >
<Text style = { styles.noPermissionMessageButtonText }>
{ t('calendarSync.permissionButton') }
</Text>
</TouchableOpacity>
</View>
);
}
return null;
</TouchableOpacity>
</View>
);
}
_onPress: string => Function;

View File

@ -45,7 +45,7 @@ CALENDAR_ENABLED
case SET_CALENDAR_AUTHORIZATION:
return {
...state,
authorization: action.status
authorization: action.authorization
};
case SET_CALENDAR_EVENTS:

View File

@ -1,3 +1,5 @@
// @flow
/**
* The type of the (redux) action which sets the visibility of
* {@link WelcomePageSideBar}.
@ -8,3 +10,15 @@
* }
*/
export const SET_SIDEBAR_VISIBLE = Symbol('SET_SIDEBAR_VISIBLE');
/**
* Action to update the default page index of the {@code WelcomePageLists}
* component.
*
* {
* type: SET_WELCOME_PAGE_LIST_DEFAULT_PAGE,
* pageIndex: number
* }
*/
export const SET_WELCOME_PAGE_LIST_DEFAULT_PAGE
= Symbol('SET_WELCOME_PAGE_LIST_DEFAULT_PAGE');

View File

@ -1,6 +1,26 @@
// @flow
import { SET_SIDEBAR_VISIBLE } from './actionTypes';
import {
SET_SIDEBAR_VISIBLE,
SET_WELCOME_PAGE_LIST_DEFAULT_PAGE
} from './actionTypes';
/**
* Action to update the default page index of the {@code WelcomePageLists}
* component.
*
* @param {number} pageIndex - The index of the selected page.
* @returns {{
* type: SET_WELCOME_PAGE_LIST_DEFAULT_PAGE,
* pageIndex: number
* }}
*/
export function setWelcomePageListDefaultPage(pageIndex: number) {
return {
type: SET_WELCOME_PAGE_LIST_DEFAULT_PAGE,
pageIndex
};
}
/**
* Sets the visibility of {@link WelcomePageSideBar}.

View File

@ -9,18 +9,25 @@ import { PagedList } from '../../base/react';
import { MeetingList } from '../../calendar-sync';
import { RecentList } from '../../recent-list';
import { setWelcomePageListDefaultPage } from '../actions';
type Props = {
/**
* True if the calendar feature has fetched entries, false otherwise
* The stored default page index.
*/
_hasCalendarEntries: boolean,
_defaultPage: number,
/**
* Renders the lists disabled.
*/
disabled: boolean,
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The i18n translate function.
*/
@ -72,6 +79,8 @@ class WelcomePageLists extends Component<Props> {
icon: isAndroid ? 'event_note' : IOS_CALENDAR_ICON,
title: t('welcomepage.calendar')
} ];
this._onSelectPage = this._onSelectPage.bind(this);
}
/**
@ -80,15 +89,35 @@ class WelcomePageLists extends Component<Props> {
* @inheritdoc
*/
render() {
const { disabled, _hasCalendarEntries } = this.props;
const { disabled, _defaultPage } = this.props;
if (typeof _defaultPage === 'undefined') {
return null;
}
return (
<PagedList
defaultPage = { _hasCalendarEntries ? 1 : 0 }
defaultPage = { _defaultPage }
disabled = { disabled }
onSelectPage = { this._onSelectPage }
pages = { this.pages } />
);
}
_onSelectPage: number => void
/**
* Callback for the {@code PagedList} page select action.
*
* @private
* @param {number} pageIndex - The index of the selected page.
* @returns {void}
*/
_onSelectPage(pageIndex) {
const { dispatch } = this.props;
dispatch(setWelcomePageListDefaultPage(pageIndex));
}
}
/**
@ -98,14 +127,18 @@ class WelcomePageLists extends Component<Props> {
* @param {Object} state - The redux state.
* @protected
* @returns {{
* _hasCalendarEntries: boolean
* _hasRecentListEntries: boolean
* }}
*/
function _mapStateToProps(state: Object) {
const { events } = state['features/calendar-sync'];
const { defaultPage } = state['features/welcome'];
const recentList = state['features/recent-list'];
const _hasRecentListEntries = Boolean(recentList && recentList.length);
return {
_hasCalendarEntries: Boolean(events && events.length)
_defaultPage: defaultPage === 'undefined'
? _hasRecentListEntries ? 0 : 1
: defaultPage
};
}

View File

@ -1,12 +1,28 @@
// @flow
import { ReducerRegistry } from '../base/redux';
import { SET_SIDEBAR_VISIBLE } from './actionTypes';
import { PersistenceRegistry } from '../base/storage';
import {
SET_SIDEBAR_VISIBLE,
SET_WELCOME_PAGE_LIST_DEFAULT_PAGE
} from './actionTypes';
/**
* The Redux store name this feature uses.
*/
const STORE_NAME = 'features/welcome';
/**
* Sets up the persistence of the feature {@code features/welcome}.
*/
PersistenceRegistry.register(STORE_NAME, {
defaultPage: true
});
/**
* Reduces redux actions for the purposes of {@code features/welcome}.
*/
ReducerRegistry.register('features/welcome', (state = {}, action) => {
ReducerRegistry.register(STORE_NAME, (state = {}, action) => {
switch (action.type) {
case SET_SIDEBAR_VISIBLE:
return {
@ -14,6 +30,12 @@ ReducerRegistry.register('features/welcome', (state = {}, action) => {
sideBarVisible: action.visible
};
case SET_WELCOME_PAGE_LIST_DEFAULT_PAGE:
return {
...state,
defaultPage: action.pageIndex
};
default:
return state;
}