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

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