diff --git a/css/_font.scss b/css/_font.scss index 41e5ed1c4..5641b64cf 100644 --- a/css/_font.scss +++ b/css/_font.scss @@ -24,7 +24,12 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } - +.icon-arrow_back:before { + content: "\e5c4"; +} +.icon-navigate_before:before { + content: "\e408"; +} .icon-event_note:before { content: "\e616"; } diff --git a/fonts/jitsi.eot b/fonts/jitsi.eot index 7621636a8..475d60124 100755 Binary files a/fonts/jitsi.eot and b/fonts/jitsi.eot differ diff --git a/fonts/jitsi.svg b/fonts/jitsi.svg index 15552e68d..d8891378c 100755 --- a/fonts/jitsi.svg +++ b/fonts/jitsi.svg @@ -11,7 +11,9 @@ + + diff --git a/fonts/jitsi.ttf b/fonts/jitsi.ttf index e67633eab..f0e20c48e 100755 Binary files a/fonts/jitsi.ttf and b/fonts/jitsi.ttf differ diff --git a/fonts/jitsi.woff b/fonts/jitsi.woff index f110bbb59..217d7fb19 100755 Binary files a/fonts/jitsi.woff and b/fonts/jitsi.woff differ diff --git a/fonts/selection.json b/fonts/selection.json index ce40a7d5b..350abea45 100755 --- a/fonts/selection.json +++ b/fonts/selection.json @@ -1,6 +1,60 @@ { "IcoMoonType": "selection", "icons": [ + { + "icon": { + "paths": [ + "M854 470v84h-520l238 240-60 60-342-342 342-342 60 60-238 240h520z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "arrow_back" + ], + "defaultCode": 58820, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "arrow_back", + "id": 45, + "order": 924, + "prevSize": 24, + "code": 58820, + "name": "arrow_back" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 45 + }, + { + "icon": { + "paths": [ + "M658 316l-196 196 196 196-60 60-256-256 256-256z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "navigate_before" + ], + "defaultCode": 58376, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "chevron_left, navigate_before", + "id": 152, + "order": 923, + "prevSize": 24, + "code": 58376, + "name": "navigate_before" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 152 + }, { "icon": { "paths": [ @@ -24,9 +78,9 @@ "code": 59403, "name": "public" }, - "setIdx": 0, - "setId": 2, - "iconIdx": 605 + "setIdx": 1, + "setId": 1, + "iconIdx": 0 }, { "icon": { @@ -53,7 +107,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 0 + "iconIdx": 1 }, { "icon": { @@ -80,7 +134,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 1 + "iconIdx": 2 }, { "icon": { @@ -107,7 +161,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 2 + "iconIdx": 3 }, { "icon": { @@ -134,7 +188,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 3 + "iconIdx": 4 }, { "icon": { @@ -161,7 +215,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 4 + "iconIdx": 5 }, { "icon": { @@ -188,7 +242,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 5 + "iconIdx": 6 }, { "icon": { @@ -217,7 +271,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 6 + "iconIdx": 7 }, { "icon": { @@ -244,7 +298,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 7 + "iconIdx": 8 }, { "icon": { @@ -271,7 +325,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 8 + "iconIdx": 9 }, { "icon": { @@ -300,7 +354,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 9 + "iconIdx": 10 }, { "icon": { @@ -329,7 +383,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 10 + "iconIdx": 11 }, { "icon": { @@ -358,7 +412,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 11 + "iconIdx": 12 }, { "icon": { @@ -387,7 +441,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 12 + "iconIdx": 13 }, { "icon": { @@ -416,7 +470,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 13 + "iconIdx": 14 }, { "icon": { @@ -442,7 +496,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 14 + "iconIdx": 15 }, { "icon": { @@ -468,7 +522,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 15 + "iconIdx": 16 }, { "icon": { @@ -494,7 +548,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 16 + "iconIdx": 17 }, { "icon": { @@ -520,7 +574,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 17 + "iconIdx": 18 }, { "icon": { @@ -546,7 +600,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 18 + "iconIdx": 19 }, { "icon": { @@ -572,7 +626,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 19 + "iconIdx": 20 }, { "icon": { @@ -598,7 +652,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 20 + "iconIdx": 21 }, { "icon": { @@ -616,7 +670,7 @@ "attrs": [], "properties": { "id": 10, - "order": 900, + "order": 922, "ligatures": "expand_less", "prevSize": 32, "code": 59679, @@ -624,7 +678,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 21 + "iconIdx": 22 }, { "icon": { @@ -650,7 +704,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 22 + "iconIdx": 23 }, { "icon": { @@ -676,7 +730,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 23 + "iconIdx": 24 }, { "icon": { @@ -702,7 +756,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 24 + "iconIdx": 25 }, { "icon": { @@ -728,7 +782,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 25 + "iconIdx": 26 }, { "icon": { @@ -754,7 +808,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 26 + "iconIdx": 27 }, { "icon": { @@ -780,7 +834,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 27 + "iconIdx": 28 }, { "icon": { @@ -806,7 +860,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 28 + "iconIdx": 29 }, { "icon": { @@ -832,7 +886,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 29 + "iconIdx": 30 }, { "icon": { @@ -858,7 +912,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 30 + "iconIdx": 31 }, { "icon": { @@ -884,7 +938,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 31 + "iconIdx": 32 }, { "icon": { @@ -910,7 +964,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 32 + "iconIdx": 33 }, { "icon": { @@ -936,7 +990,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 33 + "iconIdx": 34 }, { "icon": { @@ -962,7 +1016,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 34 + "iconIdx": 35 }, { "icon": { @@ -988,7 +1042,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 35 + "iconIdx": 36 }, { "icon": { @@ -1014,7 +1068,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 36 + "iconIdx": 37 }, { "icon": { @@ -1040,7 +1094,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 37 + "iconIdx": 38 }, { "icon": { @@ -1066,7 +1120,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 38 + "iconIdx": 39 }, { "icon": { @@ -1092,7 +1146,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 39 + "iconIdx": 40 }, { "icon": { @@ -1118,7 +1172,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 40 + "iconIdx": 41 }, { "icon": { @@ -1144,7 +1198,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 41 + "iconIdx": 42 }, { "icon": { @@ -1170,7 +1224,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 42 + "iconIdx": 43 }, { "icon": { @@ -1199,7 +1253,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 43 + "iconIdx": 44 }, { "icon": { @@ -1229,7 +1283,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 44 + "iconIdx": 45 }, { "icon": { @@ -1259,7 +1313,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 45 + "iconIdx": 46 }, { "icon": { @@ -1285,7 +1339,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 46 + "iconIdx": 47 }, { "icon": { @@ -1311,7 +1365,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 47 + "iconIdx": 48 }, { "icon": { @@ -1337,7 +1391,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 48 + "iconIdx": 49 } ], "height": 1024, diff --git a/lang/main.json b/lang/main.json index b7002dcc3..a3c0bda4d 100644 --- a/lang/main.json +++ b/lang/main.json @@ -539,9 +539,11 @@ "tooltip": "Get access info about the meeting" }, "profileModal": { + "conferenceSection": "Conference", "displayName": "Display name", "email": "Email", "header": "Settings", + "profileSection": "Profile", "serverURL": "Server URL", "startWithAudioMuted": "Start with audio muted", "startWithVideoMuted": "Start with video muted" diff --git a/react/features/app-settings/actions.js b/react/features/app-settings/actions.js index 359b96cf1..6b92cd21f 100644 --- a/react/features/app-settings/actions.js +++ b/react/features/app-settings/actions.js @@ -1,9 +1,6 @@ /* @flow */ -import { - HIDE_APP_SETTINGS, - SHOW_APP_SETTINGS -} from './actionTypes'; +import { HIDE_APP_SETTINGS, SHOW_APP_SETTINGS } from './actionTypes'; /** * Redux-signals the request to open the app settings modal. diff --git a/react/features/app-settings/components/AbstractAppSettings.js b/react/features/app-settings/components/AbstractAppSettings.js index 934c404ba..0827656bf 100644 --- a/react/features/app-settings/components/AbstractAppSettings.js +++ b/react/features/app-settings/components/AbstractAppSettings.js @@ -10,6 +10,16 @@ import { getProfile, updateProfile } from '../../base/profile'; */ type Props = { + /** + * The current aspect ratio of the screen. + */ + _aspectRatio: Symbol, + + /** + * The default URL for when there is no custom URL set in the profile. + */ + _serverURL: string, + /** * The current profile object. */ @@ -26,45 +36,13 @@ type Props = { dispatch: Dispatch<*> }; -/** - * The type of the React {@code Component} state of {@link AbstractAppSettings}. - */ -type State = { - - /** - * The display name field value on the settings screen. - */ - displayName: string, - - /** - * The email field value on the settings screen. - */ - email: string, - - /** - * The server url field value on the settings screen. - */ - serverURL: string, - - /** - * The start audio muted switch value on the settings screen. - */ - startWithAudioMuted: boolean, - - /** - * The start video muted switch value on the settings screen. - */ - startWithVideoMuted: boolean -} - /** * Base (abstract) class for container component rendering * the app settings page. * * @abstract */ -export class AbstractAppSettings extends Component { - +export class AbstractAppSettings extends Component { /** * Initializes a new {@code AbstractAppSettings} instance. * @@ -76,38 +54,14 @@ export class AbstractAppSettings extends Component { this._onChangeDisplayName = this._onChangeDisplayName.bind(this); this._onChangeEmail = this._onChangeEmail.bind(this); - this._onChangeServerName = this._onChangeServerName.bind(this); + this._onChangeServerURL = this._onChangeServerURL.bind(this); this._onRequestClose = this._onRequestClose.bind(this); - this._onSaveDisplayName = this._onSaveDisplayName.bind(this); - this._onSaveEmail = this._onSaveEmail.bind(this); - this._onSaveServerName = this._onSaveServerName.bind(this); this._onStartAudioMutedChange = this._onStartAudioMutedChange.bind(this); this._onStartVideoMutedChange = this._onStartVideoMutedChange.bind(this); } - /** - * Invokes React's {@link Component#componentWillReceiveProps()} to make - * sure we have the state Initialized on component mount. - * - * @inheritdoc - */ - componentWillMount() { - this._updateStateFromProps(this.props); - } - - /** - * Implements React's {@link Component#componentWillReceiveProps()}. Invoked - * before this mounted component receives new props. - * - * @inheritdoc - * @param {Props} nextProps - New props component will receive. - */ - componentWillReceiveProps(nextProps: Props) { - this._updateStateFromProps(nextProps); - } - _onChangeDisplayName: (string) => void; /** @@ -118,7 +72,7 @@ export class AbstractAppSettings extends Component { * @returns {void} */ _onChangeDisplayName(text) { - this.setState({ + this._updateProfile({ displayName: text }); } @@ -133,12 +87,12 @@ export class AbstractAppSettings extends Component { * @returns {void} */ _onChangeEmail(text) { - this.setState({ + this._updateProfile({ email: text }); } - _onChangeServerName: (string) => void; + _onChangeServerURL: (string) => void; /** * Handles the server name field value change. @@ -147,8 +101,8 @@ export class AbstractAppSettings extends Component { * @param {string} text - The server URL typed in the server field. * @returns {void} */ - _onChangeServerName(text) { - this.setState({ + _onChangeServerURL(text) { + this._updateProfile({ serverURL: text }); } @@ -156,7 +110,7 @@ export class AbstractAppSettings extends Component { _onRequestClose: () => void; /** - * Handles the hardware back button. + * Handles the back button. * * @returns {void} */ @@ -164,61 +118,6 @@ export class AbstractAppSettings extends Component { this.props.dispatch(hideAppSettings()); } - _onSaveDisplayName: () => void; - - /** - * Handles the display name field onEndEditing. - * - * @protected - * @returns {void} - */ - _onSaveDisplayName() { - this._updateProfile({ - displayName: this.state.displayName - }); - } - - _onSaveEmail: () => void; - - /** - * Handles the email field onEndEditing. - * - * @protected - * @returns {void} - */ - _onSaveEmail() { - this._updateProfile({ - email: this.state.email - }); - } - - _onSaveServerName: () => void; - - /** - * Handles the server name field onEndEditing. - * - * @protected - * @returns {void} - */ - _onSaveServerName() { - let serverURL; - - if (this.state.serverURL.endsWith('/')) { - serverURL = this.state.serverURL.substr( - 0, this.state.serverURL.length - 1 - ); - } else { - serverURL = this.state.serverURL; - } - - this._updateProfile({ - defaultURL: serverURL - }); - this.setState({ - serverURL - }); - } - _onStartAudioMutedChange: (boolean) => void; /** @@ -230,10 +129,6 @@ export class AbstractAppSettings extends Component { * @returns {void} */ _onStartAudioMutedChange(newValue) { - this.setState({ - startWithAudioMuted: newValue - }); - this._updateProfile({ startWithAudioMuted: newValue }); @@ -250,10 +145,6 @@ export class AbstractAppSettings extends Component { * @returns {void} */ _onStartVideoMutedChange(newValue) { - this.setState({ - startWithVideoMuted: newValue - }); - this._updateProfile({ startWithVideoMuted: newValue }); @@ -274,25 +165,6 @@ export class AbstractAppSettings extends Component { ...updateObject })); } - - _updateStateFromProps: (Object) => void; - - /** - * Updates the component state when (new) props are received. - * - * @private - * @param {Object} props - The component's props. - * @returns {void} - */ - _updateStateFromProps(props) { - this.setState({ - displayName: props._profile.displayName, - email: props._profile.email, - serverURL: props._profile.defaultURL, - startWithAudioMuted: props._profile.startWithAudioMuted, - startWithVideoMuted: props._profile.startWithVideoMuted - }); - } } /** @@ -304,8 +176,13 @@ export class AbstractAppSettings extends Component { * @returns {Object} */ export function _mapStateToProps(state: Object) { + const _serverURL = state['features/app'].app._getDefaultURL(); + const _profile = getProfile(state); + return { - _profile: getProfile(state), + _aspectRatio: state['features/base/aspect-ratio'].aspectRatio, + _serverURL, + _profile, _visible: state['features/app-settings'].visible }; } diff --git a/react/features/app-settings/components/AppSettings.native.js b/react/features/app-settings/components/AppSettings.native.js index afd5c0118..206330a89 100644 --- a/react/features/app-settings/components/AppSettings.native.js +++ b/react/features/app-settings/components/AppSettings.native.js @@ -1,6 +1,7 @@ import React from 'react'; import { Modal, + ScrollView, Switch, Text, TextInput, @@ -11,10 +12,16 @@ import { _mapStateToProps, AbstractAppSettings } from './AbstractAppSettings'; +import BackButton from './BackButton'; import FormRow from './FormRow'; -import styles from './styles'; +import FormSectionHeader from './FormSectionHeader'; +import styles, { HEADER_PADDING } from './styles'; +import { getSafetyOffset } from '../functions.native'; + +import { ASPECT_RATIO_NARROW } from '../../base/aspect-ratio'; import { translate } from '../../base/i18n'; +import { isIPad } from '../../base/react'; /** * The native container rendering the app settings page. @@ -22,6 +29,16 @@ import { translate } from '../../base/i18n'; * @extends AbstractAppSettings */ class AppSettings extends AbstractAppSettings { + /** + * Instantiates a new {@code AppSettings} instance. + * + * @inheritdoc + */ + constructor(props) { + super(props); + + this._getSafetyPadding = this._getSafetyPadding.bind(this); + } /** * Implements React's {@link Component#render()}, renders the settings page. @@ -30,49 +47,62 @@ class AppSettings extends AbstractAppSettings { * @returns {ReactElement} */ render() { - const { t } = this.props; + const { _profile, t } = this.props; + + // FIXME: presentationStyle is added to workaround + // orientation issue on iOS return ( - + + { t('profileModal.header') } - - - - + + + value = { _profile.displayName } /> + value = { _profile.email } /> + + + + + value = { + _profile.startWithAudioMuted + } /> @@ -89,12 +121,33 @@ class AppSettings extends AbstractAppSettings { onValueChange = { this._onStartVideoMutedChange } - value = { this.state.startWithVideoMuted } /> + value = { + _profile.startWithVideoMuted + } /> - + ); } + + /** + * Calculates header safety padding for mobile devices. + * See comment in functions.js. + * + * @private + * @returns {Object} + */ + _getSafetyPadding() { + if (isIPad() || this.props._aspectRatio === ASPECT_RATIO_NARROW) { + const safeOffset = Math.max(getSafetyOffset(), HEADER_PADDING); + + return { + paddingTop: safeOffset + }; + } + + return undefined; + } } export default translate(connect(_mapStateToProps)(AppSettings)); diff --git a/react/features/app-settings/components/BackButton.native.js b/react/features/app-settings/components/BackButton.native.js new file mode 100644 index 000000000..98673b8f5 --- /dev/null +++ b/react/features/app-settings/components/BackButton.native.js @@ -0,0 +1,56 @@ +// @flow + +import React, { Component } from 'react'; +import { TouchableOpacity } from 'react-native'; + +import styles from './styles'; + +import { Icon } from '../../base/font-icons'; +import { Platform } from '../../base/react'; + +/** +* The icon glyph to be used on a specific platform. +*/ +const BACK_ICON = Platform.OS === 'android' ? 'arrow_back' : 'navigate_before'; + +/** +* The type of the React {@code Component} props of {@link BackButton} +*/ +type Props = { + + /** + * The action to be performed when the button is pressed. + */ + onPress: Function, + + /** + * An external style object passed to the component. + */ + style: Object +}; + +/** + * A component rendering a back button that looks native on both platforms. + */ +export default class BackButton extends Component { + /** + * Implements React's {@link Component#render()}, renders the button. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return ( + + + + ); + } +} diff --git a/react/features/app-settings/components/FormRow.native.js b/react/features/app-settings/components/FormRow.native.js index 0c4150792..46f0b50a3 100644 --- a/react/features/app-settings/components/FormRow.native.js +++ b/react/features/app-settings/components/FormRow.native.js @@ -6,8 +6,11 @@ import { View } from 'react-native'; import { connect } from 'react-redux'; -import styles, { ANDROID_UNDERLINE_COLOR } from './styles'; +import styles, { ANDROID_UNDERLINE_COLOR, CONTAINER_PADDING } from './styles'; +import { getSafetyOffset } from '../functions'; + +import { ASPECT_RATIO_WIDE } from '../../base/aspect-ratio'; import { translate } from '../../base/i18n'; /** @@ -15,6 +18,11 @@ import { translate } from '../../base/i18n'; */ type Props = { + /** + * The current aspect ratio of the screen. + */ + _aspectRatio: Symbol, + /** */ children: Object, @@ -40,7 +48,6 @@ type Props = { * on a form. The component should have exactly one child component. */ class FormRow extends Component { - /** * Initializes a new {@code FormRow} instance. * @@ -118,6 +125,7 @@ class FormRow extends Component { /** * Assembles the row style array based on the row's props. + * For padding, see comment in functions.js. * * @private * @returns {Array} @@ -131,8 +139,33 @@ class FormRow extends Component { rowStyle.push(styles.fieldSeparator); } + if (this.props._aspectRatio === ASPECT_RATIO_WIDE) { + const safeOffset = Math.max( + getSafetyOffset() - CONTAINER_PADDING, 0 + ); + + rowStyle.push({ + marginLeft: safeOffset, + marginRight: safeOffset + }); + } + return rowStyle; } } -export default translate(connect()(FormRow)); +/** + * Maps (parts of) the redux state to the React {@code Component} props of + * {@code FormRow}. + * + * @param {Object} state - The redux state. + * @protected + * @returns {Object} + */ +export function _mapStateToProps(state: Object) { + return { + _aspectRatio: state['features/base/aspect-ratio'].aspectRatio + }; +} + +export default translate(connect(_mapStateToProps)(FormRow)); diff --git a/react/features/app-settings/components/FormSectionHeader.native.js b/react/features/app-settings/components/FormSectionHeader.native.js new file mode 100644 index 000000000..48da3163c --- /dev/null +++ b/react/features/app-settings/components/FormSectionHeader.native.js @@ -0,0 +1,119 @@ +/* @flow */ + +import React, { Component } from 'react'; +import { Text, View } from 'react-native'; +import { connect } from 'react-redux'; + +import styles, { CONTAINER_PADDING } from './styles'; + +import { getSafetyOffset } from '../functions'; + +import { ASPECT_RATIO_WIDE } from '../../base/aspect-ratio'; +import { translate } from '../../base/i18n'; + +/** +* The type of the React {@code Component} props of {@link FormSectionHeader} +*/ +type Props = { + + /** + * The current aspect ratio of the screen. + */ + _aspectRatio: Symbol, + + /** + * The i18n key of the text label of the section. + */ + i18nLabel: string, + + /** + * An external style object passed to the component. + */ + style: Object, + + /** + * Invoked to obtain translated strings. + */ + t: Function +} + +/** + * Implements a React {@code Component} which renders + * a section header on a form. This calculates the available safe view as well. + */ +class FormSectionHeader extends Component { + /** + * Initializes a new {@code FormSectionHeader} instance. + * + * @param {Object} props - Component properties. + */ + constructor(props) { + super(props); + + this._getSafetyMargin = this._getSafetyMargin.bind(this); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @override + * @returns {ReactElement} + */ + render() { + const { t } = this.props; + + return ( + + + { t(this.props.i18nLabel) } + + + ); + } + + _getSafetyMargin: () => Object; + + /** + * Calculates the safety margin for this header. + * See comment in functions.js. + * + * @private + * @returns {Object} + */ + _getSafetyMargin() { + if (this.props._aspectRatio === ASPECT_RATIO_WIDE) { + const safeOffset = Math.max( + getSafetyOffset() - CONTAINER_PADDING, 0 + ); + + return { + marginLeft: safeOffset, + marginRight: safeOffset + }; + } + + return undefined; + } +} + +/** + * Maps (parts of) the redux state to the React {@code Component} props of + * {@code FormSectionHeader}. + * + * @param {Object} state - The redux state. + * @protected + * @returns {Object} + */ +export function _mapStateToProps(state: Object) { + return { + _aspectRatio: state['features/base/aspect-ratio'].aspectRatio + }; +} + +export default translate(connect(_mapStateToProps)(FormSectionHeader)); diff --git a/react/features/app-settings/components/styles.js b/react/features/app-settings/components/styles.js index a688b2c79..5845f7a47 100644 --- a/react/features/app-settings/components/styles.js +++ b/react/features/app-settings/components/styles.js @@ -1,25 +1,45 @@ +import { Platform } from 'react-native'; import { BoxModel, ColorPalette, createStyleSheet } from '../../base/styles'; -const LABEL_TAB = 300; - export const ANDROID_UNDERLINE_COLOR = 'transparent'; +export const CONTAINER_PADDING = 2 * BoxModel.padding; +export const HEADER_COLOR = ColorPalette.blue; +export const HEADER_PADDING = BoxModel.padding; +const TEXT_SIZE = 17; /** - * The styles of the React {@code Components} of the feature welcome including - * {@code WelcomePage} and {@code BlankPage}. + * The styles of the React {@code Components} of the feature + * {@code AppSettings}. */ export default createStyleSheet({ + /** + *The platform specific back button style. + */ + backIcon: { + alignSelf: 'center', + ...Platform.select({ + ios: { + alignSelf: 'center', + fontSize: 30 + }, + android: { + fontSize: 24, + padding: 8 + } + }) + }, + /** * Standardized style for a field container {@code View}. */ fieldContainer: { - flexDirection: 'row', alignItems: 'center', + flexDirection: 'row', minHeight: 65 }, @@ -27,16 +47,16 @@ export default createStyleSheet({ * Standard container for a {@code View} containing a field label. */ fieldLabelContainer: { - flexDirection: 'row', alignItems: 'center', - width: LABEL_TAB + flexDirection: 'row' }, /** * Field container style for all but last row {@code View}. */ fieldSeparator: { - borderBottomWidth: 1 + borderBottomWidth: 1, + borderColor: 'rgba(0, 0, 0, 0.1)' }, /** @@ -44,26 +64,48 @@ export default createStyleSheet({ * field values (the actual field). */ fieldValueContainer: { + alignItems: 'center', flex: 1, - justifyContent: 'flex-end', flexDirection: 'row', - alignItems: 'center' + justifyContent: 'flex-end' + }, + + formSectionTitle: { + backgroundColor: 'rgba(0, 0, 0, 0.1)', + marginTop: 5, + padding: 5 }, /** * Page header {@code View}. */ headerContainer: { - backgroundColor: ColorPalette.blue, - flexDirection: 'row', alignItems: 'center', - padding: 2 * BoxModel.margin + backgroundColor: HEADER_COLOR, + flexDirection: 'row', + justifyContent: 'flex-start', + padding: HEADER_PADDING }, /** * The title {@code Text} of the header. */ headerTitle: { + color: ColorPalette.white, + fontSize: 24 + }, + + /** + * Style of the scrollview to be able to scroll the content. + */ + scrollView: { + flex: 1 + }, + + /** + * The back button style on the settings screen. + */ + settingsBackButton: { color: ColorPalette.white, fontSize: 25 }, @@ -76,7 +118,8 @@ export default createStyleSheet({ flex: 1, flexDirection: 'column', margin: 0, - padding: 2 * BoxModel.padding + padding: CONTAINER_PADDING, + paddingTop: 0 }, /** @@ -84,15 +127,15 @@ export default createStyleSheet({ */ text: { color: ColorPalette.black, - fontSize: 20 + fontSize: TEXT_SIZE }, /** * Standard text input field style. */ textInputField: { - fontSize: 20, flex: 1, + fontSize: TEXT_SIZE, textAlign: 'right' } }); diff --git a/react/features/app-settings/functions.native.js b/react/features/app-settings/functions.native.js new file mode 100644 index 000000000..a9fed673e --- /dev/null +++ b/react/features/app-settings/functions.native.js @@ -0,0 +1,28 @@ +// @flow + +import { isIPhoneX, Platform } from '../base/react'; + +const IPHONE_OFFSET = 20; +const IPHONEX_OFFSET = 44; + +/** +* Determines the offset to be used for the device. +* This uses a custom implementation to minimize empty area around screen, +* especially on iPhone X. +* +* @returns {number} +*/ +export function getSafetyOffset() { + if (Platform.OS === 'android') { + /* Android doesn't need offset, except the Essential phone. Should be + * addressed later with a generic solution. + */ + return 0; + } + + if (isIPhoneX()) { + return IPHONEX_OFFSET; + } + + return IPHONE_OFFSET; +} diff --git a/react/features/app-settings/functions.js b/react/features/app-settings/functions.web.js similarity index 100% rename from react/features/app-settings/functions.js rename to react/features/app-settings/functions.web.js diff --git a/react/features/app-settings/index.js b/react/features/app-settings/index.js index 61438458c..582e1f9dd 100644 --- a/react/features/app-settings/index.js +++ b/react/features/app-settings/index.js @@ -1,5 +1,4 @@ export * from './actions'; export * from './components'; -export * from './functions'; import './reducer'; diff --git a/react/features/app-settings/middleware.js b/react/features/app-settings/middleware.js new file mode 100644 index 000000000..e50d7c231 --- /dev/null +++ b/react/features/app-settings/middleware.js @@ -0,0 +1,37 @@ +/* @flow */ + +import { hideAppSettings } from './actions'; + +import { SET_ROOM } from '../base/conference'; +import { MiddlewareRegistry } from '../base/redux'; + +/** + * The Redux middleware to trigger settings screen show or hide + * when necessary. + * + * @param {Store} store - The Redux store. + * @returns {Function} + */ +MiddlewareRegistry.register(store => next => action => { + switch (action.type) { + case SET_ROOM: + return _closeAppSettings(store, next, action); + } + + return next(action); +}); + +/** + * Hides the settings screen. + * + * @param {Store} store - The redux store. + * @param {Dispatch} next - The redux dispatch function. + * @param {Action} action - The redux action. + * @private + * @returns {Object} The new state. + */ +function _closeAppSettings(store, next, action) { + store.dispatch(hideAppSettings()); + + return next(action); +} diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js index a7fe55999..72ea1d8a8 100644 --- a/react/features/app/components/AbstractApp.js +++ b/react/features/app/components/AbstractApp.js @@ -179,6 +179,8 @@ export class AbstractApp extends Component { * @returns {void} */ componentWillReceiveProps(nextProps) { + const currentProps = this.props; + this.init.then(() => { // The consumer of this AbstractApp did not provide a redux store. if (typeof nextProps.store === 'undefined' @@ -189,7 +191,7 @@ export class AbstractApp extends Component { // its own internal redux store. If the consumer did not // provide a redux store before, then this instance is // using its own internal redux store already. - && typeof this.props.store !== 'undefined') { + && typeof currentProps.store !== 'undefined') { this.setState({ store: this._maybeCreateStore(nextProps) }); @@ -199,11 +201,11 @@ export class AbstractApp extends Component { let { url } = nextProps; url = toURLString(url); - if (toURLString(this.props.url) !== url + if (toURLString(currentProps.url) !== url // XXX Refer to the implementation of loadURLObject: in // ios/sdk/src/JitsiMeetView.m for further information. - || this.props.timestamp !== nextProps.timestamp) { + || currentProps.timestamp !== nextProps.timestamp) { this._openURL(url || this._getDefaultURL()); } }); @@ -373,11 +375,11 @@ export class AbstractApp extends Component { } } - const profileDefaultURL = getProfile( + const profileServerURL = getProfile( this._getStore().getState() - ).defaultURL; + ).serverURL; - return this.props.defaultURL || profileDefaultURL || DEFAULT_URL; + return this.props.defaultURL || profileServerURL || DEFAULT_URL; } /** diff --git a/react/features/base/font-icons/jitsi.json b/react/features/base/font-icons/jitsi.json index ce40a7d5b..350abea45 100755 --- a/react/features/base/font-icons/jitsi.json +++ b/react/features/base/font-icons/jitsi.json @@ -1,6 +1,60 @@ { "IcoMoonType": "selection", "icons": [ + { + "icon": { + "paths": [ + "M854 470v84h-520l238 240-60 60-342-342 342-342 60 60-238 240h520z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "arrow_back" + ], + "defaultCode": 58820, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "arrow_back", + "id": 45, + "order": 924, + "prevSize": 24, + "code": 58820, + "name": "arrow_back" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 45 + }, + { + "icon": { + "paths": [ + "M658 316l-196 196 196 196-60 60-256-256 256-256z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "navigate_before" + ], + "defaultCode": 58376, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "chevron_left, navigate_before", + "id": 152, + "order": 923, + "prevSize": 24, + "code": 58376, + "name": "navigate_before" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 152 + }, { "icon": { "paths": [ @@ -24,9 +78,9 @@ "code": 59403, "name": "public" }, - "setIdx": 0, - "setId": 2, - "iconIdx": 605 + "setIdx": 1, + "setId": 1, + "iconIdx": 0 }, { "icon": { @@ -53,7 +107,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 0 + "iconIdx": 1 }, { "icon": { @@ -80,7 +134,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 1 + "iconIdx": 2 }, { "icon": { @@ -107,7 +161,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 2 + "iconIdx": 3 }, { "icon": { @@ -134,7 +188,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 3 + "iconIdx": 4 }, { "icon": { @@ -161,7 +215,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 4 + "iconIdx": 5 }, { "icon": { @@ -188,7 +242,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 5 + "iconIdx": 6 }, { "icon": { @@ -217,7 +271,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 6 + "iconIdx": 7 }, { "icon": { @@ -244,7 +298,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 7 + "iconIdx": 8 }, { "icon": { @@ -271,7 +325,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 8 + "iconIdx": 9 }, { "icon": { @@ -300,7 +354,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 9 + "iconIdx": 10 }, { "icon": { @@ -329,7 +383,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 10 + "iconIdx": 11 }, { "icon": { @@ -358,7 +412,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 11 + "iconIdx": 12 }, { "icon": { @@ -387,7 +441,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 12 + "iconIdx": 13 }, { "icon": { @@ -416,7 +470,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 13 + "iconIdx": 14 }, { "icon": { @@ -442,7 +496,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 14 + "iconIdx": 15 }, { "icon": { @@ -468,7 +522,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 15 + "iconIdx": 16 }, { "icon": { @@ -494,7 +548,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 16 + "iconIdx": 17 }, { "icon": { @@ -520,7 +574,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 17 + "iconIdx": 18 }, { "icon": { @@ -546,7 +600,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 18 + "iconIdx": 19 }, { "icon": { @@ -572,7 +626,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 19 + "iconIdx": 20 }, { "icon": { @@ -598,7 +652,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 20 + "iconIdx": 21 }, { "icon": { @@ -616,7 +670,7 @@ "attrs": [], "properties": { "id": 10, - "order": 900, + "order": 922, "ligatures": "expand_less", "prevSize": 32, "code": 59679, @@ -624,7 +678,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 21 + "iconIdx": 22 }, { "icon": { @@ -650,7 +704,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 22 + "iconIdx": 23 }, { "icon": { @@ -676,7 +730,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 23 + "iconIdx": 24 }, { "icon": { @@ -702,7 +756,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 24 + "iconIdx": 25 }, { "icon": { @@ -728,7 +782,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 25 + "iconIdx": 26 }, { "icon": { @@ -754,7 +808,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 26 + "iconIdx": 27 }, { "icon": { @@ -780,7 +834,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 27 + "iconIdx": 28 }, { "icon": { @@ -806,7 +860,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 28 + "iconIdx": 29 }, { "icon": { @@ -832,7 +886,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 29 + "iconIdx": 30 }, { "icon": { @@ -858,7 +912,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 30 + "iconIdx": 31 }, { "icon": { @@ -884,7 +938,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 31 + "iconIdx": 32 }, { "icon": { @@ -910,7 +964,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 32 + "iconIdx": 33 }, { "icon": { @@ -936,7 +990,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 33 + "iconIdx": 34 }, { "icon": { @@ -962,7 +1016,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 34 + "iconIdx": 35 }, { "icon": { @@ -988,7 +1042,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 35 + "iconIdx": 36 }, { "icon": { @@ -1014,7 +1068,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 36 + "iconIdx": 37 }, { "icon": { @@ -1040,7 +1094,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 37 + "iconIdx": 38 }, { "icon": { @@ -1066,7 +1120,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 38 + "iconIdx": 39 }, { "icon": { @@ -1092,7 +1146,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 39 + "iconIdx": 40 }, { "icon": { @@ -1118,7 +1172,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 40 + "iconIdx": 41 }, { "icon": { @@ -1144,7 +1198,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 41 + "iconIdx": 42 }, { "icon": { @@ -1170,7 +1224,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 42 + "iconIdx": 43 }, { "icon": { @@ -1199,7 +1253,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 43 + "iconIdx": 44 }, { "icon": { @@ -1229,7 +1283,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 44 + "iconIdx": 45 }, { "icon": { @@ -1259,7 +1313,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 45 + "iconIdx": 46 }, { "icon": { @@ -1285,7 +1339,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 46 + "iconIdx": 47 }, { "icon": { @@ -1311,7 +1365,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 47 + "iconIdx": 48 }, { "icon": { @@ -1337,7 +1391,7 @@ }, "setIdx": 1, "setId": 1, - "iconIdx": 48 + "iconIdx": 49 } ], "height": 1024, diff --git a/react/features/base/profile/middleware.js b/react/features/base/profile/middleware.js index 659268a0b..a7503e4d3 100644 --- a/react/features/base/profile/middleware.js +++ b/react/features/base/profile/middleware.js @@ -2,7 +2,10 @@ import { PROFILE_UPDATED } from './actionTypes'; import MiddlewareRegistry from '../redux/MiddlewareRegistry'; -import { participantUpdated } from '../participants'; +import { + getLocalParticipant, + participantUpdated +} from '../participants'; import { getProfile } from '../profile'; import { toState } from '../redux'; @@ -31,13 +34,11 @@ MiddlewareRegistry.register(store => next => action => { * @returns {void} */ function _updateLocalParticipant(store) { + const localParticipant = getLocalParticipant(toState(store)); const profile = getProfile(toState(store)); - const newLocalParticipant = { - email: profile.email, - local: true, - name: profile.displayName - }; + localParticipant.email = profile.email; + localParticipant.name = profile.displayName; - store.dispatch(participantUpdated(newLocalParticipant)); + store.dispatch(participantUpdated(localParticipant)); } diff --git a/react/features/base/react/functions.native.js b/react/features/base/react/functions.native.js new file mode 100644 index 000000000..52aed8cac --- /dev/null +++ b/react/features/base/react/functions.native.js @@ -0,0 +1,37 @@ +// @flow + +import { Dimensions } from 'react-native'; +import Platform from './Platform'; + +const IPHONEX_HEIGHT = 812; +const IPHONEX_WIDTH = 375; + +/** +* Determines if the device is an iPad or not. +* +* @returns {boolean} +*/ +export function isIPad() { + const { height, width } = Dimensions.get('window'); + + return Platform.OS === 'ios' && ( + Math.max(height, width) + / Math.min(height, width)) < 1.6; +} + +/** +* Determines if it's an iPhone X or not. +* +* @returns {boolean} +*/ +export function isIPhoneX() { + const { height, width } = Dimensions.get('window'); + + return ( + Platform.OS === 'ios' + && ((height === IPHONEX_HEIGHT + && width === IPHONEX_WIDTH) + || (height === IPHONEX_WIDTH + && width === IPHONEX_HEIGHT)) + ); +}