2018-02-26 16:14:46 +00:00
|
|
|
// @flow
|
|
|
|
|
|
|
|
import React from 'react';
|
2019-04-02 08:03:01 +00:00
|
|
|
import { Alert, NativeModules, SafeAreaView, ScrollView, Switch, Text, TextInput, View } from 'react-native';
|
2018-02-26 16:14:46 +00:00
|
|
|
|
2019-03-05 17:41:39 +00:00
|
|
|
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
2018-02-26 16:14:46 +00:00
|
|
|
import { translate } from '../../../base/i18n';
|
2019-05-07 14:46:52 +00:00
|
|
|
import { HeaderWithNavigation, Modal } from '../../../base/react';
|
2019-03-21 16:38:29 +00:00
|
|
|
import { connect } from '../../../base/redux';
|
2018-02-26 16:14:46 +00:00
|
|
|
|
|
|
|
import {
|
|
|
|
AbstractSettingsView,
|
2019-03-05 17:41:39 +00:00
|
|
|
_mapStateToProps as _abstractMapStateToProps,
|
|
|
|
type Props as AbstractProps
|
2018-02-26 16:14:46 +00:00
|
|
|
} from '../AbstractSettingsView';
|
|
|
|
import { setSettingsViewVisible } from '../../actions';
|
|
|
|
import FormRow from './FormRow';
|
|
|
|
import FormSectionHeader from './FormSectionHeader';
|
|
|
|
import { normalizeUserInputURL } from '../../functions';
|
|
|
|
import styles from './styles';
|
|
|
|
|
2019-04-02 08:03:01 +00:00
|
|
|
/**
|
|
|
|
* Application information module.
|
|
|
|
*/
|
|
|
|
const { AppInfo } = NativeModules;
|
|
|
|
|
2019-03-05 17:41:39 +00:00
|
|
|
type Props = AbstractProps & {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Color schemed style of the header component.
|
|
|
|
*/
|
|
|
|
_headerStyles: Object
|
|
|
|
}
|
|
|
|
|
2018-02-26 16:14:46 +00:00
|
|
|
/**
|
|
|
|
* The native container rendering the app settings page.
|
|
|
|
*
|
|
|
|
* @extends AbstractSettingsView
|
|
|
|
*/
|
2019-03-05 17:41:39 +00:00
|
|
|
class SettingsView extends AbstractSettingsView<Props> {
|
2018-02-26 16:14:46 +00:00
|
|
|
_urlField: Object;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes a new {@code SettingsView} instance.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
// Bind event handlers so they are only bound once per instance.
|
|
|
|
this._onBlurServerURL = this._onBlurServerURL.bind(this);
|
|
|
|
this._onRequestClose = this._onRequestClose.bind(this);
|
|
|
|
this._setURLFieldReference = this._setURLFieldReference.bind(this);
|
|
|
|
this._showURLAlert = this._showURLAlert.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements React's {@link Component#render()}, renders the settings page.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
* @returns {ReactElement}
|
|
|
|
*/
|
|
|
|
render() {
|
|
|
|
return (
|
|
|
|
<Modal
|
|
|
|
onRequestClose = { this._onRequestClose }
|
2019-01-09 16:58:29 +00:00
|
|
|
presentationStyle = 'overFullScreen'
|
2018-02-26 16:14:46 +00:00
|
|
|
visible = { this.props._visible }>
|
2019-03-05 17:41:39 +00:00
|
|
|
<View style = { this.props._headerStyles.page }>
|
2018-02-26 16:14:46 +00:00
|
|
|
{ this._renderHeader() }
|
|
|
|
{ this._renderBody() }
|
|
|
|
</View>
|
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
_onBlurServerURL: () => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler the server URL lose focus event. Here we validate the server URL
|
|
|
|
* and update it to the normalized version, or show an error if incorrect.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_onBlurServerURL() {
|
|
|
|
this._processServerURL(false /* hideOnSuccess */);
|
|
|
|
}
|
|
|
|
|
|
|
|
_onChangeDisplayName: (string) => void;
|
|
|
|
|
|
|
|
_onChangeEmail: (string) => void;
|
|
|
|
|
|
|
|
_onChangeServerURL: (string) => void;
|
|
|
|
|
|
|
|
_onRequestClose: () => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles the back button. Also invokes normalizeUserInputURL to validate
|
|
|
|
* the URL entered by the user.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_onRequestClose() {
|
|
|
|
this._processServerURL(true /* hideOnSuccess */);
|
|
|
|
}
|
|
|
|
|
|
|
|
_onStartAudioMutedChange: (boolean) => void;
|
|
|
|
|
|
|
|
_onStartVideoMutedChange: (boolean) => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Processes the server URL. It normalizes it and an error alert is
|
|
|
|
* displayed in case it's incorrect.
|
|
|
|
*
|
|
|
|
* @param {boolean} hideOnSuccess - True if the dialog should be hidden if
|
|
|
|
* normalization / validation succeeds, false otherwise.
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_processServerURL(hideOnSuccess: boolean) {
|
2018-04-12 19:58:20 +00:00
|
|
|
const { serverURL } = this.props._settings;
|
2018-02-26 16:14:46 +00:00
|
|
|
const normalizedURL = normalizeUserInputURL(serverURL);
|
|
|
|
|
|
|
|
if (normalizedURL === null) {
|
|
|
|
this._showURLAlert();
|
|
|
|
} else {
|
|
|
|
this._onChangeServerURL(normalizedURL);
|
|
|
|
if (hideOnSuccess) {
|
|
|
|
this.props.dispatch(setSettingsViewVisible(false));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Renders the body (under the header) of {@code SettingsView}.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {React$Element}
|
|
|
|
*/
|
|
|
|
_renderBody() {
|
2018-04-12 19:58:20 +00:00
|
|
|
const { _settings } = this.props;
|
2018-02-26 16:14:46 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<SafeAreaView style = { styles.settingsForm }>
|
|
|
|
<ScrollView>
|
|
|
|
<FormSectionHeader
|
2018-05-29 12:51:55 +00:00
|
|
|
label = 'settingsView.profileSection' />
|
2018-02-26 16:14:46 +00:00
|
|
|
<FormRow
|
|
|
|
fieldSeparator = { true }
|
2018-05-29 12:51:55 +00:00
|
|
|
label = 'settingsView.displayName'>
|
2018-02-26 16:14:46 +00:00
|
|
|
<TextInput
|
2018-03-27 08:09:27 +00:00
|
|
|
autoCorrect = { false }
|
2018-02-26 16:14:46 +00:00
|
|
|
onChangeText = { this._onChangeDisplayName }
|
|
|
|
placeholder = 'John Doe'
|
2018-04-12 19:58:20 +00:00
|
|
|
value = { _settings.displayName } />
|
2018-02-26 16:14:46 +00:00
|
|
|
</FormRow>
|
2018-05-29 12:51:55 +00:00
|
|
|
<FormRow label = 'settingsView.email'>
|
2018-02-26 16:14:46 +00:00
|
|
|
<TextInput
|
2018-03-26 12:12:13 +00:00
|
|
|
autoCapitalize = 'none'
|
2018-03-27 08:09:27 +00:00
|
|
|
autoCorrect = { false }
|
2018-02-26 16:14:46 +00:00
|
|
|
keyboardType = { 'email-address' }
|
|
|
|
onChangeText = { this._onChangeEmail }
|
|
|
|
placeholder = 'email@example.com'
|
2018-04-12 19:58:20 +00:00
|
|
|
value = { _settings.email } />
|
2018-02-26 16:14:46 +00:00
|
|
|
</FormRow>
|
|
|
|
<FormSectionHeader
|
2018-05-29 12:51:55 +00:00
|
|
|
label = 'settingsView.conferenceSection' />
|
2018-02-26 16:14:46 +00:00
|
|
|
<FormRow
|
|
|
|
fieldSeparator = { true }
|
2018-05-29 12:51:55 +00:00
|
|
|
label = 'settingsView.serverURL'>
|
2018-02-26 16:14:46 +00:00
|
|
|
<TextInput
|
|
|
|
autoCapitalize = 'none'
|
2018-03-27 08:09:27 +00:00
|
|
|
autoCorrect = { false }
|
2018-02-26 16:14:46 +00:00
|
|
|
onBlur = { this._onBlurServerURL }
|
|
|
|
onChangeText = { this._onChangeServerURL }
|
|
|
|
placeholder = { this.props._serverURL }
|
2018-04-12 19:58:20 +00:00
|
|
|
value = { _settings.serverURL } />
|
2018-02-26 16:14:46 +00:00
|
|
|
</FormRow>
|
|
|
|
<FormRow
|
|
|
|
fieldSeparator = { true }
|
2018-05-29 12:51:55 +00:00
|
|
|
label = 'settingsView.startWithAudioMuted'>
|
2018-02-26 16:14:46 +00:00
|
|
|
<Switch
|
|
|
|
onValueChange = { this._onStartAudioMutedChange }
|
2018-04-12 19:58:20 +00:00
|
|
|
value = { _settings.startWithAudioMuted } />
|
2018-02-26 16:14:46 +00:00
|
|
|
</FormRow>
|
2018-05-29 12:51:55 +00:00
|
|
|
<FormRow label = 'settingsView.startWithVideoMuted'>
|
2018-02-26 16:14:46 +00:00
|
|
|
<Switch
|
|
|
|
onValueChange = { this._onStartVideoMutedChange }
|
2018-04-12 19:58:20 +00:00
|
|
|
value = { _settings.startWithVideoMuted } />
|
2018-02-26 16:14:46 +00:00
|
|
|
</FormRow>
|
2019-04-02 08:03:01 +00:00
|
|
|
<FormSectionHeader
|
|
|
|
label = 'settingsView.buildInfoSection' />
|
|
|
|
<FormRow
|
|
|
|
fieldSeparator = { true }
|
|
|
|
label = 'settingsView.version'>
|
|
|
|
<Text>
|
|
|
|
{ `${AppInfo.version} build ${AppInfo.buildNumber}` }
|
|
|
|
</Text>
|
|
|
|
</FormRow>
|
2018-02-26 16:14:46 +00:00
|
|
|
</ScrollView>
|
|
|
|
</SafeAreaView>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Renders the header of {@code SettingsView}.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {React$Element}
|
|
|
|
*/
|
|
|
|
_renderHeader() {
|
|
|
|
return (
|
2019-05-07 14:46:52 +00:00
|
|
|
<HeaderWithNavigation
|
|
|
|
headerLabelKey = 'settingsView.header'
|
|
|
|
onPressBack = { this._onRequestClose } />
|
2018-02-26 16:14:46 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
_setURLFieldReference: (React$ElementRef<*> | null) => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stores a reference to the URL field for later use.
|
|
|
|
*
|
|
|
|
* @param {Object} component - The field component.
|
|
|
|
* @protected
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_setURLFieldReference(component) {
|
|
|
|
this._urlField = component;
|
|
|
|
}
|
|
|
|
|
|
|
|
_showURLAlert: () => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows an alert telling the user that the URL he/she entered was invalid.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_showURLAlert() {
|
|
|
|
const { t } = this.props;
|
|
|
|
|
|
|
|
Alert.alert(
|
|
|
|
t('settingsView.alertTitle'),
|
|
|
|
t('settingsView.alertURLText'),
|
|
|
|
[
|
|
|
|
{
|
|
|
|
onPress: () => this._urlField.focus(),
|
|
|
|
text: t('settingsView.alertOk')
|
|
|
|
}
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-05 17:41:39 +00:00
|
|
|
/**
|
|
|
|
* Maps part of the Redux state to the props of this component.
|
|
|
|
*
|
|
|
|
* @param {Object} state - The Redux state.
|
|
|
|
* @returns {{
|
|
|
|
* _headerStyles: Object
|
|
|
|
* }}
|
|
|
|
*/
|
|
|
|
function _mapStateToProps(state) {
|
|
|
|
return {
|
|
|
|
..._abstractMapStateToProps(state),
|
|
|
|
_headerStyles: ColorSchemeRegistry.get(state, 'Header')
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-02-26 16:14:46 +00:00
|
|
|
export default translate(connect(_mapStateToProps)(SettingsView));
|