diff --git a/images/history@2x.png b/images/history@2x.png new file mode 100644 index 000000000..f6096cab3 Binary files /dev/null and b/images/history@2x.png differ diff --git a/images/history@3x.png b/images/history@3x.png new file mode 100644 index 000000000..f837fda0e Binary files /dev/null and b/images/history@3x.png differ diff --git a/lang/main.json b/lang/main.json index efd6f46dd..3fde6edc4 100644 --- a/lang/main.json +++ b/lang/main.json @@ -55,6 +55,7 @@ "go": "GO", "join": "JOIN", "privacy": "Privacy", + "recentList": "History", "roomname": "Enter room name", "roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.", "sendFeedback": "Send feedback", diff --git a/react/features/welcome/components/AbstractPagedList.native.js b/react/features/base/react/components/native/AbstractPagedList.js similarity index 53% rename from react/features/welcome/components/AbstractPagedList.native.js rename to react/features/base/react/components/native/AbstractPagedList.js index e7ad0c707..42b162696 100644 --- a/react/features/welcome/components/AbstractPagedList.native.js +++ b/react/features/base/react/components/native/AbstractPagedList.js @@ -3,18 +3,16 @@ import React, { Component } from 'react'; import { View } from 'react-native'; -import { MeetingList } from '../../calendar-sync'; -import { RecentList } from '../../recent-list'; - import styles from './styles'; -/** - * The page to be displayed on render. - */ -export const DEFAULT_PAGE = 0; - type Props = { + /** + * The index (starting from 0) of the page that should be rendered + * active as default. + */ + defaultPage: number, + /** * Indicates if the list is disabled or not. */ @@ -26,9 +24,15 @@ type Props = { dispatch: Function, /** - * The i18n translate function + * The pages of the PagedList component to be rendered. + * Note: page.component may be undefined and then they don't need to be + * rendered. */ - t: Function + pages: Array<{ + component: Object, + icon: string | number, + title: string + }> }; type State = { @@ -40,14 +44,9 @@ type State = { }; /** - * Abstract class for the platform specific paged lists. + * Abstract class containing the platform independent logic of the paged lists. */ export default class AbstractPagedList extends Component { - /** - * The list of pages displayed in the component, referenced by page index. - */ - _pages: Array; - /** * Constructor of the component. * @@ -56,16 +55,13 @@ export default class AbstractPagedList extends Component { constructor(props: Props) { super(props); - 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); - } + // props.defaultPage may point to a non existing page if some of the + // pages are disabled. + const maxPageIndex + = props.pages.filter(page => page.component).length - 1; this.state = { - pageIndex: DEFAULT_PAGE + pageIndex: Math.max(0, Math.min(maxPageIndex, props.defaultPage)) }; } @@ -75,7 +71,8 @@ export default class AbstractPagedList extends Component { * @inheritdoc */ render() { - const { disabled } = this.props; + const { disabled, pages } = this.props; + const enabledPages = pages.filter(page => page.component); return ( { disabled ? styles.pagedListContainerDisabled : null ] }> { - this._pages.length > 1 + enabledPages.length > 1 ? this._renderPagedList(disabled) - : React.createElement( - /* type */ this._pages[0], - /* props */ { - disabled, - style: styles.pagedList - }) + : enabledPages.length === 1 + ? React.createElement( + /* type */ enabledPages[0].component, + /* props */ { + disabled, + style: styles.pagedList + }) : null } ); @@ -115,10 +113,10 @@ export default class AbstractPagedList extends Component { // The page's Component may have a refresh(dispatch) function which we // invoke when the page is selected. - const selectedPageComponent = this._pages[pageIndex]; + const selectedPage = this.props.pages[pageIndex]; - if (selectedPageComponent) { - const { refresh } = selectedPageComponent; + if (selectedPage && selectedPage.component) { + const { refresh } = selectedPage.component; typeof refresh === 'function' && refresh(this.props.dispatch); } diff --git a/react/features/base/react/components/native/PagedList.android.js b/react/features/base/react/components/native/PagedList.android.js new file mode 100644 index 000000000..7e4cd6825 --- /dev/null +++ b/react/features/base/react/components/native/PagedList.android.js @@ -0,0 +1,198 @@ +// @flow + +import React from 'react'; +import { Text, TouchableOpacity, View, ViewPagerAndroid } from 'react-native'; +import { connect } from 'react-redux'; + +import { Icon } from '../../../font-icons'; + +import AbstractPagedList from './AbstractPagedList'; +import styles from './styles'; + +/** + * An Android specific component to render a paged list. + * + * @extends PagedList + */ +class PagedList extends AbstractPagedList { + /** + * A reference to the viewpager. + */ + _viewPager: ViewPagerAndroid; + + /** + * Initializes a new {@code PagedList} instance. + * + * @inheritdoc + */ + constructor(props) { + super(props); + + // Bind event handlers so they are only bound once per instance. + this._onIconPress = this._onIconPress.bind(this); + this._getIndicatorStyle = this._getIndicatorStyle.bind(this); + this._onPageSelected = this._onPageSelected.bind(this); + this._setViewPager = this._setViewPager.bind(this); + } + + _onIconPress: number => Function; + + /** + * Constructs a function to be used as a callback for the icons in the tab + * bar. + * + * @param {number} pageIndex - The index of the page to activate via the + * callback. + * @private + * @returns {Function} + */ + _onIconPress(pageIndex) { + return () => { + this._viewPager.setPage(pageIndex); + this._selectPage(pageIndex); + }; + } + + _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.pageIndicatorActive; + } + + return null; + } + + _onPageSelected: Object => void; + + /** + * Updates the index of the currently selected page, based on the native + * event received from the {@link ViewPagerAndroid} component. + * + * @param {Object} event - The native event of the callback. + * @private + * @returns {void} + */ + _onPageSelected({ nativeEvent: { position } }) { + if (this.state.pageIndex !== position) { + this._selectPage(position); + } + } + + /** + * Renders a single page of the page list. + * + * @private + * @param {Object} page - The page to render. + * @param {number} index - The index of the rendered page. + * @param {boolean} disabled - Renders the page disabled. + * @returns {React$Node} + */ + _renderPage(page, index, disabled) { + return page.component + ? + { + React.createElement( + page.component, + { + disabled + }) + } + + : null; + } + + /** + * Renders a page indicator (icon) for the page. + * + * @private + * @param {Object} page - The page the indicator is rendered for. + * @param {number} index - The index of the page the indicator is rendered + * for. + * @param {boolean} disabled - Renders the indicator disabled. + * @returns {React$Node} + */ + _renderPageIndicator(page, index, disabled) { + return page.component + ? + + + + { page.title } + + + + : null; + } + + /** + * Renders the paged list if multiple pages are to be rendered. This is the + * platform dependent part of the component. + * + * @param {boolean} disabled - True if the rendered lists should be + * disabled. + * @returns {ReactElement} + */ + _renderPagedList(disabled) { + const { defaultPage, pages } = this.props; + + return ( + + + { + pages.map((page, index) => this._renderPage( + page, index, disabled + )) + } + + + { + pages.map((page, index) => this._renderPageIndicator( + page, index, disabled + )) + } + + + ); + } + + _setViewPager: Object => void; + + /** + * Sets the {@link ViewPagerAndroid} instance. + * + * @param {ViewPagerAndroid} viewPager - The {@code ViewPagerAndroid} + * instance. + * @private + * @returns {void} + */ + _setViewPager(viewPager) { + this._viewPager = viewPager; + } +} + +export default connect()(PagedList); diff --git a/react/features/base/react/components/native/PagedList.ios.js b/react/features/base/react/components/native/PagedList.ios.js new file mode 100644 index 000000000..dbc939fff --- /dev/null +++ b/react/features/base/react/components/native/PagedList.ios.js @@ -0,0 +1,100 @@ +// @flow + +import React from 'react'; +import { TabBarIOS } from 'react-native'; +import { connect } from 'react-redux'; + +import AbstractPagedList from './AbstractPagedList'; +import styles from './styles'; + +/** + * An iOS specific component to render a paged list. + * + * @extends PagedList + */ +class PagedList extends AbstractPagedList { + + /** + * Initializes a new {@code PagedList} instance. + * + * @inheritdoc + */ + constructor(props) { + super(props); + + // Bind event handlers so they are only bound once per instance. + this._onTabSelected = this._onTabSelected.bind(this); + } + + _onTabSelected: number => Function; + + /** + * Constructs a callback to update the selected tab when the bottom bar icon + * is pressed. + * + * @param {number} tabIndex - The selected tab. + * @private + * @returns {Function} + */ + _onTabSelected(tabIndex) { + return () => super._selectPage(tabIndex); + } + + _renderPage: (Object, number, boolean) => React$Node + + /** + * Renders a single page of the page list. + * + * @private + * @param {Object} page - The page to render. + * @param {number} index - The index of the rendered page. + * @param {boolean} disabled - Renders the page disabled. + * @returns {React$Node} + */ + _renderPage(page, index, disabled) { + const { pageIndex } = this.state; + + return page.component + ? + { + React.createElement( + page.component, + { + disabled + }) + } + + : null; + } + + /** + * Renders the paged list if multiple pages are to be rendered. This is the + * platform dependent part of the component. + * + * @param {boolean} disabled - True if the rendered lists should be + * disabled. + * @returns {ReactElement} + */ + _renderPagedList(disabled) { + const { pages } = this.props; + + return ( + + { + pages.map((page, index) => this._renderPage( + page, index, disabled + )) + } + + ); + } +} + +export default connect()(PagedList); diff --git a/react/features/base/react/components/native/index.js b/react/features/base/react/components/native/index.js index 20cb0ab24..3041a8073 100644 --- a/react/features/base/react/components/native/index.js +++ b/react/features/base/react/components/native/index.js @@ -3,6 +3,7 @@ export { default as Header } from './Header'; export { default as NavigateSectionList } from './NavigateSectionList'; export { default as Link } from './Link'; export { default as LoadingIndicator } from './LoadingIndicator'; +export { default as PagedList } from './PagedList'; export { default as Pressable } from './Pressable'; export { default as SideBar } from './SideBar'; export { default as Text } from './Text'; diff --git a/react/features/base/react/components/native/styles.js b/react/features/base/react/components/native/styles.js index 935553544..2faf75c3f 100644 --- a/react/features/base/react/components/native/styles.js +++ b/react/features/base/react/components/native/styles.js @@ -71,6 +71,77 @@ const HEADER_STYLES = { } }; +/** + * Style classes of the PagedList-based components. + */ +const PAGED_LIST_STYLES = { + + /** + * Style of the page indicator (Android). + */ + pageIndicator: { + alignItems: 'center', + flex: 1, + flexDirection: 'column', + justifyContent: 'center' + }, + + /** + * Additional style for the active indicator icon (Android). + */ + pageIndicatorActive: { + color: ColorPalette.white + }, + + /** + * Container for the page indicators (Android). + */ + pageIndicatorContainer: { + alignItems: 'stretch', + backgroundColor: ColorPalette.blue, + flexDirection: 'row', + height: 56, + justifyContent: 'center' + }, + + /** + * Icon of the page indicator (Android). + */ + pageIndicatorIcon: { + color: ColorPalette.blueHighlight, + fontSize: 24 + }, + + /** + * Label of the page indicator (Android). + */ + pageIndicatorText: { + color: ColorPalette.blueHighlight + }, + + /** + * Top level style of the paged list. + */ + pagedList: { + flex: 1 + }, + + /** + * The paged list container View. + */ + pagedListContainer: { + flex: 1, + flexDirection: 'column' + }, + + /** + * Disabled style for the container. + */ + pagedListContainerDisabled: { + opacity: 0.2 + } +}; + const SECTION_LIST_STYLES = { /** * The style of the actual avatar. @@ -248,6 +319,7 @@ const SIDEBAR_STYLES = { */ export default createStyleSheet({ ...HEADER_STYLES, + ...PAGED_LIST_STYLES, ...SECTION_LIST_STYLES, ...SIDEBAR_STYLES }); diff --git a/react/features/welcome/components/AbstractPagedList.web.js b/react/features/welcome/components/AbstractPagedList.web.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/welcome/components/PagedList.android.js b/react/features/welcome/components/PagedList.android.js deleted file mode 100644 index f6a83d9a5..000000000 --- a/react/features/welcome/components/PagedList.android.js +++ /dev/null @@ -1,173 +0,0 @@ -// @flow - -import React from 'react'; -import { Text, TouchableOpacity, View, ViewPagerAndroid } from 'react-native'; -import { connect } from 'react-redux'; - -import { Icon } from '../../base/font-icons'; -import { MeetingList } from '../../calendar-sync'; -import { RecentList } from '../../recent-list'; - -import AbstractPagedList, { DEFAULT_PAGE } from './AbstractPagedList'; -import styles from './styles'; - -/** - * A platform specific component to render a paged or tabbed list/view. - * - * @extends PagedList - */ -class PagedList extends AbstractPagedList { - /** - * A reference to the viewpager. - */ - _viewPager: Object; - - /** - * Initializes a new {@code PagedList} instance. - * - * @inheritdoc - */ - constructor(props) { - super(props); - - // Bind event handlers so they are only bound once per instance. - this._getIndicatorStyle = this._getIndicatorStyle.bind(this); - this._onPageSelected = this._onPageSelected.bind(this); - this._onSelectPage = this._onSelectPage.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); - }; - } - - /** - * Renders the entire paged list if calendar is enabled. - * - * @param {boolean} disabled - True if the rendered lists should be - * disabled. - * @returns {ReactElement} - */ - _renderPagedList(disabled) { - return ( - - - - - - - - - - - - - - - History - - - - - - - - Calendar - - - - - - ); - } - - _setViewPager: Object => void; - - /** - * Sets the {@link ViewPagerAndroid} instance. - * - * @param {ViewPagerAndroid} viewPager - The {@code ViewPagerAndroid} - * instance. - * @private - * @returns {void} - */ - _setViewPager(viewPager) { - this._viewPager = viewPager; - } -} - -export default connect()(PagedList); diff --git a/react/features/welcome/components/PagedList.ios.js b/react/features/welcome/components/PagedList.ios.js deleted file mode 100644 index e13af3707..000000000 --- a/react/features/welcome/components/PagedList.ios.js +++ /dev/null @@ -1,81 +0,0 @@ -// @flow - -import React from 'react'; -import { TabBarIOS } from 'react-native'; -import { connect } from 'react-redux'; - -import { translate } from '../../base/i18n'; -import { MeetingList } from '../../calendar-sync'; -import { RecentList } from '../../recent-list'; - -import AbstractPagedList from './AbstractPagedList'; -import styles from './styles'; - -const CALENDAR_ICON = require('../../../../images/calendar.png'); - -/** - * A platform specific component to render a paged or tabbed list/view. - * - * @extends PagedList - */ -class PagedList extends AbstractPagedList { - - /** - * Initializes a new {@code PagedList} instance. - * - * @inheritdoc - */ - constructor(props) { - super(props); - - // Bind event handlers so they are only bound once per instance. - 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. - * - * @param {boolean} disabled - True if the rendered lists should be - * disabled. - * @returns {ReactElement} - */ - _renderPagedList(disabled) { - const { pageIndex } = this.state; - const { t } = this.props; - - return ( - - - - - - - - - ); - } -} - -export default translate(connect()(PagedList)); diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js index 3fd78fcd6..1ee589837 100644 --- a/react/features/welcome/components/WelcomePage.native.js +++ b/react/features/welcome/components/WelcomePage.native.js @@ -26,12 +26,12 @@ import { SettingsView } from '../../settings'; import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage'; import { setSideBarVisible } from '../actions'; import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay'; -import PagedList from './PagedList'; import styles, { PLACEHOLDER_TEXT_COLOR, SWITCH_THUMB_COLOR, SWITCH_UNDER_COLOR } from './styles'; +import WelcomePageLists from './WelcomePageLists'; import WelcomePageSideBar from './WelcomePageSideBar'; /** @@ -139,7 +139,7 @@ class WelcomePage extends AbstractWelcomePage { } - + diff --git a/react/features/welcome/components/WelcomePageLists.js b/react/features/welcome/components/WelcomePageLists.js new file mode 100644 index 000000000..2722e22a3 --- /dev/null +++ b/react/features/welcome/components/WelcomePageLists.js @@ -0,0 +1,88 @@ +// @flow + +import React, { Component } from 'react'; +import { Platform } from 'react-native'; + +import { translate } from '../../base/i18n'; +import { PagedList } from '../../base/react'; +import { MeetingList } from '../../calendar-sync'; +import { RecentList } from '../../recent-list'; + +type Props = { + + /** + * Renders the lists disabled. + */ + disabled: boolean, + + /** + * The i18n translate function. + */ + t: Function +}; + +/** + * Icon to be used for the calendar page on iOS. + */ +const IOS_CALENDAR_ICON = require('../../../../images/calendar.png'); + +/** + * Icon to be used for the recent list page on iOS. + */ +const IOS_RECENT_LIST_ICON = require('../../../../images/history.png'); + +/** + * Implements the lists displayed on the mobile welcome screen. + */ +class WelcomePageLists extends Component { + /** + * The pages to be rendered. + * Note: The component field may be undefined if a feature (such as + * Calendar) is disabled, and that means that the page must not be rendered. + */ + pages: Array<{ + component: Object, + icon: string | number, + title: string + }> + + /** + * Component contructor. + * + * @inheritdoc + */ + constructor(props) { + super(props); + + const { t } = props; + const isAndroid = Platform.OS === 'android'; + + this.pages = [ { + component: RecentList, + icon: isAndroid ? 'restore' : IOS_RECENT_LIST_ICON, + title: t('welcomepage.recentList') + }, { + component: MeetingList, + icon: isAndroid ? 'event_note' : IOS_CALENDAR_ICON, + title: t('welcomepage.calendar') + } ]; + } + + /** + * Implements React Component's render. + * + * @inheritdoc + */ + render() { + const { disabled } = this.props; + + return ( + + ); + } +} + +export default translate(WelcomePageLists); diff --git a/react/features/welcome/components/styles.js b/react/features/welcome/components/styles.js index 72daa9214..92335af97 100644 --- a/react/features/welcome/components/styles.js +++ b/react/features/welcome/components/styles.js @@ -161,53 +161,6 @@ export default createStyleSheet({ flexDirection: 'column' }, - pageIndicator: { - alignItems: 'center', - flex: 1, - flexDirection: 'column', - justifyContent: 'center' - }, - - pageIndicatorContainer: { - alignItems: 'stretch', - backgroundColor: ColorPalette.blue, - flexDirection: 'row', - height: 56, - justifyContent: 'center' - }, - - pageIndicatorIcon: { - color: ColorPalette.blueHighlight, - fontSize: 24 - }, - - pageIndicatorText: { - color: ColorPalette.blueHighlight - }, - - pageIndicatorTextActive: { - color: ColorPalette.white - }, - - /** - * Top level style of the paged list. - */ - pagedList: { - flex: 1 - }, - - pagedListContainer: { - flex: 1, - flexDirection: 'column' - }, - - /** - * Disabled style for the container. - */ - pagedListContainerDisabled: { - opacity: 0.2 - }, - /** * Container for room name input box and 'join' button. */