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 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. // @flow
  2. import React from 'react';
  3. import { Alert, NativeModules, SafeAreaView, ScrollView, Switch, Text, TextInput, View } from 'react-native';
  4. import { ColorSchemeRegistry } from '../../../base/color-scheme';
  5. import { translate } from '../../../base/i18n';
  6. import { HeaderWithNavigation, Modal } from '../../../base/react';
  7. import { connect } from '../../../base/redux';
  8. import {
  9. AbstractSettingsView,
  10. _mapStateToProps as _abstractMapStateToProps,
  11. type Props as AbstractProps
  12. } from '../AbstractSettingsView';
  13. import { setSettingsViewVisible } from '../../actions';
  14. import FormRow from './FormRow';
  15. import FormSectionHeader from './FormSectionHeader';
  16. import { normalizeUserInputURL } from '../../functions';
  17. import styles from './styles';
  18. /**
  19. * Application information module.
  20. */
  21. const { AppInfo } = NativeModules;
  22. type Props = AbstractProps & {
  23. /**
  24. * Color schemed style of the header component.
  25. */
  26. _headerStyles: Object
  27. }
  28. type State = {
  29. /**
  30. * Whether to show advanced settings or not.
  31. */
  32. showAdvanced: boolean
  33. }
  34. /**
  35. * The native container rendering the app settings page.
  36. *
  37. * @extends AbstractSettingsView
  38. */
  39. class SettingsView extends AbstractSettingsView<Props, State> {
  40. _urlField: Object;
  41. /**
  42. * Initializes a new {@code SettingsView} instance.
  43. *
  44. * @inheritdoc
  45. */
  46. constructor(props) {
  47. super(props);
  48. this.state = {
  49. showAdvanced: false
  50. };
  51. // Bind event handlers so they are only bound once per instance.
  52. this._onBlurServerURL = this._onBlurServerURL.bind(this);
  53. this._onDisableCallIntegration = this._onDisableCallIntegration.bind(this);
  54. this._onDisableP2P = this._onDisableP2P.bind(this);
  55. this._onRequestClose = this._onRequestClose.bind(this);
  56. this._onShowAdvanced = this._onShowAdvanced.bind(this);
  57. this._setURLFieldReference = this._setURLFieldReference.bind(this);
  58. this._showURLAlert = this._showURLAlert.bind(this);
  59. }
  60. /**
  61. * Implements React's {@link Component#render()}, renders the settings page.
  62. *
  63. * @inheritdoc
  64. * @returns {ReactElement}
  65. */
  66. render() {
  67. return (
  68. <Modal
  69. onRequestClose = { this._onRequestClose }
  70. presentationStyle = 'overFullScreen'
  71. visible = { this.props._visible }>
  72. <View style = { this.props._headerStyles.page }>
  73. { this._renderHeader() }
  74. { this._renderBody() }
  75. </View>
  76. </Modal>
  77. );
  78. }
  79. _onBlurServerURL: () => void;
  80. /**
  81. * Handler the server URL lose focus event. Here we validate the server URL
  82. * and update it to the normalized version, or show an error if incorrect.
  83. *
  84. * @private
  85. * @returns {void}
  86. */
  87. _onBlurServerURL() {
  88. this._processServerURL(false /* hideOnSuccess */);
  89. }
  90. _onChangeDisplayName: (string) => void;
  91. _onChangeEmail: (string) => void;
  92. _onChangeServerURL: (string) => void;
  93. _onDisableCallIntegration: (boolean) => void;
  94. /**
  95. * Handles the disable call integration change event.
  96. *
  97. * @param {boolean} newValue - The new value
  98. * option.
  99. * @private
  100. * @returns {void}
  101. */
  102. _onDisableCallIntegration(newValue) {
  103. this._updateSettings({
  104. disableCallIntegration: newValue
  105. });
  106. }
  107. _onDisableP2P: (boolean) => void;
  108. /**
  109. * Handles the disable P2P change event.
  110. *
  111. * @param {boolean} newValue - The new value
  112. * option.
  113. * @private
  114. * @returns {void}
  115. */
  116. _onDisableP2P(newValue) {
  117. this._updateSettings({
  118. disableP2P: newValue
  119. });
  120. }
  121. _onRequestClose: () => void;
  122. /**
  123. * Handles the back button. Also invokes normalizeUserInputURL to validate
  124. * the URL entered by the user.
  125. *
  126. * @returns {void}
  127. */
  128. _onRequestClose() {
  129. this.setState({ showAdvanced: false });
  130. this._processServerURL(true /* hideOnSuccess */);
  131. }
  132. _onShowAdvanced: () => void;
  133. /**
  134. * Handles the advanced settings button.
  135. *
  136. * @returns {void}
  137. */
  138. _onShowAdvanced() {
  139. this.setState({ showAdvanced: !this.state.showAdvanced });
  140. }
  141. _onStartAudioMutedChange: (boolean) => void;
  142. _onStartVideoMutedChange: (boolean) => void;
  143. /**
  144. * Processes the server URL. It normalizes it and an error alert is
  145. * displayed in case it's incorrect.
  146. *
  147. * @param {boolean} hideOnSuccess - True if the dialog should be hidden if
  148. * normalization / validation succeeds, false otherwise.
  149. * @private
  150. * @returns {void}
  151. */
  152. _processServerURL(hideOnSuccess: boolean) {
  153. const { serverURL } = this.props._settings;
  154. const normalizedURL = normalizeUserInputURL(serverURL);
  155. if (normalizedURL === null) {
  156. this._showURLAlert();
  157. } else {
  158. this._onChangeServerURL(normalizedURL);
  159. if (hideOnSuccess) {
  160. this.props.dispatch(setSettingsViewVisible(false));
  161. }
  162. }
  163. }
  164. /**
  165. * Renders the advanced settings options.
  166. *
  167. * @private
  168. * @returns {React$Element}
  169. */
  170. _renderAdvancedSettings() {
  171. const { _settings } = this.props;
  172. const { showAdvanced } = this.state;
  173. if (!showAdvanced) {
  174. return (
  175. <FormRow
  176. fieldSeparator = { true }
  177. label = 'settingsView.showAdvanced'>
  178. <Switch
  179. onValueChange = { this._onShowAdvanced }
  180. value = { showAdvanced } />
  181. </FormRow>
  182. );
  183. }
  184. return (
  185. <>
  186. <FormRow
  187. fieldSeparator = { true }
  188. label = 'settingsView.disableCallIntegration'>
  189. <Switch
  190. onValueChange = { this._onDisableCallIntegration }
  191. value = { _settings.disableCallIntegration } />
  192. </FormRow>
  193. <FormRow
  194. fieldSeparator = { true }
  195. label = 'settingsView.disableP2P'>
  196. <Switch
  197. onValueChange = { this._onDisableP2P }
  198. value = { _settings.disableP2P } />
  199. </FormRow>
  200. </>
  201. );
  202. }
  203. /**
  204. * Renders the body (under the header) of {@code SettingsView}.
  205. *
  206. * @private
  207. * @returns {React$Element}
  208. */
  209. _renderBody() {
  210. const { _settings } = this.props;
  211. return (
  212. <SafeAreaView style = { styles.settingsForm }>
  213. <ScrollView>
  214. <FormSectionHeader
  215. label = 'settingsView.profileSection' />
  216. <FormRow
  217. fieldSeparator = { true }
  218. label = 'settingsView.displayName'>
  219. <TextInput
  220. autoCorrect = { false }
  221. onChangeText = { this._onChangeDisplayName }
  222. placeholder = 'John Doe'
  223. value = { _settings.displayName } />
  224. </FormRow>
  225. <FormRow label = 'settingsView.email'>
  226. <TextInput
  227. autoCapitalize = 'none'
  228. autoCorrect = { false }
  229. keyboardType = { 'email-address' }
  230. onChangeText = { this._onChangeEmail }
  231. placeholder = 'email@example.com'
  232. value = { _settings.email } />
  233. </FormRow>
  234. <FormSectionHeader
  235. label = 'settingsView.conferenceSection' />
  236. <FormRow
  237. fieldSeparator = { true }
  238. label = 'settingsView.serverURL'>
  239. <TextInput
  240. autoCapitalize = 'none'
  241. autoCorrect = { false }
  242. onBlur = { this._onBlurServerURL }
  243. onChangeText = { this._onChangeServerURL }
  244. placeholder = { this.props._serverURL }
  245. value = { _settings.serverURL } />
  246. </FormRow>
  247. <FormRow
  248. fieldSeparator = { true }
  249. label = 'settingsView.startWithAudioMuted'>
  250. <Switch
  251. onValueChange = { this._onStartAudioMutedChange }
  252. value = { _settings.startWithAudioMuted } />
  253. </FormRow>
  254. <FormRow label = 'settingsView.startWithVideoMuted'>
  255. <Switch
  256. onValueChange = { this._onStartVideoMutedChange }
  257. value = { _settings.startWithVideoMuted } />
  258. </FormRow>
  259. <FormSectionHeader
  260. label = 'settingsView.buildInfoSection' />
  261. <FormRow
  262. label = 'settingsView.version'>
  263. <Text>
  264. { `${AppInfo.version} build ${AppInfo.buildNumber}` }
  265. </Text>
  266. </FormRow>
  267. <FormSectionHeader
  268. label = 'settingsView.advanced' />
  269. { this._renderAdvancedSettings() }
  270. </ScrollView>
  271. </SafeAreaView>
  272. );
  273. }
  274. /**
  275. * Renders the header of {@code SettingsView}.
  276. *
  277. * @private
  278. * @returns {React$Element}
  279. */
  280. _renderHeader() {
  281. return (
  282. <HeaderWithNavigation
  283. headerLabelKey = 'settingsView.header'
  284. onPressBack = { this._onRequestClose } />
  285. );
  286. }
  287. _setURLFieldReference: (React$ElementRef<*> | null) => void;
  288. /**
  289. * Stores a reference to the URL field for later use.
  290. *
  291. * @param {Object} component - The field component.
  292. * @protected
  293. * @returns {void}
  294. */
  295. _setURLFieldReference(component) {
  296. this._urlField = component;
  297. }
  298. _showURLAlert: () => void;
  299. /**
  300. * Shows an alert telling the user that the URL he/she entered was invalid.
  301. *
  302. * @returns {void}
  303. */
  304. _showURLAlert() {
  305. const { t } = this.props;
  306. Alert.alert(
  307. t('settingsView.alertTitle'),
  308. t('settingsView.alertURLText'),
  309. [
  310. {
  311. onPress: () => this._urlField.focus(),
  312. text: t('settingsView.alertOk')
  313. }
  314. ]
  315. );
  316. }
  317. _updateSettings: (Object) => void;
  318. }
  319. /**
  320. * Maps part of the Redux state to the props of this component.
  321. *
  322. * @param {Object} state - The Redux state.
  323. * @returns {{
  324. * _headerStyles: Object
  325. * }}
  326. */
  327. function _mapStateToProps(state) {
  328. return {
  329. ..._abstractMapStateToProps(state),
  330. _headerStyles: ColorSchemeRegistry.get(state, 'Header')
  331. };
  332. }
  333. export default translate(connect(_mapStateToProps)(SettingsView));