Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

SecurityDialog.js 16KB

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