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

SecurityDialog.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. // @flow
  2. import Clipboard from '@react-native-community/clipboard';
  3. import React, { PureComponent } from 'react';
  4. import {
  5. Text,
  6. TextInput,
  7. View
  8. } from 'react-native';
  9. import { TouchableRipple } from 'react-native-paper';
  10. import type { Dispatch } from 'redux';
  11. import { ColorSchemeRegistry } from '../../../../base/color-scheme';
  12. import { FIELD_UNDERLINE } from '../../../../base/dialog';
  13. import { getFeatureFlag, MEETING_PASSWORD_ENABLED } from '../../../../base/flags';
  14. import { translate } from '../../../../base/i18n';
  15. import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
  16. import { isLocalParticipantModerator } from '../../../../base/participants';
  17. import { connect } from '../../../../base/redux';
  18. import { StyleType } from '../../../../base/styles';
  19. import BaseTheme from '../../../../base/ui/components/BaseTheme';
  20. import { isInBreakoutRoom } from '../../../../breakout-rooms/functions';
  21. import { toggleLobbyMode } from '../../../../lobby/actions.any';
  22. import LobbyModeSwitch
  23. from '../../../../lobby/components/native/LobbyModeSwitch';
  24. import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../../../room-lock';
  25. import {
  26. endRoomLockRequest,
  27. unlockRoom
  28. } from '../../../../room-lock/actions';
  29. import styles from './styles';
  30. /**
  31. * The style of the {@link TextInput} rendered by {@code SecurityDialog}. As it
  32. * requests the entry of a password, {@code TextInput} automatically correcting
  33. * the entry of the password is a pain to deal with as a user.
  34. */
  35. const _TEXT_INPUT_PROPS = {
  36. autoCapitalize: 'none',
  37. autoCorrect: false
  38. };
  39. /**
  40. * The type of the React {@code Component} props of {@link SecurityDialog}.
  41. */
  42. type Props = {
  43. /**
  44. * The JitsiConference which requires a password.
  45. */
  46. _conference: Object,
  47. /**
  48. * The color-schemed stylesheet of the feature.
  49. */
  50. _dialogStyles: StyleType,
  51. /**
  52. * Whether the local user is the moderator.
  53. */
  54. _isModerator: boolean,
  55. /**
  56. * State of the lobby mode.
  57. */
  58. _lobbyEnabled: boolean,
  59. /**
  60. * Whether the lobby mode switch is available or not.
  61. */
  62. _lobbyModeSwitchVisible: boolean,
  63. /**
  64. * The value for how the conference is locked (or undefined if not locked)
  65. * as defined by room-lock constants.
  66. */
  67. _locked: string,
  68. /**
  69. * Checks if the conference room is locked or not.
  70. */
  71. _lockedConference: boolean,
  72. /**
  73. * The current known password for the JitsiConference.
  74. */
  75. _password: string,
  76. /**
  77. * Number of digits used in the room-lock password.
  78. */
  79. _passwordNumberOfDigits: number,
  80. /**
  81. * Whether setting a room password is available or not.
  82. */
  83. _roomPasswordControls: boolean,
  84. /**
  85. * Redux store dispatch function.
  86. */
  87. dispatch: Dispatch<any>,
  88. /**
  89. * Invoked to obtain translated strings.
  90. */
  91. t: Function
  92. };
  93. /**
  94. * The type of the React {@code Component} state of {@link SecurityDialog}.
  95. */
  96. type State = {
  97. /**
  98. * Password added by the participant for room lock.
  99. */
  100. passwordInputValue: string,
  101. /**
  102. * Shows an input or a message.
  103. */
  104. showElement: boolean
  105. };
  106. /**
  107. * Component that renders the security options dialog.
  108. *
  109. * @returns {React$Element<any>}
  110. */
  111. class SecurityDialog extends PureComponent<Props, State> {
  112. /**
  113. * Instantiates a new {@code SecurityDialog}.
  114. *
  115. * @inheritdoc
  116. */
  117. constructor(props: Props) {
  118. super(props);
  119. this.state = {
  120. passwordInputValue: '',
  121. showElement: props._locked === LOCKED_LOCALLY || false
  122. };
  123. this._onChangeText = this._onChangeText.bind(this);
  124. this._onCancel = this._onCancel.bind(this);
  125. this._onCopy = this._onCopy.bind(this);
  126. this._onSubmit = this._onSubmit.bind(this);
  127. this._onToggleLobbyMode = this._onToggleLobbyMode.bind(this);
  128. this._onAddPassword = this._onAddPassword.bind(this);
  129. }
  130. /**
  131. * Implements {@code SecurityDialog.render}.
  132. *
  133. * @inheritdoc
  134. */
  135. render() {
  136. return (
  137. <JitsiScreen style = { styles.securityDialogContainer }>
  138. { this._renderLobbyMode() }
  139. { this._renderSetRoomPassword() }
  140. </JitsiScreen>
  141. );
  142. }
  143. /**
  144. * Renders lobby mode.
  145. *
  146. * @returns {ReactElement}
  147. * @private
  148. */
  149. _renderLobbyMode() {
  150. const {
  151. _lobbyEnabled,
  152. _lobbyModeSwitchVisible,
  153. t
  154. } = this.props;
  155. if (!_lobbyModeSwitchVisible) {
  156. return null;
  157. }
  158. return (
  159. <View style = { styles.lobbyModeContainer }>
  160. <View style = { styles.lobbyModeContent } >
  161. <Text style = { styles.lobbyModeText }>
  162. { t('lobby.enableDialogText') }
  163. </Text>
  164. <View style = { styles.lobbyModeSection }>
  165. <Text style = { styles.lobbyModeLabel } >
  166. { t('lobby.toggleLabel') }
  167. </Text>
  168. <LobbyModeSwitch
  169. lobbyEnabled = { _lobbyEnabled }
  170. onToggleLobbyMode = { this._onToggleLobbyMode } />
  171. </View>
  172. </View>
  173. </View>
  174. );
  175. }
  176. /**
  177. * Renders setting the password.
  178. *
  179. * @returns {ReactElement}
  180. * @private
  181. */
  182. _renderSetRoomPassword() {
  183. const {
  184. _isModerator,
  185. _locked,
  186. _lockedConference,
  187. _password,
  188. _roomPasswordControls,
  189. t
  190. } = this.props;
  191. const { showElement } = this.state;
  192. let setPasswordControls;
  193. if (!_roomPasswordControls) {
  194. return null;
  195. }
  196. if (_locked && showElement) {
  197. setPasswordControls = (
  198. <>
  199. <TouchableRipple
  200. onPress = { this._onCancel }
  201. rippleColor = { BaseTheme.palette.ui01 } >
  202. <Text style = { styles.passwordSetupButton }>
  203. { t('dialog.Remove') }
  204. </Text>
  205. </TouchableRipple>
  206. {
  207. _password
  208. && <TouchableRipple
  209. onPress = { this._onCopy }
  210. rippleColor = { BaseTheme.palette.ui01 } >
  211. <Text style = { styles.passwordSetupButton }>
  212. { t('dialog.copy') }
  213. </Text>
  214. </TouchableRipple>
  215. }
  216. </>
  217. );
  218. } else if (!_lockedConference && showElement) {
  219. setPasswordControls = (
  220. <>
  221. <TouchableRipple
  222. onPress = { this._onCancel }
  223. rippleColor = { BaseTheme.palette.ui01 } >
  224. <Text style = { styles.passwordSetupButton }>
  225. { t('dialog.Cancel') }
  226. </Text>
  227. </TouchableRipple>
  228. <TouchableRipple
  229. onPress = { this._onSubmit }
  230. rippleColor = { BaseTheme.palette.ui01 } >
  231. <Text style = { styles.passwordSetupButton }>
  232. { t('dialog.add') }
  233. </Text>
  234. </TouchableRipple>
  235. </>
  236. );
  237. } else if (!_lockedConference && !showElement) {
  238. setPasswordControls = (
  239. <TouchableRipple
  240. disabled = { !_isModerator }
  241. onPress = { this._onAddPassword }
  242. rippleColor = { BaseTheme.palette.ui01 } >
  243. <Text style = { styles.passwordSetupButton }>
  244. { t('info.addPassword') }
  245. </Text>
  246. </TouchableRipple>
  247. );
  248. }
  249. if (_locked === LOCKED_REMOTELY) {
  250. if (_isModerator) {
  251. setPasswordControls = (
  252. <View style = { styles.passwordSetRemotelyContainer }>
  253. <Text style = { styles.passwordSetRemotelyText }>
  254. { t('passwordSetRemotely') }
  255. </Text>
  256. <TouchableRipple
  257. onPress = { this._onCancel }
  258. rippleColor = { BaseTheme.palette.ui01 } >
  259. <Text style = { styles.passwordSetupButton }>
  260. { t('dialog.Remove') }
  261. </Text>
  262. </TouchableRipple>
  263. </View>
  264. );
  265. } else {
  266. setPasswordControls = (
  267. <View style = { styles.passwordSetRemotelyContainer }>
  268. <Text style = { styles.passwordSetRemotelyTextDisabled }>
  269. { t('passwordSetRemotely') }
  270. </Text>
  271. <TouchableRipple
  272. disabled = { !_isModerator }
  273. onPress = { this._onAddPassword }
  274. rippleColor = { BaseTheme.palette.ui01 } >
  275. <Text style = { styles.passwordSetupButton }>
  276. { t('info.addPassword') }
  277. </Text>
  278. </TouchableRipple>
  279. </View>
  280. );
  281. }
  282. }
  283. return (
  284. <View
  285. style = { styles.passwordContainer } >
  286. <Text style = { styles.passwordContainerText }>
  287. { t('security.about') }
  288. </Text>
  289. <View
  290. style = {
  291. _locked !== LOCKED_REMOTELY
  292. && styles.passwordContainerControls
  293. }>
  294. <View>
  295. { this._setRoomPasswordMessage() }
  296. </View>
  297. { setPasswordControls }
  298. </View>
  299. </View>
  300. );
  301. }
  302. /**
  303. * Renders room lock text input/message.
  304. *
  305. * @returns {ReactElement}
  306. * @private
  307. */
  308. _setRoomPasswordMessage() {
  309. let textInputProps = _TEXT_INPUT_PROPS;
  310. const {
  311. _isModerator,
  312. _locked,
  313. _password,
  314. _passwordNumberOfDigits,
  315. t
  316. } = this.props;
  317. const { passwordInputValue, showElement } = this.state;
  318. if (_passwordNumberOfDigits) {
  319. textInputProps = {
  320. ...textInputProps,
  321. keyboardType: 'numeric',
  322. maxLength: _passwordNumberOfDigits
  323. };
  324. }
  325. if (!_isModerator) {
  326. return null;
  327. }
  328. if (showElement) {
  329. if (typeof _locked === 'undefined') {
  330. return (
  331. <TextInput
  332. autoFocus = { true }
  333. onChangeText = { this._onChangeText }
  334. placeholder = { t('lobby.passwordField') }
  335. placeholderTextColor = { BaseTheme.palette.text03 }
  336. selectionColor = { BaseTheme.palette.text03 }
  337. style = { styles.passwordInput }
  338. underlineColorAndroid = { FIELD_UNDERLINE }
  339. value = { passwordInputValue }
  340. { ...textInputProps } />
  341. );
  342. } else if (_locked) {
  343. if (_locked === LOCKED_LOCALLY && typeof _password !== 'undefined') {
  344. return (
  345. <View style = { styles.savedPasswordContainer }>
  346. <Text style = { styles.savedPasswordLabel }>
  347. { t('info.password') }
  348. </Text>
  349. <Text style = { styles.savedPassword }>
  350. { passwordInputValue }
  351. </Text>
  352. </View>
  353. );
  354. }
  355. }
  356. }
  357. }
  358. _onToggleLobbyMode: () => void;
  359. /**
  360. * Handles the enable-disable lobby mode switch.
  361. *
  362. * @private
  363. * @returns {void}
  364. */
  365. _onToggleLobbyMode() {
  366. const { _lobbyEnabled, dispatch } = this.props;
  367. if (_lobbyEnabled) {
  368. dispatch(toggleLobbyMode(false));
  369. } else {
  370. dispatch(toggleLobbyMode(true));
  371. }
  372. }
  373. _onAddPassword: () => void;
  374. /**
  375. * Callback to be invoked when add password button is pressed.
  376. *
  377. * @returns {void}
  378. */
  379. _onAddPassword() {
  380. const { showElement } = this.state;
  381. this.setState({
  382. showElement: !showElement
  383. });
  384. }
  385. /**
  386. * Verifies input in case only digits are required.
  387. *
  388. * @param {string} passwordInputValue - The value of the password
  389. * text input.
  390. * @private
  391. * @returns {boolean} False when the value is not valid and True otherwise.
  392. */
  393. _validateInputValue(passwordInputValue: string) {
  394. const { _passwordNumberOfDigits } = this.props;
  395. // we want only digits,
  396. // but both number-pad and numeric add ',' and '.' as symbols
  397. if (_passwordNumberOfDigits
  398. && passwordInputValue.length > 0
  399. && !/^\d+$/.test(passwordInputValue)) {
  400. return false;
  401. }
  402. return true;
  403. }
  404. _onChangeText: string => void;
  405. /**
  406. * Callback to be invoked when the text in the field changes.
  407. *
  408. * @param {string} passwordInputValue - The value of password input.
  409. * @returns {void}
  410. */
  411. _onChangeText(passwordInputValue) {
  412. if (!this._validateInputValue(passwordInputValue)) {
  413. return;
  414. }
  415. this.setState({
  416. passwordInputValue
  417. });
  418. }
  419. _onCancel: () => void;
  420. /**
  421. * Cancels value typed in text input.
  422. *
  423. * @returns {void}
  424. */
  425. _onCancel() {
  426. this.setState({
  427. passwordInputValue: '',
  428. showElement: false
  429. });
  430. this.props.dispatch(unlockRoom());
  431. }
  432. _onCopy: () => void;
  433. /**
  434. * Copies room password.
  435. *
  436. * @returns {void}
  437. */
  438. _onCopy() {
  439. const { passwordInputValue } = this.state;
  440. Clipboard.setString(passwordInputValue);
  441. }
  442. _onSubmit: () => void;
  443. /**
  444. * Submits value typed in text input.
  445. *
  446. * @returns {void}
  447. */
  448. _onSubmit() {
  449. const {
  450. _conference,
  451. dispatch
  452. } = this.props;
  453. const { passwordInputValue } = this.state;
  454. dispatch(endRoomLockRequest(_conference, passwordInputValue));
  455. }
  456. }
  457. /**
  458. * Maps part of the Redux state to the props of this component.
  459. *
  460. * @param {Object} state - The Redux state.
  461. * @returns {Props}
  462. */
  463. function _mapStateToProps(state: Object): Object {
  464. const { conference, locked, password } = state['features/base/conference'];
  465. const { hideLobbyButton } = state['features/base/config'];
  466. const { lobbyEnabled } = state['features/lobby'];
  467. const { roomPasswordNumberOfDigits } = state['features/base/config'];
  468. const lobbySupported = conference && conference.isLobbySupported();
  469. const visible = getFeatureFlag(state, MEETING_PASSWORD_ENABLED, true);
  470. return {
  471. _conference: conference,
  472. _dialogStyles: ColorSchemeRegistry.get(state, 'Dialog'),
  473. _isModerator: isLocalParticipantModerator(state),
  474. _lobbyEnabled: lobbyEnabled,
  475. _lobbyModeSwitchVisible:
  476. lobbySupported && isLocalParticipantModerator(state) && !hideLobbyButton && !isInBreakoutRoom(state),
  477. _locked: locked,
  478. _lockedConference: Boolean(conference && locked),
  479. _password: password,
  480. _passwordNumberOfDigits: roomPasswordNumberOfDigits,
  481. _roomPasswordControls: visible
  482. };
  483. }
  484. export default translate(connect(_mapStateToProps)(SecurityDialog));