diff --git a/android/sdk/build.gradle b/android/sdk/build.gradle index 8d283a7d31..1d96849710 100644 --- a/android/sdk/build.gradle +++ b/android/sdk/build.gradle @@ -68,6 +68,7 @@ dependencies { implementation project(':react-native-async-storage') implementation project(':react-native-background-timer') implementation project(':react-native-calendar-events') + implementation project(':react-native-community_clipboard') implementation project(':react-native-community_netinfo') implementation project(':react-native-default-preference') implementation project(':react-native-gesture-handler') diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java index 7e40bbc8e1..1e86e1de33 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java @@ -180,6 +180,7 @@ class ReactInstanceManagerHolder { new com.calendarevents.CalendarEventsPackage(), new com.corbt.keepawake.KCKeepAwakePackage(), new com.facebook.react.shell.MainReactPackage(), + new com.reactnativecommunity.clipboard.ClipboardPackage(), new com.reactnativecommunity.netinfo.NetInfoPackage(), new com.oblador.performance.PerformancePackage(), new com.reactnativecommunity.slider.ReactSliderPackage(), diff --git a/android/settings.gradle b/android/settings.gradle index 7f2ea526d1..a6aa74e994 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -9,6 +9,8 @@ include ':react-native-background-timer' project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android') include ':react-native-calendar-events' project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android') +include ':react-native-community_clipboard' +project(':react-native-community_clipboard').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/clipboard/android') include ':react-native-community_netinfo' project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android') include ':react-native-default-preference' diff --git a/ios/Podfile b/ios/Podfile index ffa0b456c4..9a67203e10 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -61,23 +61,24 @@ target 'JitsiMeetSDK' do pod 'react-native-keep-awake', :path => '../node_modules/react-native-keep-awake' pod 'react-native-netinfo', :path => '../node_modules/@react-native-community/netinfo' pod 'react-native-performance', :path => '../node_modules/react-native-performance/ios' + pod 'react-native-safe-area-context', :path => '../node_modules/react-native-safe-area-context' pod 'react-native-slider', :path => '../node_modules/@react-native-community/slider' pod 'react-native-splash-screen', :path => '../node_modules/react-native-splash-screen' pod 'react-native-video', :path => '../node_modules/react-native-video/react-native-video.podspec' pod 'react-native-webview', :path => '../node_modules/react-native-webview' pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc' pod 'RNCAsyncStorage', :path => '../node_modules/@react-native-async-storage/async-storage' + pod 'RNCClipboard', :path => '../node_modules/@react-native-community/clipboard' + pod 'RNCMaskedView', :path => '../node_modules/@react-native-masked-view/masked-view' + pod 'RNDefaultPreference', :path => '../node_modules/react-native-default-preference' pod 'RNDeviceInfo', :path => '../node_modules/react-native-device-info' + pod 'RNGestureHandler', :path => '../node_modules/react-native-gesture-handler' pod 'RNGoogleSignin', :path => '../node_modules/@react-native-community/google-signin' + pod 'RNReanimated', :path => '../node_modules/react-native-reanimated' + pod 'RNScreens', :path => '../node_modules/react-native-screens' pod 'RNSound', :path => '../node_modules/react-native-sound' pod 'RNSVG', :path => '../node_modules/react-native-svg' pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity' - pod 'RNDefaultPreference', :path => '../node_modules/react-native-default-preference' - pod 'RNGestureHandler', :path => '../node_modules/react-native-gesture-handler' - pod 'RNReanimated', :path => '../node_modules/react-native-reanimated' - pod 'RNScreens', :path => '../node_modules/react-native-screens' - pod 'react-native-safe-area-context', :path => '../node_modules/react-native-safe-area-context' - pod 'RNCMaskedView', :path => '../node_modules/@react-native-masked-view/masked-view' # Native pod dependencies # diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6db35f3920..cdcc913ce9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -363,6 +363,8 @@ PODS: - ReactCommon/turbomodule/core (= 0.61.5-jitsi.2) - RNCAsyncStorage (1.15.5): - React-Core + - RNCClipboard (1.5.1): + - React-Core - RNCMaskedView (0.2.6): - React-Core - RNDefaultPreference (1.4.2): @@ -435,6 +437,7 @@ DEPENDENCIES: - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - ReactCommon/turbomodule (from `../node_modules/react-native/ReactCommon`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" + - "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)" - "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)" - RNDefaultPreference (from `../node_modules/react-native-default-preference`) - RNDeviceInfo (from `../node_modules/react-native-device-info`) @@ -547,6 +550,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" + RNCClipboard: + :path: "../node_modules/@react-native-community/clipboard" RNCMaskedView: :path: "../node_modules/@react-native-masked-view/masked-view" RNDefaultPreference: @@ -629,6 +634,7 @@ SPEC CHECKSUMS: React-RCTVibration: c1041024893fdfdb8371e7c720c437751b711676 ReactCommon: 18014e1d98dbeb9141e935cfe35fc93bd511ffb6 RNCAsyncStorage: 56a3355a10b5d660c48c6e37325ac85ebfd09885 + RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495 RNCMaskedView: c298b644a10c0c142055b3ae24d83879ecb13ccd RNDefaultPreference: 1f8133ec0bc0f9453cdada578564ba1ef551fb44 RNDeviceInfo: 87d2d175c760f6bcf58acd036f887e8b2392802c @@ -641,6 +647,6 @@ SPEC CHECKSUMS: RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3 Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c -PODFILE CHECKSUM: 836d4804218c0608e1326471ec83fe31cfa9c86d +PODFILE CHECKSUM: 0cfc1f35e2872ceb0a86252e14e226bd489a2602 COCOAPODS: 1.11.2 diff --git a/lang/main.json b/lang/main.json index 9820fcc738..36eade6cb1 100644 --- a/lang/main.json +++ b/lang/main.json @@ -470,7 +470,7 @@ "noPassword": "None", "noRoom": "No room was specified to dial-in into.", "numbers": "Dial-in Numbers", - "password": "$t(lockRoomPasswordUppercase):", + "password": "$t(lockRoomPasswordUppercase): ", "sip": "SIP address", "title": "Share", "tooltip": "Share link and dial-in info for this meeting", @@ -824,8 +824,8 @@ "security": { "about": "You can add a $t(lockRoomPassword) to your meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.", "aboutReadOnly": "Moderator participants can add a $t(lockRoomPassword) to the meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.", - "insecureRoomNameWarning": "The room name is unsafe. Unwanted participants may join your conference. Consider securing your meeting using the security button.", - "securityOptions": "Security options" + "header": "Security Options", + "insecureRoomNameWarning": "The room name is unsafe. Unwanted participants may join your conference. Consider securing your meeting using the security button." }, "settings": { "calendar": { @@ -893,20 +893,20 @@ }, "speaker": "Speaker", "speakerStats": { - "search": "Search", + "angry": "Angry", + "disgusted": "Disgusted", + "fearful": "Fearful", + "happy": "Happy", "hours": "{{count}}h", "minutes": "{{count}}m", "name": "Name", - "seconds": "{{count}}s", - "speakerStats": "Speaker Stats", - "speakerTime": "Speaker Time", - "happy": "Happy", "neutral": "Neutral", "sad": "Sad", - "surprised": "Surprised", - "angry": "Angry", - "fearful": "Fearful", - "disgusted": "Disgusted" + "search": "Search", + "seconds": "{{count}}s", + "speakerTime": "Speaker Time", + "speakerStats": "Speaker Stats", + "surprised": "Surprised" }, "startupoverlay": { "policyText": " ", diff --git a/package-lock.json b/package-lock.json index 368533ef7b..5c7a09a1be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "@microsoft/microsoft-graph-client": "1.1.0", "@react-native-async-storage/async-storage": "1.15.5", + "@react-native-community/clipboard": "1.5.1", "@react-native-community/google-signin": "3.0.1", "@react-native-community/netinfo": "4.1.5", "@react-native-community/slider": "3.0.3", @@ -4097,6 +4098,15 @@ "node": ">=8" } }, + "node_modules/@react-native-community/clipboard": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@react-native-community/clipboard/-/clipboard-1.5.1.tgz", + "integrity": "sha512-AHAmrkLEH5UtPaDiRqoULERHh3oNv7Dgs0bTC0hO5Z2GdNokAMPT5w8ci8aMcRemcwbtdHjxChgtjbeA38GBdA==", + "peerDependencies": { + "react": ">=16.0", + "react-native": ">=0.57.0" + } + }, "node_modules/@react-native-community/google-signin": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/google-signin/-/google-signin-3.0.1.tgz", @@ -23388,6 +23398,11 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-3.0.0.tgz", "integrity": "sha512-ng6Tm537E/M42GjE4TRUxQyL8sRfClcL7bQWblOCoxPZzJ2J3bdALsjeG3vDnVCIfI/R0AeFalN9KjMt0+Z/Zg==" }, + "@react-native-community/clipboard": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@react-native-community/clipboard/-/clipboard-1.5.1.tgz", + "integrity": "sha512-AHAmrkLEH5UtPaDiRqoULERHh3oNv7Dgs0bTC0hO5Z2GdNokAMPT5w8ci8aMcRemcwbtdHjxChgtjbeA38GBdA==" + }, "@react-native-community/google-signin": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/google-signin/-/google-signin-3.0.1.tgz", diff --git a/package.json b/package.json index e44bf6cdea..d774cc5c81 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "@microsoft/microsoft-graph-client": "1.1.0", "@react-native-async-storage/async-storage": "1.15.5", + "@react-native-community/clipboard": "1.5.1", "@react-native-community/google-signin": "3.0.1", "@react-native-community/netinfo": "4.1.5", "@react-native-community/slider": "3.0.3", diff --git a/react/features/base/ui/Tokens.js b/react/features/base/ui/Tokens.js index 48147ccf3c..277e2b9506 100644 --- a/react/features/base/ui/Tokens.js +++ b/react/features/base/ui/Tokens.js @@ -19,6 +19,7 @@ export const colors = { primary09: '#CCDDF9', primary10: '#17A0DB', primary11: '#1081B2', + primary12: '#B8C7E0', surface00: '#111111', surface01: '#040404', @@ -158,6 +159,9 @@ export const colorMap = { // Text for drawer menu displayed name text05: 'surface06', + // Text for saved input values + text06: 'surface03', + // error messages textError: 'error06', @@ -226,6 +230,8 @@ export const colorMap = { // Line separators border03: 'surface04', + border04: 'primary12', + // Color for error border & message borderError: 'error06', diff --git a/react/features/breakout-rooms/actions.js b/react/features/breakout-rooms/actions.js index fed2faa536..9cac6e6656 100644 --- a/react/features/breakout-rooms/actions.js +++ b/react/features/breakout-rooms/actions.js @@ -17,12 +17,12 @@ import { setVideoMuted } from '../base/media'; import { getRemoteParticipants } from '../base/participants'; -import { createDesiredLocalTracks } from '../base/tracks/actions'; import { getLocalTracks, isLocalCameraTrackMuted, isLocalTrackMuted } from '../base/tracks'; +import { createDesiredLocalTracks } from '../base/tracks/actions'; import { NOTIFICATION_TIMEOUT_TYPE, clearNotifications, diff --git a/react/features/conference/components/native/ConferenceNavigationContainer.js b/react/features/conference/components/native/ConferenceNavigationContainer.js index 278a3a4476..070f642282 100644 --- a/react/features/conference/components/native/ConferenceNavigationContainer.js +++ b/react/features/conference/components/native/ConferenceNavigationContainer.js @@ -13,6 +13,8 @@ import AddPeopleDialog from '../../../invite/components/add-people-dialog/native/AddPeopleDialog'; import LobbyScreen from '../../../lobby/components/native/LobbyScreen'; import { ParticipantsPane } from '../../../participants-pane/components/native'; +import SecurityDialog + from '../../../security/components/security-dialog/native/SecurityDialog'; import SpeakerStats from '../../../speaker-stats/components/native/SpeakerStats'; import { getDisablePolls } from '../../functions'; @@ -28,6 +30,7 @@ import { lobbyScreenOptions, navigationContainerTheme, participantsScreenOptions, + securityScreenOptions, sharedDocumentScreenOptions, speakerStatsScreenOptions } from './ConferenceNavigatorScreenOptions'; @@ -80,11 +83,19 @@ const ConferenceNavigationContainer = () => { ...participantsScreenOptions, title: t('participantsPane.header') }} /> + { - { t('inviteDialog.send') } + + { t('inviteDialog.send') } ) @@ -171,9 +171,8 @@ class AddPeopleDialog extends AbstractAddPeopleDialog { disabled = { this._isAddDisabled() } onPress = { this._onInvite } rippleColor = { palette.screen01Header } > - { t('inviteDialog.send') } + + { t('inviteDialog.send') } ) diff --git a/react/features/lobby/components/native/styles.js b/react/features/lobby/components/native/styles.js index fb616c1854..2a41bc55de 100644 --- a/react/features/lobby/components/native/styles.js +++ b/react/features/lobby/components/native/styles.js @@ -1,12 +1,12 @@ // @flow -import { ColorPalette } from '../../../base/styles'; +import BaseTheme from '../../../base/ui/components/BaseTheme'; -const SECONDARY_COLOR = '#B8C7E0'; +const SECONDARY_COLOR = BaseTheme.palette.border04; -export const ENABLED_THUMB_COLOR = ColorPalette.blueHighlight; -export const ENABLED_TRACK_COLOR = ColorPalette.blue; -export const DISABLED_THUMB_COLOR = ColorPalette.darkGrey; +export const ENABLED_THUMB_COLOR = BaseTheme.palette.action04; +export const ENABLED_TRACK_COLOR = BaseTheme.palette.screen01Header; +export const DISABLED_THUMB_COLOR = BaseTheme.palette.icon04; export default { button: { @@ -61,7 +61,7 @@ export default { }, fieldError: { - color: ColorPalette.warning, + color: BaseTheme.palette.warning07, fontSize: 10 }, @@ -165,7 +165,7 @@ export default { lobbySwitchContainer: { flexDirection: 'column', - marginTop: 16 + marginTop: BaseTheme.spacing[2] }, lobbySwitchIcon: { diff --git a/react/features/room-lock/components/RoomLockSwitch.js b/react/features/room-lock/components/RoomLockSwitch.js deleted file mode 100644 index 746522c23d..0000000000 --- a/react/features/room-lock/components/RoomLockSwitch.js +++ /dev/null @@ -1,83 +0,0 @@ -// @flow - -import React from 'react'; -import { Switch, Text, View } from 'react-native'; - -import { translate } from '../../base/i18n'; -import { connect } from '../../base/redux'; -import { LOCKED_REMOTELY } from '../constants'; - -import styles, { - DISABLED_THUMB_COLOR, - ENABLED_THUMB_COLOR, ENABLED_TRACK_COLOR -} from './styles'; - -/** - * The type of the React {@code Component} props of {@link RoomLockSwitch}. - */ -type Props = { - - /** - * Checks if the room is locked based on defined room lock constants. - */ - locked: string, - - /** - * Whether the switch is disabled. - */ - disabled: boolean, - - /** - * Callback to be invoked when the user toggles room lock. - */ - onToggleRoomLock: Function, - - /** - * Control for room lock. - */ - toggleRoomLock: boolean, - - /** - * Invoked to obtain translated strings. - */ - t: Function -}; - -/** - * Component meant to Add/Remove meeting password. - * - * @returns {React$Element} - */ -function RoomLockSwitch( - { - locked, - disabled, - onToggleRoomLock, - toggleRoomLock, - t - }: Props) { - - return ( - - - { - locked === LOCKED_REMOTELY - && t('passwordSetRemotely') - } - - - - ); -} - - -export default translate(connect()(RoomLockSwitch)); diff --git a/react/features/room-lock/components/styles.js b/react/features/room-lock/components/styles.js deleted file mode 100644 index 701da77030..0000000000 --- a/react/features/room-lock/components/styles.js +++ /dev/null @@ -1,16 +0,0 @@ -// @flow - -import { ColorPalette } from '../../base/styles'; - -export const ENABLED_THUMB_COLOR = ColorPalette.blueHighlight; -export const ENABLED_TRACK_COLOR = ColorPalette.blue; -export const DISABLED_THUMB_COLOR = ColorPalette.darkGrey; - -export default { - roomLockSwitchContainer: { - alignItems: 'center', - flexDirection: 'row', - justifyContent: 'space-between', - marginTop: 16 - } -}; diff --git a/react/features/security/components/security-dialog/SecurityDialogButton.js b/react/features/security/components/security-dialog/AbstractSecurityDialogButton.js similarity index 78% rename from react/features/security/components/security-dialog/SecurityDialogButton.js rename to react/features/security/components/security-dialog/AbstractSecurityDialogButton.js index 87fd498e02..37ceb44f91 100644 --- a/react/features/security/components/security-dialog/SecurityDialogButton.js +++ b/react/features/security/components/security-dialog/AbstractSecurityDialogButton.js @@ -9,15 +9,11 @@ import { MEETING_PASSWORD_ENABLED, SECURITY_OPTIONS_ENABLED } from '../../../base/flags'; -import { translate } from '../../../base/i18n'; import { IconSecurityOff, IconSecurityOn } from '../../../base/icons'; import { isLocalParticipantModerator } from '../../../base/participants'; -import { connect } from '../../../base/redux'; import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; -import { toggleSecurityDialog } from '../../actions'; - -type Props = AbstractButtonProps & { +export type Props = AbstractButtonProps & { /** * Whether the shared document is being edited or not. @@ -32,9 +28,10 @@ type Props = AbstractButtonProps & { /** - * Implements an {@link AbstractButton} to open the security dialog. + * Implements an {@link AbstractButton} to open the security dialog/screen. */ -class SecurityDialogButton extends AbstractButton { +export default class AbstractSecurityDialogButton + extends AbstractButton { accessibilityLabel = 'toolbar.accessibilityLabel.security'; icon = IconSecurityOff; label = 'toolbar.security'; @@ -42,13 +39,24 @@ class SecurityDialogButton extends AbstractButton { tooltip = 'toolbar.security'; /** - * Handles clicking / pressing the button, and opens / closes the appropriate dialog. + * Helper function to be implemented by subclasses, which should be used + * to handle the security button being clicked / pressed. + * + * @protected + * @returns {void} + */ + _handleClickSecurityButton() { + // To be implemented by subclass. + } + + /** + * Handles clicking / pressing the button. * * @private * @returns {void} */ _handleClick() { - const { _locked, dispatch, handleClick } = this.props; + const { _locked, handleClick } = this.props; if (handleClick) { handleClick(); @@ -57,7 +65,7 @@ class SecurityDialogButton extends AbstractButton { } sendAnalytics(createToolbarEvent('toggle.security', { enable: !_locked })); - dispatch(toggleSecurityDialog()); + this._handleClickSecurityButton(); } /** @@ -77,7 +85,7 @@ class SecurityDialogButton extends AbstractButton { * @param {Object} state - The redux store/state. * @returns {Props} */ -function mapStateToProps(state: Object) { +export function _mapStateToProps(state: Object) { const { conference } = state['features/base/conference']; const { hideLobbyButton } = state['features/base/config']; const { locked } = state['features/base/conference']; @@ -93,5 +101,3 @@ function mapStateToProps(state: Object) { visible: enabledFlag || (enabledLobbyModeFlag || enabledMeetingPassFlag) }; } - -export default translate(connect(mapStateToProps)(SecurityDialogButton)); diff --git a/react/features/security/components/security-dialog/native/SecurityDialog.js b/react/features/security/components/security-dialog/native/SecurityDialog.js index d72c84f01b..badc1d8c56 100644 --- a/react/features/security/components/security-dialog/native/SecurityDialog.js +++ b/react/features/security/components/security-dialog/native/SecurityDialog.js @@ -1,35 +1,39 @@ // @flow +import Clipboard from '@react-native-community/clipboard'; import React, { PureComponent } from 'react'; import { - KeyboardAvoidingView, - Platform, Text, TextInput, View } from 'react-native'; -import { connect } from 'react-redux'; +import { TouchableRipple } from 'react-native-paper'; import type { Dispatch } from 'redux'; import { ColorSchemeRegistry } from '../../../../base/color-scheme'; -import { - FIELD_UNDERLINE, - CustomSubmitDialog -} from '../../../../base/dialog'; +import { FIELD_UNDERLINE } from '../../../../base/dialog'; import { getFeatureFlag, MEETING_PASSWORD_ENABLED } from '../../../../base/flags'; import { translate } from '../../../../base/i18n'; +import { IconClose } from '../../../../base/icons'; +import JitsiScreen from '../../../../base/modal/components/JitsiScreen'; import { isLocalParticipantModerator } from '../../../../base/participants'; +import { connect } from '../../../../base/redux'; import { StyleType } from '../../../../base/styles'; +import BaseTheme from '../../../../base/ui/components/BaseTheme'; import { isInBreakoutRoom } from '../../../../breakout-rooms/functions'; +import { goBack } from '../../../../conference/components/native/ConferenceNavigationContainerRef'; +import HeaderNavigationButton + from '../../../../conference/components/native/HeaderNavigationButton'; import { toggleLobbyMode } from '../../../../lobby/actions.any'; import LobbyModeSwitch from '../../../../lobby/components/native/LobbyModeSwitch'; -import { LOCKED_LOCALLY } from '../../../../room-lock'; +import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../../../room-lock'; import { endRoomLockRequest, unlockRoom } from '../../../../room-lock/actions'; -import RoomLockSwitch from '../../../../room-lock/components/RoomLockSwitch'; + +import styles from './styles'; /** * The style of the {@link TextInput} rendered by {@code SecurityDialog}. As it @@ -93,20 +97,20 @@ type Props = { _passwordNumberOfDigits: number, /** - * Whether the room lock switch is available or not. + * Whether setting a room password is available or not. */ - _roomLockSwitchVisible: boolean, - - /** - * The color-schemed stylesheet of the security dialog feature. - */ - _securityDialogStyles: StyleType, + _roomPasswordControls: boolean, /** * Redux store dispatch function. */ dispatch: Dispatch, + /** + * Default prop for navigation between screen components(React Navigation). + */ + navigation: Object, + /** * Invoked to obtain translated strings. */ @@ -150,9 +154,31 @@ class SecurityDialog extends PureComponent { }; this._onChangeText = this._onChangeText.bind(this); + this._onCancel = this._onCancel.bind(this); + this._onCopy = this._onCopy.bind(this); this._onSubmit = this._onSubmit.bind(this); this._onToggleLobbyMode = this._onToggleLobbyMode.bind(this); - this._onToggleRoomLock = this._onToggleRoomLock.bind(this); + this._onAddPassword = this._onAddPassword.bind(this); + } + + /** + * Implements React's {@link Component#componentDidMount()}. Invoked + * immediately after this component is mounted. + * + * @inheritdoc + * @returns {void} + */ + componentDidMount() { + const { navigation } = this.props; + + navigation.setOptions({ + headerLeft: () => ( + + ) + }); } /** @@ -162,19 +188,10 @@ class SecurityDialog extends PureComponent { */ render() { return ( - - - { this._renderLobbyMode() } - { this._renderRoomLock() } - - + + { this._renderLobbyMode() } + { this._renderSetRoomPassword() } + ); } @@ -188,7 +205,6 @@ class SecurityDialog extends PureComponent { const { _lobbyEnabled, _lobbyModeSwitchVisible, - _securityDialogStyles, t } = this.props; @@ -197,55 +213,151 @@ class SecurityDialog extends PureComponent { } return ( - - - { t('lobby.dialogTitle') } - - - { t('lobby.enableDialogText') } - - + + + + { t('lobby.enableDialogText') } + + + + { t('lobby.toggleLabel') } + + + + ); } /** - * Renders room lock. + * Renders setting the password. * * @returns {ReactElement} * @private */ - _renderRoomLock() { + _renderSetRoomPassword() { const { _isModerator, _locked, _lockedConference, - _roomLockSwitchVisible, - _securityDialogStyles, + _password, + _roomPasswordControls, t } = this.props; const { showElement } = this.state; + let setPasswordControls; - if (!_roomLockSwitchVisible) { + if (!_roomPasswordControls) { return null; } + if (_locked && showElement) { + setPasswordControls = ( + <> + + + { t('dialog.Remove') } + + + { + _password + && + + { t('dialog.copy') } + + + } + + ); + } else if (!_lockedConference && showElement) { + setPasswordControls = ( + <> + + + { t('dialog.Cancel') } + + + + + { t('dialog.add') } + + + + ); + } else if (!_lockedConference && !showElement) { + setPasswordControls = ( + + + { t('info.addPassword') } + + + ); + } + + if (_locked === LOCKED_REMOTELY) { + if (_isModerator) { + setPasswordControls = ( + + + { t('passwordSetRemotely') } + + + + { t('dialog.Remove') } + + + + ); + } else { + setPasswordControls = ( + + + { t('passwordSetRemotely') } + + + + { t('info.addPassword') } + + + + ); + } + } + return ( - - - { t('dialog.lockRoom') } - - + + { t('security.about') } - - { this._renderRoomLockMessage() } + + + { this._setRoomPasswordMessage() } + + { setPasswordControls } + ); } @@ -256,14 +368,13 @@ class SecurityDialog extends PureComponent { * @returns {ReactElement} * @private */ - _renderRoomLockMessage() { + _setRoomPasswordMessage() { let textInputProps = _TEXT_INPUT_PROPS; const { _isModerator, _locked, _password, _passwordNumberOfDigits, - _securityDialogStyles, t } = this.props; const { passwordInputValue, showElement } = this.state; @@ -284,9 +395,12 @@ class SecurityDialog extends PureComponent { if (typeof _locked === 'undefined') { return ( @@ -294,13 +408,14 @@ class SecurityDialog extends PureComponent { } else if (_locked) { if (_locked === LOCKED_LOCALLY && typeof _password !== 'undefined') { return ( - + + + { t('info.password') } + + + { passwordInputValue } + + ); } } @@ -325,28 +440,19 @@ class SecurityDialog extends PureComponent { } } - _onToggleRoomLock: () => void; + _onAddPassword: () => void; /** - * Callback to be invoked when room lock button is pressed. + * Callback to be invoked when add password button is pressed. * * @returns {void} */ - _onToggleRoomLock() { - const { _isModerator, _locked, dispatch } = this.props; + _onAddPassword() { const { showElement } = this.state; this.setState({ showElement: !showElement }); - - if (_locked && _isModerator) { - dispatch(unlockRoom()); - - this.setState({ - showElement: false - }); - } } /** @@ -389,14 +495,41 @@ class SecurityDialog extends PureComponent { }); } - _onSubmit: () => boolean; + _onCancel: () => void; + + /** + * Cancels value typed in text input. + * + * @returns {void} + */ + _onCancel() { + this.setState({ + passwordInputValue: '', + showElement: false + }); + + this.props.dispatch(unlockRoom()); + } + + _onCopy: () => void; + + /** + * Copies room password. + * + * @returns {void} + */ + _onCopy() { + const { passwordInputValue } = this.state; + + Clipboard.setString(passwordInputValue); + } + + _onSubmit: () => void; /** * Submits value typed in text input. * - * @returns {boolean} False because we do not want to hide this - * dialog/prompt as the hiding will be handled inside endRoomLockRequest - * after setting the password is resolved. + * @returns {void} */ _onSubmit() { const { @@ -406,8 +539,6 @@ class SecurityDialog extends PureComponent { const { passwordInputValue } = this.state; dispatch(endRoomLockRequest(_conference, passwordInputValue)); - - return false; } } @@ -436,8 +567,7 @@ function _mapStateToProps(state: Object): Object { _lockedConference: Boolean(conference && locked), _password: password, _passwordNumberOfDigits: roomPasswordNumberOfDigits, - _roomLockSwitchVisible: visible, - _securityDialogStyles: ColorSchemeRegistry.get(state, 'SecurityDialog') + _roomPasswordControls: visible }; } diff --git a/react/features/security/components/security-dialog/native/SecurityDialogButton.js b/react/features/security/components/security-dialog/native/SecurityDialogButton.js new file mode 100644 index 0000000000..20ad240f63 --- /dev/null +++ b/react/features/security/components/security-dialog/native/SecurityDialogButton.js @@ -0,0 +1,30 @@ +// @flow + +import { translate } from '../../../../base/i18n'; +import { connect } from '../../../../base/redux'; +import { navigate } from '../../../../conference/components/native/ConferenceNavigationContainerRef'; +import { screen } from '../../../../conference/components/native/routes'; +import AbstractSecurityDialogButton, { + _mapStateToProps as _abstractMapStateToProps, + type Props as AbstractSecurityDialogButtonProps +} from '../AbstractSecurityDialogButton'; + +type Props = AbstractSecurityDialogButtonProps; + +/** + * Implements an {@link AbstractSecurityDialogButton} to open the security screen. + */ +class SecurityDialogButton extends AbstractSecurityDialogButton { + + /** + * Opens / closes the security screen. + * + * @private + * @returns {void} + */ + _handleClickSecurityButton() { + navigate(screen.conference.security); + } +} + +export default translate(connect(_abstractMapStateToProps)(SecurityDialogButton)); diff --git a/react/features/security/components/security-dialog/native/styles.js b/react/features/security/components/security-dialog/native/styles.js new file mode 100644 index 0000000000..f09a45b820 --- /dev/null +++ b/react/features/security/components/security-dialog/native/styles.js @@ -0,0 +1,98 @@ +// @flow + +import BaseTheme from '../../../../base/ui/components/BaseTheme.native'; + +/** + * The styles of the feature security. + */ +export default { + + securityDialogContainer: { + flex: 1, + marginTop: BaseTheme.spacing[4] + }, + + headerCloseButton: { + marginLeft: 12 + }, + + lobbyModeContainer: { + borderBottomColor: BaseTheme.palette.border01, + borderBottomWidth: 1 + }, + + lobbyModeContent: { + marginHorizontal: BaseTheme.spacing[3], + marginBottom: BaseTheme.spacing[4] + }, + + lobbyModeLabel: { + fontWeight: 'bold', + marginTop: BaseTheme.spacing[2] + }, + + lobbyModeSection: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + marginTop: BaseTheme.spacing[1] + }, + + passwordContainer: { + marginHorizontal: BaseTheme.spacing[3], + marginTop: BaseTheme.spacing[4] + }, + + passwordContainerControls: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between' + }, + + savedPasswordContainer: { + flexDirection: 'row', + marginTop: 20, + width: 208 + }, + + savedPasswordLabel: { + fontWeight: 'bold' + }, + + savedPassword: { + color: BaseTheme.palette.text06 + }, + + passwordInput: { + borderColor: BaseTheme.palette.action03Active, + borderRadius: BaseTheme.spacing[1], + borderWidth: 2, + height: BaseTheme.spacing[6], + marginTop: BaseTheme.spacing[2], + paddingLeft: BaseTheme.spacing[1], + width: 208 + }, + + passwordSetupButton: { + ...BaseTheme.typography.heading7, + color: BaseTheme.palette.screen01Header, + marginTop: BaseTheme.spacing[4], + textTransform: 'uppercase' + }, + + passwordSetRemotelyContainer: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between' + }, + + passwordSetRemotelyText: { + color: BaseTheme.palette.text06, + marginTop: 22 + }, + + passwordSetRemotelyTextDisabled: { + color: BaseTheme.palette.text03, + marginTop: 22 + } +}; diff --git a/react/features/security/components/security-dialog/web/SecurityDialog.js b/react/features/security/components/security-dialog/web/SecurityDialog.js index c3c15ccae8..347a5df048 100644 --- a/react/features/security/components/security-dialog/web/SecurityDialog.js +++ b/react/features/security/components/security-dialog/web/SecurityDialog.js @@ -77,7 +77,7 @@ function SecurityDialog({
diff --git a/react/features/security/components/security-dialog/web/SecurityDialogButton.js b/react/features/security/components/security-dialog/web/SecurityDialogButton.js new file mode 100644 index 0000000000..56cbf20a8d --- /dev/null +++ b/react/features/security/components/security-dialog/web/SecurityDialogButton.js @@ -0,0 +1,31 @@ +// @flow + +import { translate } from '../../../../base/i18n'; +import { connect } from '../../../../base/redux'; +import { toggleSecurityDialog } from '../../../actions'; +import AbstractSecurityDialogButton, { + _mapStateToProps as _abstractMapStateToProps, + type Props as AbstractSecurityDialogButtonProps +} from '../AbstractSecurityDialogButton'; + +type Props = AbstractSecurityDialogButtonProps; + +/** + * Implements an {@link AbstractSecurityDialogButton} to open the security dialog. + */ +class SecurityDialogButton extends AbstractSecurityDialogButton { + + /** + * Opens / closes the security dialog. + * + * @private + * @returns {void} + */ + _handleClickSecurityButton() { + const { dispatch } = this.props; + + dispatch(toggleSecurityDialog()); + } +} + +export default translate(connect(_abstractMapStateToProps)(SecurityDialogButton)); diff --git a/react/features/speaker-stats/components/native/SpeakerStats.js b/react/features/speaker-stats/components/native/SpeakerStats.js index c7c8f9a02c..b12b66e300 100644 --- a/react/features/speaker-stats/components/native/SpeakerStats.js +++ b/react/features/speaker-stats/components/native/SpeakerStats.js @@ -15,7 +15,6 @@ import style from './styles'; */ const SpeakerStats = () => ( diff --git a/react/features/toolbox/components/native/OverflowMenu.js b/react/features/toolbox/components/native/OverflowMenu.js index 3eb30022ab..048811dded 100644 --- a/react/features/toolbox/components/native/OverflowMenu.js +++ b/react/features/toolbox/components/native/OverflowMenu.js @@ -13,7 +13,8 @@ import { ParticipantsPaneButton } from '../../../participants-pane/components/na import { ReactionMenu } from '../../../reactions/components'; import { isReactionsEnabled } from '../../../reactions/functions.any'; import { LiveStreamButton, RecordButton } from '../../../recording'; -import SecurityDialogButton from '../../../security/components/security-dialog/SecurityDialogButton'; +import SecurityDialogButton + from '../../../security/components/security-dialog/native/SecurityDialogButton'; import { SharedVideoButton } from '../../../shared-video/components'; import SpeakerStatsButton from '../../../speaker-stats/components/native/SpeakerStatsButton'; import { ClosedCaptionButton } from '../../../subtitles'; diff --git a/react/features/toolbox/components/web/Toolbox.js b/react/features/toolbox/components/web/Toolbox.js index 96925a9db3..345b67cd32 100644 --- a/react/features/toolbox/components/web/Toolbox.js +++ b/react/features/toolbox/components/web/Toolbox.js @@ -54,7 +54,7 @@ import { ShareAudioButton, startScreenShareFlow } from '../../../screen-share/'; -import SecurityDialogButton from '../../../security/components/security-dialog/SecurityDialogButton'; +import SecurityDialogButton from '../../../security/components/security-dialog/web/SecurityDialogButton'; import { SettingsButton } from '../../../settings'; import { SharedVideoButton } from '../../../shared-video/components'; import { SpeakerStatsButton } from '../../../speaker-stats/components/web';