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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. // @flow
  2. import React from 'react';
  3. import {
  4. Alert,
  5. NativeModules,
  6. Platform,
  7. ScrollView,
  8. Text
  9. } from 'react-native';
  10. import { Divider, Switch, TextInput, withTheme } from 'react-native-paper';
  11. import { translate } from '../../../../../base/i18n';
  12. import JitsiScreen from '../../../../../base/modal/components/JitsiScreen';
  13. import { connect } from '../../../../../base/redux';
  14. import { renderArrowBackButton }
  15. from '../../../../../mobile/navigation/components/welcome/functions';
  16. import { screen } from '../../../../../mobile/navigation/routes';
  17. import {
  18. AbstractSettingsView,
  19. _mapStateToProps as _abstractMapStateToProps,
  20. type Props as AbstractProps
  21. } from '../../../../../settings/components/AbstractSettingsView';
  22. import { normalizeUserInputURL, isServerURLChangeEnabled } from '../../../../../settings/functions';
  23. import FormRow from './FormRow';
  24. import FormSectionAccordion from './FormSectionAccordion';
  25. import styles, {
  26. DISABLED_TRACK_COLOR,
  27. ENABLED_TRACK_COLOR,
  28. THUMB_COLOR
  29. } from './styles';
  30. /**
  31. * Application information module.
  32. */
  33. const { AppInfo } = NativeModules;
  34. type State = {
  35. /**
  36. * State variable for the disable call integration switch.
  37. */
  38. disableCallIntegration: boolean,
  39. /**
  40. * State variable for the disable p2p switch.
  41. */
  42. disableP2P: boolean,
  43. /**
  44. * State variable for the disable crash reporting switch.
  45. */
  46. disableCrashReporting: boolean,
  47. /**
  48. * State variable for the display name field.
  49. */
  50. displayName: string,
  51. /**
  52. * State variable for the email field.
  53. */
  54. email: string,
  55. /**
  56. * State variable for the server URL field.
  57. */
  58. serverURL: string,
  59. /**
  60. * State variable for the start with audio muted switch.
  61. */
  62. startWithAudioMuted: boolean,
  63. /**
  64. * State variable for the start with video muted switch.
  65. */
  66. startWithVideoMuted: boolean
  67. }
  68. /**
  69. * The type of the React {@code Component} props of
  70. * {@link SettingsView}.
  71. */
  72. type Props = AbstractProps & {
  73. /**
  74. * Flag indicating if URL can be changed by user.
  75. *
  76. * @protected
  77. */
  78. _serverURLChangeEnabled: boolean,
  79. /**
  80. * Default prop for navigating between screen components(React Navigation).
  81. */
  82. navigation: Object,
  83. /**
  84. * Theme used for styles.
  85. */
  86. theme: Object
  87. }
  88. /**
  89. * The native container rendering the app settings page.
  90. *
  91. * @augments AbstractSettingsView
  92. */
  93. class SettingsView extends AbstractSettingsView<Props, State> {
  94. _urlField: Object;
  95. /**
  96. * Initializes a new {@code SettingsView} instance.
  97. *
  98. * @inheritdoc
  99. */
  100. constructor(props) {
  101. super(props);
  102. const {
  103. disableCallIntegration,
  104. disableCrashReporting,
  105. disableP2P,
  106. displayName,
  107. email,
  108. serverURL,
  109. startWithAudioMuted,
  110. startWithVideoMuted
  111. } = props._settings || {};
  112. this.state = {
  113. disableCallIntegration,
  114. disableCrashReporting,
  115. disableP2P,
  116. displayName,
  117. email,
  118. serverURL,
  119. startWithAudioMuted,
  120. startWithVideoMuted
  121. };
  122. // Bind event handlers so they are only bound once per instance.
  123. this._onBlurServerURL = this._onBlurServerURL.bind(this);
  124. this._onClose = this._onClose.bind(this);
  125. this._onDisableCallIntegration = this._onDisableCallIntegration.bind(this);
  126. this._onDisableCrashReporting = this._onDisableCrashReporting.bind(this);
  127. this._onDisableP2P = this._onDisableP2P.bind(this);
  128. this._setURLFieldReference = this._setURLFieldReference.bind(this);
  129. this._showURLAlert = this._showURLAlert.bind(this);
  130. }
  131. /**
  132. * Implements React's {@link Component#componentDidMount()}. Invoked
  133. * immediately after mounting occurs.
  134. *
  135. * @inheritdoc
  136. * @returns {void}
  137. */
  138. componentDidMount() {
  139. const {
  140. navigation,
  141. t
  142. } = this.props;
  143. navigation.setOptions({
  144. headerLeft: () =>
  145. renderArrowBackButton(() =>
  146. navigation.navigate(screen.welcome.main)),
  147. headerTitle: t('settings.screenTitle')
  148. });
  149. }
  150. /**
  151. * Implements React's {@link Component#render()}, renders the settings page.
  152. *
  153. * @inheritdoc
  154. * @returns {ReactElement}
  155. */
  156. render() {
  157. const {
  158. disableCallIntegration,
  159. disableCrashReporting,
  160. disableP2P,
  161. displayName,
  162. email,
  163. serverURL,
  164. startWithAudioMuted,
  165. startWithVideoMuted
  166. } = this.state;
  167. const { palette } = this.props.theme;
  168. const textInputTheme = {
  169. colors: {
  170. background: palette.ui01,
  171. placeholder: palette.text01,
  172. primary: palette.screen01Header,
  173. underlineColor: 'transparent',
  174. text: palette.text01
  175. }
  176. };
  177. return (
  178. <JitsiScreen
  179. style = { styles.settingsViewContainer }>
  180. <ScrollView>
  181. <FormSectionAccordion
  182. accordion = { false }
  183. expandable = { false }
  184. label = 'settingsView.profileSection'>
  185. <TextInput
  186. autoCorrect = { false }
  187. label = { this.props.t('settingsView.displayName') }
  188. mode = 'outlined'
  189. onChangeText = { this._onChangeDisplayName }
  190. placeholder = { this.props.t('settingsView.displayNamePlaceholderText') }
  191. spellCheck = { false }
  192. style = { styles.textInputContainer }
  193. textContentType = { 'name' } // iOS only
  194. theme = { textInputTheme }
  195. value = { displayName } />
  196. <Divider style = { styles.fieldSeparator } />
  197. <TextInput
  198. autoCapitalize = 'none'
  199. autoCorrect = { false }
  200. keyboardType = { 'email-address' }
  201. label = { this.props.t('settingsView.email') }
  202. mode = 'outlined'
  203. onChangeText = { this._onChangeEmail }
  204. placeholder = 'email@example.com'
  205. spellCheck = { false }
  206. style = { styles.textInputContainer }
  207. textContentType = { 'emailAddress' } // iOS only
  208. theme = { textInputTheme }
  209. value = { email } />
  210. </FormSectionAccordion>
  211. <FormSectionAccordion
  212. accordion = { false }
  213. expandable = { false }
  214. label = 'settingsView.conferenceSection'>
  215. <TextInput
  216. autoCapitalize = 'none'
  217. autoCorrect = { false }
  218. editable = { this.props._serverURLChangeEnabled }
  219. keyboardType = { 'url' }
  220. label = { this.props.t('settingsView.serverURL') }
  221. mode = 'outlined'
  222. onBlur = { this._onBlurServerURL }
  223. onChangeText = { this._onChangeServerURL }
  224. placeholder = { this.props._serverURL }
  225. spellCheck = { false }
  226. style = { styles.textInputContainer }
  227. textContentType = { 'URL' } // iOS only
  228. theme = { textInputTheme }
  229. value = { serverURL } />
  230. <Divider style = { styles.fieldSeparator } />
  231. <FormRow
  232. label = 'settingsView.startWithAudioMuted'>
  233. <Switch
  234. onValueChange = { this._onStartAudioMutedChange }
  235. thumbColor = { THUMB_COLOR }
  236. trackColor = {{
  237. true: ENABLED_TRACK_COLOR,
  238. false: DISABLED_TRACK_COLOR
  239. }}
  240. value = { startWithAudioMuted } />
  241. </FormRow>
  242. <Divider style = { styles.fieldSeparator } />
  243. <FormRow label = 'settingsView.startWithVideoMuted'>
  244. <Switch
  245. onValueChange = { this._onStartVideoMutedChange }
  246. thumbColor = { THUMB_COLOR }
  247. trackColor = {{
  248. true: ENABLED_TRACK_COLOR,
  249. false: DISABLED_TRACK_COLOR
  250. }}
  251. value = { startWithVideoMuted } />
  252. </FormRow>
  253. </FormSectionAccordion>
  254. <FormSectionAccordion
  255. accordion = { false }
  256. expandable = { false }
  257. label = 'settingsView.buildInfoSection'>
  258. <FormRow
  259. label = 'settingsView.version'>
  260. <Text style = { styles.text }>
  261. {`${AppInfo.version} build ${AppInfo.buildNumber}`}
  262. </Text>
  263. </FormRow>
  264. </FormSectionAccordion>
  265. <FormSectionAccordion
  266. accordion = { true }
  267. expandable = { true }
  268. label = 'settingsView.advanced'>
  269. { Platform.OS === 'android' && (
  270. <>
  271. <FormRow
  272. label = 'settingsView.disableCallIntegration'>
  273. <Switch
  274. onValueChange = { this._onDisableCallIntegration }
  275. thumbColor = { THUMB_COLOR }
  276. trackColor = {{
  277. true: ENABLED_TRACK_COLOR,
  278. false: DISABLED_TRACK_COLOR
  279. }}
  280. value = { disableCallIntegration } />
  281. </FormRow>
  282. <Divider style = { styles.fieldSeparator } />
  283. </>
  284. )}
  285. <FormRow
  286. label = 'settingsView.disableP2P'>
  287. <Switch
  288. onValueChange = { this._onDisableP2P }
  289. thumbColor = { THUMB_COLOR }
  290. trackColor = {{
  291. true: ENABLED_TRACK_COLOR,
  292. false: DISABLED_TRACK_COLOR
  293. }}
  294. value = { disableP2P } />
  295. </FormRow>
  296. <Divider style = { styles.fieldSeparator } />
  297. {AppInfo.GOOGLE_SERVICES_ENABLED && (
  298. <FormRow
  299. fieldSeparator = { true }
  300. label = 'settingsView.disableCrashReporting'>
  301. <Switch
  302. onValueChange = { this._onDisableCrashReporting }
  303. thumbColor = { THUMB_COLOR }
  304. trackColor = {{
  305. true: ENABLED_TRACK_COLOR,
  306. false: DISABLED_TRACK_COLOR
  307. }}
  308. value = { disableCrashReporting } />
  309. </FormRow>
  310. )}
  311. </FormSectionAccordion>
  312. </ScrollView>
  313. </JitsiScreen>
  314. );
  315. }
  316. _onBlurServerURL: () => void;
  317. /**
  318. * Handler the server URL lose focus event. Here we validate the server URL
  319. * and update it to the normalized version, or show an error if incorrect.
  320. *
  321. * @private
  322. * @returns {void}
  323. */
  324. _onBlurServerURL() {
  325. this._processServerURL(false /* hideOnSuccess */);
  326. }
  327. /**
  328. * Callback to update the display name.
  329. *
  330. * @param {string} displayName - The new value to set.
  331. * @returns {void}
  332. */
  333. _onChangeDisplayName(displayName) {
  334. super._onChangeDisplayName(displayName);
  335. this.setState({
  336. displayName
  337. });
  338. }
  339. /**
  340. * Callback to update the email.
  341. *
  342. * @param {string} email - The new value to set.
  343. * @returns {void}
  344. */
  345. _onChangeEmail(email) {
  346. super._onChangeEmail(email);
  347. this.setState({
  348. email
  349. });
  350. }
  351. /**
  352. * Callback to update the server URL.
  353. *
  354. * @param {string} serverURL - The new value to set.
  355. * @returns {void}
  356. */
  357. _onChangeServerURL(serverURL) {
  358. super._onChangeServerURL(serverURL);
  359. this.setState({
  360. serverURL
  361. });
  362. }
  363. _onDisableCallIntegration: (boolean) => void;
  364. /**
  365. * Handles the disable call integration change event.
  366. *
  367. * @param {boolean} disableCallIntegration - The new value
  368. * option.
  369. * @private
  370. * @returns {void}
  371. */
  372. _onDisableCallIntegration(disableCallIntegration) {
  373. this._updateSettings({
  374. disableCallIntegration
  375. });
  376. this.setState({
  377. disableCallIntegration
  378. });
  379. }
  380. _onDisableP2P: (boolean) => void;
  381. /**
  382. * Handles the disable P2P change event.
  383. *
  384. * @param {boolean} disableP2P - The new value
  385. * option.
  386. * @private
  387. * @returns {void}
  388. */
  389. _onDisableP2P(disableP2P) {
  390. this._updateSettings({
  391. disableP2P
  392. });
  393. this.setState({
  394. disableP2P
  395. });
  396. }
  397. _onDisableCrashReporting: (boolean) => void;
  398. /**
  399. * Handles the disable crash reporting change event.
  400. *
  401. * @param {boolean} disableCrashReporting - The new value
  402. * option.
  403. * @private
  404. * @returns {void}
  405. */
  406. _onDisableCrashReporting(disableCrashReporting) {
  407. if (disableCrashReporting) {
  408. this._showCrashReportingDisableAlert();
  409. } else {
  410. this._disableCrashReporting(disableCrashReporting);
  411. }
  412. }
  413. _onClose: () => void;
  414. /**
  415. * Callback to be invoked on closing the modal. Also invokes normalizeUserInputURL to validate
  416. * the URL entered by the user.
  417. *
  418. * @returns {boolean} - True if the modal can be closed.
  419. */
  420. _onClose() {
  421. return this._processServerURL(true /* hideOnSuccess */);
  422. }
  423. /**
  424. * Callback to update the start with audio muted value.
  425. *
  426. * @param {boolean} startWithAudioMuted - The new value to set.
  427. * @returns {void}
  428. */
  429. _onStartAudioMutedChange(startWithAudioMuted) {
  430. super._onStartAudioMutedChange(startWithAudioMuted);
  431. this.setState({
  432. startWithAudioMuted
  433. });
  434. }
  435. /**
  436. * Callback to update the start with video muted value.
  437. *
  438. * @param {boolean} startWithVideoMuted - The new value to set.
  439. * @returns {void}
  440. */
  441. _onStartVideoMutedChange(startWithVideoMuted) {
  442. super._onStartVideoMutedChange(startWithVideoMuted);
  443. this.setState({
  444. startWithVideoMuted
  445. });
  446. }
  447. /**
  448. * Processes the server URL. It normalizes it and an error alert is
  449. * displayed in case it's incorrect.
  450. *
  451. * @param {boolean} hideOnSuccess - True if the dialog should be hidden if
  452. * normalization / validation succeeds, false otherwise.
  453. * @private
  454. * @returns {void}
  455. */
  456. _processServerURL(hideOnSuccess: boolean) {
  457. const { serverURL } = this.props._settings;
  458. const normalizedURL = normalizeUserInputURL(serverURL);
  459. if (normalizedURL === null) {
  460. this._showURLAlert();
  461. return false;
  462. }
  463. this._onChangeServerURL(normalizedURL);
  464. return hideOnSuccess;
  465. }
  466. _setURLFieldReference: (React$ElementRef<*> | null) => void;
  467. /**
  468. * Stores a reference to the URL field for later use.
  469. *
  470. * @param {Object} component - The field component.
  471. * @protected
  472. * @returns {void}
  473. */
  474. _setURLFieldReference(component) {
  475. this._urlField = component;
  476. }
  477. _showURLAlert: () => void;
  478. /**
  479. * Shows an alert telling the user that the URL he/she entered was invalid.
  480. *
  481. * @returns {void}
  482. */
  483. _showURLAlert() {
  484. const { t } = this.props;
  485. Alert.alert(
  486. t('settingsView.alertTitle'),
  487. t('settingsView.alertURLText'),
  488. [
  489. {
  490. onPress: () => this._urlField.focus(),
  491. text: t('settingsView.alertOk')
  492. }
  493. ]
  494. );
  495. }
  496. /**
  497. * Shows an alert warning the user about disabling crash reporting.
  498. *
  499. * @returns {void}
  500. */
  501. _showCrashReportingDisableAlert() {
  502. const { t } = this.props;
  503. Alert.alert(
  504. t('settingsView.alertTitle'),
  505. t('settingsView.disableCrashReportingWarning'),
  506. [
  507. {
  508. onPress: () => this._disableCrashReporting(true),
  509. text: t('settingsView.alertOk')
  510. },
  511. {
  512. text: t('settingsView.alertCancel')
  513. }
  514. ]
  515. );
  516. }
  517. _updateSettings: (Object) => void;
  518. /**
  519. * Updates the settings and sets state for disableCrashReporting.
  520. *
  521. * @param {boolean} disableCrashReporting - Whether crash reporting is disabled or not.
  522. * @returns {void}
  523. */
  524. _disableCrashReporting(disableCrashReporting) {
  525. this._updateSettings({ disableCrashReporting });
  526. this.setState({ disableCrashReporting });
  527. }
  528. }
  529. /**
  530. * Maps part of the Redux state to the props of this component.
  531. *
  532. * @param {Object} state - The Redux state.
  533. * @returns {Props}
  534. */
  535. function _mapStateToProps(state) {
  536. return {
  537. ..._abstractMapStateToProps(state),
  538. _serverURLChangeEnabled: isServerURLChangeEnabled(state)
  539. };
  540. }
  541. export default translate(connect(_mapStateToProps)(withTheme(SettingsView)));