Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

SettingsView.tsx 23KB

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