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.

SecurityDialog.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. // @flow
  2. import React, { PureComponent } from 'react';
  3. import {
  4. KeyboardAvoidingView,
  5. Platform,
  6. Text,
  7. TextInput,
  8. View
  9. } from 'react-native';
  10. import { connect } from 'react-redux';
  11. import type { Dispatch } from 'redux';
  12. import { ColorSchemeRegistry } from '../../../../base/color-scheme';
  13. import {
  14. FIELD_UNDERLINE,
  15. CustomSubmitDialog
  16. } from '../../../../base/dialog';
  17. import { getFeatureFlag, MEETING_PASSWORD_ENABLED } from '../../../../base/flags';
  18. import { translate } from '../../../../base/i18n';
  19. import { isLocalParticipantModerator } from '../../../../base/participants';
  20. import { StyleType } from '../../../../base/styles';
  21. import { toggleLobbyMode } from '../../../../lobby/actions.any';
  22. import LobbyModeSwitch
  23. from '../../../../lobby/components/native/LobbyModeSwitch';
  24. import { LOCKED_LOCALLY } from '../../../../room-lock';
  25. import {
  26. endRoomLockRequest,
  27. unlockRoom
  28. } from '../../../../room-lock/actions';
  29. import RoomLockSwitch from '../../../../room-lock/components/RoomLockSwitch';
  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 the room lock switch is available or not.
  82. */
  83. _roomLockSwitchVisible: boolean,
  84. /**
  85. * The color-schemed stylesheet of the security dialog feature.
  86. */
  87. _securityDialogStyles: StyleType,
  88. /**
  89. * Redux store dispatch function.
  90. */
  91. dispatch: Dispatch<any>,
  92. /**
  93. * Invoked to obtain translated strings.
  94. */
  95. t: Function
  96. };
  97. /**
  98. * The type of the React {@code Component} state of {@link SecurityDialog}.
  99. */
  100. type State = {
  101. /**
  102. * Password added by the participant for room lock.
  103. */
  104. passwordInputValue: string,
  105. /**
  106. * Shows an input or a message.
  107. */
  108. showElement: boolean
  109. };
  110. /**
  111. * Component that renders the security options dialog.
  112. *
  113. * @returns {React$Element<any>}
  114. */
  115. class SecurityDialog extends PureComponent<Props, State> {
  116. /**
  117. * Instantiates a new {@code SecurityDialog}.
  118. *
  119. * @inheritdoc
  120. */
  121. constructor(props: Props) {
  122. super(props);
  123. this.state = {
  124. passwordInputValue: '',
  125. showElement: props._locked === LOCKED_LOCALLY || false
  126. };
  127. this._onChangeText = this._onChangeText.bind(this);
  128. this._onSubmit = this._onSubmit.bind(this);
  129. this._onToggleLobbyMode = this._onToggleLobbyMode.bind(this);
  130. this._onToggleRoomLock = this._onToggleRoomLock.bind(this);
  131. }
  132. /**
  133. * Implements {@code SecurityDialog.render}.
  134. *
  135. * @inheritdoc
  136. */
  137. render() {
  138. return (
  139. <CustomSubmitDialog
  140. onSubmit = { this._onSubmit }>
  141. <KeyboardAvoidingView
  142. behavior =
  143. {
  144. Platform.OS === 'ios'
  145. ? 'padding' : 'height'
  146. }
  147. enabled = { true }>
  148. { this._renderLobbyMode() }
  149. { this._renderRoomLock() }
  150. </KeyboardAvoidingView>
  151. </CustomSubmitDialog>
  152. );
  153. }
  154. /**
  155. * Renders lobby mode.
  156. *
  157. * @returns {ReactElement}
  158. * @private
  159. */
  160. _renderLobbyMode() {
  161. const {
  162. _lobbyEnabled,
  163. _lobbyModeSwitchVisible,
  164. _securityDialogStyles,
  165. t
  166. } = this.props;
  167. if (!_lobbyModeSwitchVisible) {
  168. return null;
  169. }
  170. return (
  171. <View>
  172. <Text style = { _securityDialogStyles.title } >
  173. { t('lobby.dialogTitle') }
  174. </Text>
  175. <Text style = { _securityDialogStyles.text } >
  176. { t('lobby.enableDialogText') }
  177. </Text>
  178. <LobbyModeSwitch
  179. lobbyEnabled = { _lobbyEnabled }
  180. onToggleLobbyMode = { this._onToggleLobbyMode } />
  181. </View>
  182. );
  183. }
  184. /**
  185. * Renders room lock.
  186. *
  187. * @returns {ReactElement}
  188. * @private
  189. */
  190. _renderRoomLock() {
  191. const {
  192. _isModerator,
  193. _locked,
  194. _lockedConference,
  195. _roomLockSwitchVisible,
  196. _securityDialogStyles,
  197. t
  198. } = this.props;
  199. const { showElement } = this.state;
  200. if (!_roomLockSwitchVisible) {
  201. return null;
  202. }
  203. return (
  204. <View>
  205. <Text style = { _securityDialogStyles.title } >
  206. { t('dialog.lockRoom') }
  207. </Text>
  208. <Text style = { _securityDialogStyles.text } >
  209. { t('security.about') }
  210. </Text>
  211. <RoomLockSwitch
  212. disabled = { !_isModerator }
  213. locked = { _locked }
  214. onToggleRoomLock = { this._onToggleRoomLock }
  215. toggleRoomLock = { showElement || _lockedConference } />
  216. { this._renderRoomLockMessage() }
  217. </View>
  218. );
  219. }
  220. /**
  221. * Renders room lock text input/message.
  222. *
  223. * @returns {ReactElement}
  224. * @private
  225. */
  226. _renderRoomLockMessage() {
  227. let textInputProps = _TEXT_INPUT_PROPS;
  228. const {
  229. _isModerator,
  230. _locked,
  231. _password,
  232. _passwordNumberOfDigits,
  233. _securityDialogStyles,
  234. t
  235. } = this.props;
  236. const { passwordInputValue, showElement } = this.state;
  237. if (_passwordNumberOfDigits) {
  238. textInputProps = {
  239. ...textInputProps,
  240. keyboardType: 'numeric',
  241. maxLength: _passwordNumberOfDigits
  242. };
  243. }
  244. if (!_isModerator) {
  245. return null;
  246. }
  247. if (showElement) {
  248. if (typeof _locked === 'undefined') {
  249. return (
  250. <TextInput
  251. onChangeText = { this._onChangeText }
  252. placeholder = { t('lobby.passwordField') }
  253. style = { _securityDialogStyles.field }
  254. underlineColorAndroid = { FIELD_UNDERLINE }
  255. value = { passwordInputValue }
  256. { ...textInputProps } />
  257. );
  258. } else if (_locked) {
  259. if (_locked === LOCKED_LOCALLY && typeof _password !== 'undefined') {
  260. return (
  261. <TextInput
  262. onChangeText = { this._onChangeText }
  263. placeholder = { _password }
  264. style = { _securityDialogStyles.field }
  265. underlineColorAndroid = { FIELD_UNDERLINE }
  266. value = { passwordInputValue }
  267. { ...textInputProps } />
  268. );
  269. }
  270. }
  271. }
  272. }
  273. _onToggleLobbyMode: () => void;
  274. /**
  275. * Handles the enable-disable lobby mode switch.
  276. *
  277. * @private
  278. * @returns {void}
  279. */
  280. _onToggleLobbyMode() {
  281. const { _lobbyEnabled, dispatch } = this.props;
  282. if (_lobbyEnabled) {
  283. dispatch(toggleLobbyMode(false));
  284. } else {
  285. dispatch(toggleLobbyMode(true));
  286. }
  287. }
  288. _onToggleRoomLock: () => void;
  289. /**
  290. * Callback to be invoked when room lock button is pressed.
  291. *
  292. * @returns {void}
  293. */
  294. _onToggleRoomLock() {
  295. const { _isModerator, _locked, dispatch } = this.props;
  296. const { showElement } = this.state;
  297. this.setState({
  298. showElement: !showElement
  299. });
  300. if (_locked && _isModerator) {
  301. dispatch(unlockRoom());
  302. this.setState({
  303. showElement: false
  304. });
  305. }
  306. }
  307. /**
  308. * Verifies input in case only digits are required.
  309. *
  310. * @param {string} passwordInputValue - The value of the password
  311. * text input.
  312. * @private
  313. * @returns {boolean} False when the value is not valid and True otherwise.
  314. */
  315. _validateInputValue(passwordInputValue: string) {
  316. const { _passwordNumberOfDigits } = this.props;
  317. // we want only digits,
  318. // but both number-pad and numeric add ',' and '.' as symbols
  319. if (_passwordNumberOfDigits
  320. && passwordInputValue.length > 0
  321. && !/^\d+$/.test(passwordInputValue)) {
  322. return false;
  323. }
  324. return true;
  325. }
  326. _onChangeText: string => void;
  327. /**
  328. * Callback to be invoked when the text in the field changes.
  329. *
  330. * @param {string} passwordInputValue - The value of password input.
  331. * @returns {void}
  332. */
  333. _onChangeText(passwordInputValue) {
  334. if (!this._validateInputValue(passwordInputValue)) {
  335. return;
  336. }
  337. this.setState({
  338. passwordInputValue
  339. });
  340. }
  341. _onSubmit: () => boolean;
  342. /**
  343. * Submits value typed in text input.
  344. *
  345. * @returns {boolean} False because we do not want to hide this
  346. * dialog/prompt as the hiding will be handled inside endRoomLockRequest
  347. * after setting the password is resolved.
  348. */
  349. _onSubmit() {
  350. const {
  351. _conference,
  352. dispatch
  353. } = this.props;
  354. const { passwordInputValue } = this.state;
  355. dispatch(endRoomLockRequest(_conference, passwordInputValue));
  356. return false;
  357. }
  358. }
  359. /**
  360. * Maps part of the Redux state to the props of this component.
  361. *
  362. * @param {Object} state - The Redux state.
  363. * @returns {Props}
  364. */
  365. function _mapStateToProps(state: Object): Object {
  366. const { conference, locked, password } = state['features/base/conference'];
  367. const { hideLobbyButton } = state['features/base/config'];
  368. const { lobbyEnabled } = state['features/lobby'];
  369. const { roomPasswordNumberOfDigits } = state['features/base/config'];
  370. const lobbySupported = conference && conference.isLobbySupported();
  371. const visible = getFeatureFlag(state, MEETING_PASSWORD_ENABLED, true);
  372. return {
  373. _conference: conference,
  374. _dialogStyles: ColorSchemeRegistry.get(state, 'Dialog'),
  375. _isModerator: isLocalParticipantModerator(state),
  376. _lobbyEnabled: lobbyEnabled,
  377. _lobbyModeSwitchVisible:
  378. lobbySupported && isLocalParticipantModerator(state) && !hideLobbyButton,
  379. _locked: locked,
  380. _lockedConference: Boolean(conference && locked),
  381. _password: password,
  382. _passwordNumberOfDigits: roomPasswordNumberOfDigits,
  383. _roomLockSwitchVisible: visible,
  384. _securityDialogStyles: ColorSchemeRegistry.get(state, 'SecurityDialog')
  385. };
  386. }
  387. export default translate(connect(_mapStateToProps)(SecurityDialog));