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

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