Fix settings screen layout on iOS and add soft back button

This commit is contained in:
zbettenbuk
2018-01-08 12:00:31 +01:00
committed by Lyubo Marinov
parent b7f950f5f7
commit 410dc132e1
22 changed files with 714 additions and 315 deletions

View File

@@ -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.

View File

@@ -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<Props, State> {
export class AbstractAppSettings extends Component<Props> {
/**
* Initializes a new {@code AbstractAppSettings} instance.
*
@@ -76,38 +54,14 @@ export class AbstractAppSettings extends Component<Props, State> {
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<Props, State> {
* @returns {void}
*/
_onChangeDisplayName(text) {
this.setState({
this._updateProfile({
displayName: text
});
}
@@ -133,12 +87,12 @@ export class AbstractAppSettings extends Component<Props, State> {
* @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<Props, State> {
* @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<Props, State> {
_onRequestClose: () => void;
/**
* Handles the hardware back button.
* Handles the back button.
*
* @returns {void}
*/
@@ -164,61 +118,6 @@ export class AbstractAppSettings extends Component<Props, State> {
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<Props, State> {
* @returns {void}
*/
_onStartAudioMutedChange(newValue) {
this.setState({
startWithAudioMuted: newValue
});
this._updateProfile({
startWithAudioMuted: newValue
});
@@ -250,10 +145,6 @@ export class AbstractAppSettings extends Component<Props, State> {
* @returns {void}
*/
_onStartVideoMutedChange(newValue) {
this.setState({
startWithVideoMuted: newValue
});
this._updateProfile({
startWithVideoMuted: newValue
});
@@ -274,25 +165,6 @@ export class AbstractAppSettings extends Component<Props, State> {
...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<Props, State> {
* @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
};
}

View File

@@ -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 (
<Modal
animationType = 'slide'
onRequestClose = { this._onRequestClose }
presentationStyle = 'fullScreen'
style = { styles.modal }
presentationStyle = 'overFullScreen'
supportedOrientations = { [
'landscape',
'portrait'
] }
visible = { this.props._visible }>
<View style = { styles.headerContainer } >
<View
style = { [
styles.headerContainer,
this._getSafetyPadding()
] } >
<BackButton
onPress = { this._onRequestClose }
style = { styles.settingsBackButton } />
<Text style = { [ styles.text, styles.headerTitle ] } >
{ t('profileModal.header') }
</Text>
</View>
<View style = { styles.settingsContainer } >
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.serverURL' >
<TextInput
autoCapitalize = 'none'
onChangeText = { this._onChangeServerName }
onEndEditing = { this._onSaveServerName }
placeholder = 'https://jitsi.example.com'
value = { this.state.serverURL } />
</FormRow>
<ScrollView style = { styles.settingsContainer } >
<FormSectionHeader
i18nLabel = 'profileModal.profileSection' />
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.displayName' >
<TextInput
onChangeText = { this._onChangeDisplayName }
onEndEditing = { this._onSaveDisplayName }
placeholder = 'John Doe'
value = { this.state.displayName } />
value = { _profile.displayName } />
</FormRow>
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.email' >
<TextInput
keyboardType = { 'email-address' }
onChangeText = { this._onChangeEmail }
onEndEditing = { this._onSaveEmail }
placeholder = 'email@example.com'
value = { this.state.email } />
value = { _profile.email } />
</FormRow>
<FormSectionHeader
i18nLabel = 'profileModal.conferenceSection' />
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.serverURL' >
<TextInput
autoCapitalize = 'none'
onChangeText = { this._onChangeServerURL }
placeholder = { this.props._serverURL }
value = { _profile.serverURL } />
</FormRow>
<FormRow
fieldSeparator = { true }
@@ -81,7 +111,9 @@ class AppSettings extends AbstractAppSettings {
onValueChange = {
this._onStartAudioMutedChange
}
value = { this.state.startWithAudioMuted } />
value = {
_profile.startWithAudioMuted
} />
</FormRow>
<FormRow
i18nLabel = 'profileModal.startWithVideoMuted' >
@@ -89,12 +121,33 @@ class AppSettings extends AbstractAppSettings {
onValueChange = {
this._onStartVideoMutedChange
}
value = { this.state.startWithVideoMuted } />
value = {
_profile.startWithVideoMuted
} />
</FormRow>
</View>
</ScrollView>
</Modal>
);
}
/**
* 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));

View File

@@ -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<Props> {
/**
* Implements React's {@link Component#render()}, renders the button.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<TouchableOpacity
accessibilityLabel = { 'Back' }
onPress = { this.props.onPress }>
<Icon
name = { BACK_ICON }
style = { [
styles.backIcon,
this.props.style
] } />
</TouchableOpacity>
);
}
}

View File

@@ -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<Props> {
/**
* Initializes a new {@code FormRow} instance.
*
@@ -118,6 +125,7 @@ class FormRow extends Component<Props> {
/**
* Assembles the row style array based on the row's props.
* For padding, see comment in functions.js.
*
* @private
* @returns {Array<Object>}
@@ -131,8 +139,33 @@ class FormRow extends Component<Props> {
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));

View File

@@ -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<Props> {
/**
* 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 (
<View
style = { [
styles.formSectionTitle,
this.props.style,
this._getSafetyMargin()
] } >
<Text>
{ t(this.props.i18nLabel) }
</Text>
</View>
);
}
_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));

View File

@@ -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'
}
});

View File

@@ -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;
}

View File

@@ -1,5 +1,4 @@
export * from './actions';
export * from './components';
export * from './functions';
import './reducer';

View File

@@ -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);
}

View File

@@ -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;
}
/**

View File

@@ -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,

View File

@@ -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));
}

View File

@@ -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))
);
}