You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SettingsView.js 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // @flow
  2. import React from 'react';
  3. import {
  4. Alert,
  5. Modal,
  6. SafeAreaView,
  7. ScrollView,
  8. Switch,
  9. TextInput,
  10. View
  11. } from 'react-native';
  12. import { connect } from 'react-redux';
  13. import { translate } from '../../../base/i18n';
  14. import { BackButton, Header } from '../../../base/react';
  15. import {
  16. AbstractSettingsView,
  17. _mapStateToProps
  18. } from '../AbstractSettingsView';
  19. import { setSettingsViewVisible } from '../../actions';
  20. import FormRow from './FormRow';
  21. import FormSectionHeader from './FormSectionHeader';
  22. import { normalizeUserInputURL } from '../../functions';
  23. import styles from './styles';
  24. import { HeaderLabel } from '../../../base/react/components/native';
  25. /**
  26. * The native container rendering the app settings page.
  27. *
  28. * @extends AbstractSettingsView
  29. */
  30. class SettingsView extends AbstractSettingsView {
  31. _urlField: Object;
  32. /**
  33. * Initializes a new {@code SettingsView} instance.
  34. *
  35. * @inheritdoc
  36. */
  37. constructor(props) {
  38. super(props);
  39. // Bind event handlers so they are only bound once per instance.
  40. this._onBlurServerURL = this._onBlurServerURL.bind(this);
  41. this._onRequestClose = this._onRequestClose.bind(this);
  42. this._setURLFieldReference = this._setURLFieldReference.bind(this);
  43. this._showURLAlert = this._showURLAlert.bind(this);
  44. }
  45. /**
  46. * Implements React's {@link Component#render()}, renders the settings page.
  47. *
  48. * @inheritdoc
  49. * @returns {ReactElement}
  50. */
  51. render() {
  52. return (
  53. <Modal
  54. animationType = 'slide'
  55. onRequestClose = { this._onRequestClose }
  56. presentationStyle = 'fullScreen'
  57. supportedOrientations = { [
  58. 'landscape',
  59. 'portrait'
  60. ] }
  61. visible = { this.props._visible }>
  62. <View style = { Header.pageStyle }>
  63. { this._renderHeader() }
  64. { this._renderBody() }
  65. </View>
  66. </Modal>
  67. );
  68. }
  69. _onBlurServerURL: () => void;
  70. /**
  71. * Handler the server URL lose focus event. Here we validate the server URL
  72. * and update it to the normalized version, or show an error if incorrect.
  73. *
  74. * @private
  75. * @returns {void}
  76. */
  77. _onBlurServerURL() {
  78. this._processServerURL(false /* hideOnSuccess */);
  79. }
  80. _onChangeDisplayName: (string) => void;
  81. _onChangeEmail: (string) => void;
  82. _onChangeServerURL: (string) => void;
  83. _onRequestClose: () => void;
  84. /**
  85. * Handles the back button. Also invokes normalizeUserInputURL to validate
  86. * the URL entered by the user.
  87. *
  88. * @returns {void}
  89. */
  90. _onRequestClose() {
  91. this._processServerURL(true /* hideOnSuccess */);
  92. }
  93. _onStartAudioMutedChange: (boolean) => void;
  94. _onStartVideoMutedChange: (boolean) => void;
  95. /**
  96. * Processes the server URL. It normalizes it and an error alert is
  97. * displayed in case it's incorrect.
  98. *
  99. * @param {boolean} hideOnSuccess - True if the dialog should be hidden if
  100. * normalization / validation succeeds, false otherwise.
  101. * @private
  102. * @returns {void}
  103. */
  104. _processServerURL(hideOnSuccess: boolean) {
  105. const { serverURL } = this.props._settings;
  106. const normalizedURL = normalizeUserInputURL(serverURL);
  107. if (normalizedURL === null) {
  108. this._showURLAlert();
  109. } else {
  110. this._onChangeServerURL(normalizedURL);
  111. if (hideOnSuccess) {
  112. this.props.dispatch(setSettingsViewVisible(false));
  113. }
  114. }
  115. }
  116. /**
  117. * Renders the body (under the header) of {@code SettingsView}.
  118. *
  119. * @private
  120. * @returns {React$Element}
  121. */
  122. _renderBody() {
  123. const { _settings } = this.props;
  124. return (
  125. <SafeAreaView style = { styles.settingsForm }>
  126. <ScrollView>
  127. <FormSectionHeader
  128. label = 'settingsView.profileSection' />
  129. <FormRow
  130. fieldSeparator = { true }
  131. label = 'settingsView.displayName'>
  132. <TextInput
  133. autoCorrect = { false }
  134. onChangeText = { this._onChangeDisplayName }
  135. placeholder = 'John Doe'
  136. value = { _settings.displayName } />
  137. </FormRow>
  138. <FormRow label = 'settingsView.email'>
  139. <TextInput
  140. autoCapitalize = 'none'
  141. autoCorrect = { false }
  142. keyboardType = { 'email-address' }
  143. onChangeText = { this._onChangeEmail }
  144. placeholder = 'email@example.com'
  145. value = { _settings.email } />
  146. </FormRow>
  147. <FormSectionHeader
  148. label = 'settingsView.conferenceSection' />
  149. <FormRow
  150. fieldSeparator = { true }
  151. label = 'settingsView.serverURL'>
  152. <TextInput
  153. autoCapitalize = 'none'
  154. autoCorrect = { false }
  155. onBlur = { this._onBlurServerURL }
  156. onChangeText = { this._onChangeServerURL }
  157. placeholder = { this.props._serverURL }
  158. value = { _settings.serverURL } />
  159. </FormRow>
  160. <FormRow
  161. fieldSeparator = { true }
  162. label = 'settingsView.startWithAudioMuted'>
  163. <Switch
  164. onValueChange = { this._onStartAudioMutedChange }
  165. value = { _settings.startWithAudioMuted } />
  166. </FormRow>
  167. <FormRow label = 'settingsView.startWithVideoMuted'>
  168. <Switch
  169. onValueChange = { this._onStartVideoMutedChange }
  170. value = { _settings.startWithVideoMuted } />
  171. </FormRow>
  172. </ScrollView>
  173. </SafeAreaView>
  174. );
  175. }
  176. /**
  177. * Renders the header of {@code SettingsView}.
  178. *
  179. * @private
  180. * @returns {React$Element}
  181. */
  182. _renderHeader() {
  183. return (
  184. <Header>
  185. <BackButton onPress = { this._onRequestClose } />
  186. <HeaderLabel labelKey = 'settingsView.header' />
  187. </Header>
  188. );
  189. }
  190. _setURLFieldReference: (React$ElementRef<*> | null) => void;
  191. /**
  192. * Stores a reference to the URL field for later use.
  193. *
  194. * @param {Object} component - The field component.
  195. * @protected
  196. * @returns {void}
  197. */
  198. _setURLFieldReference(component) {
  199. this._urlField = component;
  200. }
  201. _showURLAlert: () => void;
  202. /**
  203. * Shows an alert telling the user that the URL he/she entered was invalid.
  204. *
  205. * @returns {void}
  206. */
  207. _showURLAlert() {
  208. const { t } = this.props;
  209. Alert.alert(
  210. t('settingsView.alertTitle'),
  211. t('settingsView.alertURLText'),
  212. [
  213. {
  214. onPress: () => this._urlField.focus(),
  215. text: t('settingsView.alertOk')
  216. }
  217. ]
  218. );
  219. }
  220. }
  221. export default translate(connect(_mapStateToProps)(SettingsView));