feat(security) created SecurityOptions React Navigation screen (#10509)

* feat(security) Security Options screen
This commit is contained in:
Calinteodor 2021-12-10 18:23:27 +02:00 committed by GitHub
parent 75e6dd389f
commit bf3cc65f4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 483 additions and 236 deletions

View File

@ -68,6 +68,7 @@ dependencies {
implementation project(':react-native-async-storage') implementation project(':react-native-async-storage')
implementation project(':react-native-background-timer') implementation project(':react-native-background-timer')
implementation project(':react-native-calendar-events') implementation project(':react-native-calendar-events')
implementation project(':react-native-community_clipboard')
implementation project(':react-native-community_netinfo') implementation project(':react-native-community_netinfo')
implementation project(':react-native-default-preference') implementation project(':react-native-default-preference')
implementation project(':react-native-gesture-handler') implementation project(':react-native-gesture-handler')

View File

@ -180,6 +180,7 @@ class ReactInstanceManagerHolder {
new com.calendarevents.CalendarEventsPackage(), new com.calendarevents.CalendarEventsPackage(),
new com.corbt.keepawake.KCKeepAwakePackage(), new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(), new com.facebook.react.shell.MainReactPackage(),
new com.reactnativecommunity.clipboard.ClipboardPackage(),
new com.reactnativecommunity.netinfo.NetInfoPackage(), new com.reactnativecommunity.netinfo.NetInfoPackage(),
new com.oblador.performance.PerformancePackage(), new com.oblador.performance.PerformancePackage(),
new com.reactnativecommunity.slider.ReactSliderPackage(), new com.reactnativecommunity.slider.ReactSliderPackage(),

View File

@ -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') project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
include ':react-native-calendar-events' include ':react-native-calendar-events'
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android') 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' include ':react-native-community_netinfo'
project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android') project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
include ':react-native-default-preference' include ':react-native-default-preference'

View File

@ -61,23 +61,24 @@ target 'JitsiMeetSDK' do
pod 'react-native-keep-awake', :path => '../node_modules/react-native-keep-awake' 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-netinfo', :path => '../node_modules/@react-native-community/netinfo'
pod 'react-native-performance', :path => '../node_modules/react-native-performance/ios' 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-slider', :path => '../node_modules/@react-native-community/slider'
pod 'react-native-splash-screen', :path => '../node_modules/react-native-splash-screen' 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-video', :path => '../node_modules/react-native-video/react-native-video.podspec'
pod 'react-native-webview', :path => '../node_modules/react-native-webview' pod 'react-native-webview', :path => '../node_modules/react-native-webview'
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc' pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
pod 'RNCAsyncStorage', :path => '../node_modules/@react-native-async-storage/async-storage' 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 '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 '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 'RNSound', :path => '../node_modules/react-native-sound'
pod 'RNSVG', :path => '../node_modules/react-native-svg' pod 'RNSVG', :path => '../node_modules/react-native-svg'
pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity' 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 # Native pod dependencies
# #

View File

@ -363,6 +363,8 @@ PODS:
- ReactCommon/turbomodule/core (= 0.61.5-jitsi.2) - ReactCommon/turbomodule/core (= 0.61.5-jitsi.2)
- RNCAsyncStorage (1.15.5): - RNCAsyncStorage (1.15.5):
- React-Core - React-Core
- RNCClipboard (1.5.1):
- React-Core
- RNCMaskedView (0.2.6): - RNCMaskedView (0.2.6):
- React-Core - React-Core
- RNDefaultPreference (1.4.2): - RNDefaultPreference (1.4.2):
@ -435,6 +437,7 @@ DEPENDENCIES:
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- ReactCommon/turbomodule (from `../node_modules/react-native/ReactCommon`) - ReactCommon/turbomodule (from `../node_modules/react-native/ReactCommon`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "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`)" - "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)"
- RNDefaultPreference (from `../node_modules/react-native-default-preference`) - RNDefaultPreference (from `../node_modules/react-native-default-preference`)
- RNDeviceInfo (from `../node_modules/react-native-device-info`) - RNDeviceInfo (from `../node_modules/react-native-device-info`)
@ -547,6 +550,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon" :path: "../node_modules/react-native/ReactCommon"
RNCAsyncStorage: RNCAsyncStorage:
:path: "../node_modules/@react-native-async-storage/async-storage" :path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
:path: "../node_modules/@react-native-community/clipboard"
RNCMaskedView: RNCMaskedView:
:path: "../node_modules/@react-native-masked-view/masked-view" :path: "../node_modules/@react-native-masked-view/masked-view"
RNDefaultPreference: RNDefaultPreference:
@ -629,6 +634,7 @@ SPEC CHECKSUMS:
React-RCTVibration: c1041024893fdfdb8371e7c720c437751b711676 React-RCTVibration: c1041024893fdfdb8371e7c720c437751b711676
ReactCommon: 18014e1d98dbeb9141e935cfe35fc93bd511ffb6 ReactCommon: 18014e1d98dbeb9141e935cfe35fc93bd511ffb6
RNCAsyncStorage: 56a3355a10b5d660c48c6e37325ac85ebfd09885 RNCAsyncStorage: 56a3355a10b5d660c48c6e37325ac85ebfd09885
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNCMaskedView: c298b644a10c0c142055b3ae24d83879ecb13ccd RNCMaskedView: c298b644a10c0c142055b3ae24d83879ecb13ccd
RNDefaultPreference: 1f8133ec0bc0f9453cdada578564ba1ef551fb44 RNDefaultPreference: 1f8133ec0bc0f9453cdada578564ba1ef551fb44
RNDeviceInfo: 87d2d175c760f6bcf58acd036f887e8b2392802c RNDeviceInfo: 87d2d175c760f6bcf58acd036f887e8b2392802c
@ -641,6 +647,6 @@ SPEC CHECKSUMS:
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3 RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
PODFILE CHECKSUM: 836d4804218c0608e1326471ec83fe31cfa9c86d PODFILE CHECKSUM: 0cfc1f35e2872ceb0a86252e14e226bd489a2602
COCOAPODS: 1.11.2 COCOAPODS: 1.11.2

View File

@ -824,8 +824,8 @@
"security": { "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.", "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.", "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.", "header": "Security Options",
"securityOptions": "Security options" "insecureRoomNameWarning": "The room name is unsafe. Unwanted participants may join your conference. Consider securing your meeting using the security button."
}, },
"settings": { "settings": {
"calendar": { "calendar": {
@ -893,20 +893,20 @@
}, },
"speaker": "Speaker", "speaker": "Speaker",
"speakerStats": { "speakerStats": {
"search": "Search", "angry": "Angry",
"disgusted": "Disgusted",
"fearful": "Fearful",
"happy": "Happy",
"hours": "{{count}}h", "hours": "{{count}}h",
"minutes": "{{count}}m", "minutes": "{{count}}m",
"name": "Name", "name": "Name",
"seconds": "{{count}}s",
"speakerStats": "Speaker Stats",
"speakerTime": "Speaker Time",
"happy": "Happy",
"neutral": "Neutral", "neutral": "Neutral",
"sad": "Sad", "sad": "Sad",
"surprised": "Surprised", "search": "Search",
"angry": "Angry", "seconds": "{{count}}s",
"fearful": "Fearful", "speakerTime": "Speaker Time",
"disgusted": "Disgusted" "speakerStats": "Speaker Stats",
"surprised": "Surprised"
}, },
"startupoverlay": { "startupoverlay": {
"policyText": " ", "policyText": " ",

15
package-lock.json generated
View File

@ -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", "@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", "@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-async-storage/async-storage": "1.15.5", "@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/google-signin": "3.0.1",
"@react-native-community/netinfo": "4.1.5", "@react-native-community/netinfo": "4.1.5",
"@react-native-community/slider": "3.0.3", "@react-native-community/slider": "3.0.3",
@ -4097,6 +4098,15 @@
"node": ">=8" "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": { "node_modules/@react-native-community/google-signin": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@react-native-community/google-signin/-/google-signin-3.0.1.tgz", "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", "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-3.0.0.tgz",
"integrity": "sha512-ng6Tm537E/M42GjE4TRUxQyL8sRfClcL7bQWblOCoxPZzJ2J3bdALsjeG3vDnVCIfI/R0AeFalN9KjMt0+Z/Zg==" "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": { "@react-native-community/google-signin": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@react-native-community/google-signin/-/google-signin-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@react-native-community/google-signin/-/google-signin-3.0.1.tgz",

View File

@ -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", "@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", "@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-async-storage/async-storage": "1.15.5", "@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/google-signin": "3.0.1",
"@react-native-community/netinfo": "4.1.5", "@react-native-community/netinfo": "4.1.5",
"@react-native-community/slider": "3.0.3", "@react-native-community/slider": "3.0.3",

View File

@ -19,6 +19,7 @@ export const colors = {
primary09: '#CCDDF9', primary09: '#CCDDF9',
primary10: '#17A0DB', primary10: '#17A0DB',
primary11: '#1081B2', primary11: '#1081B2',
primary12: '#B8C7E0',
surface00: '#111111', surface00: '#111111',
surface01: '#040404', surface01: '#040404',
@ -158,6 +159,9 @@ export const colorMap = {
// Text for drawer menu displayed name // Text for drawer menu displayed name
text05: 'surface06', text05: 'surface06',
// Text for saved input values
text06: 'surface03',
// error messages // error messages
textError: 'error06', textError: 'error06',
@ -226,6 +230,8 @@ export const colorMap = {
// Line separators // Line separators
border03: 'surface04', border03: 'surface04',
border04: 'primary12',
// Color for error border & message // Color for error border & message
borderError: 'error06', borderError: 'error06',

View File

@ -17,12 +17,12 @@ import {
setVideoMuted setVideoMuted
} from '../base/media'; } from '../base/media';
import { getRemoteParticipants } from '../base/participants'; import { getRemoteParticipants } from '../base/participants';
import { createDesiredLocalTracks } from '../base/tracks/actions';
import { import {
getLocalTracks, getLocalTracks,
isLocalCameraTrackMuted, isLocalCameraTrackMuted,
isLocalTrackMuted isLocalTrackMuted
} from '../base/tracks'; } from '../base/tracks';
import { createDesiredLocalTracks } from '../base/tracks/actions';
import { import {
NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TIMEOUT_TYPE,
clearNotifications, clearNotifications,

View File

@ -13,6 +13,8 @@ import AddPeopleDialog
from '../../../invite/components/add-people-dialog/native/AddPeopleDialog'; from '../../../invite/components/add-people-dialog/native/AddPeopleDialog';
import LobbyScreen from '../../../lobby/components/native/LobbyScreen'; import LobbyScreen from '../../../lobby/components/native/LobbyScreen';
import { ParticipantsPane } from '../../../participants-pane/components/native'; import { ParticipantsPane } from '../../../participants-pane/components/native';
import SecurityDialog
from '../../../security/components/security-dialog/native/SecurityDialog';
import SpeakerStats import SpeakerStats
from '../../../speaker-stats/components/native/SpeakerStats'; from '../../../speaker-stats/components/native/SpeakerStats';
import { getDisablePolls } from '../../functions'; import { getDisablePolls } from '../../functions';
@ -28,6 +30,7 @@ import {
lobbyScreenOptions, lobbyScreenOptions,
navigationContainerTheme, navigationContainerTheme,
participantsScreenOptions, participantsScreenOptions,
securityScreenOptions,
sharedDocumentScreenOptions, sharedDocumentScreenOptions,
speakerStatsScreenOptions speakerStatsScreenOptions
} from './ConferenceNavigatorScreenOptions'; } from './ConferenceNavigatorScreenOptions';
@ -80,11 +83,19 @@ const ConferenceNavigationContainer = () => {
...participantsScreenOptions, ...participantsScreenOptions,
title: t('participantsPane.header') title: t('participantsPane.header')
}} /> }} />
<ConferenceStack.Screen
component = { SecurityDialog }
name = { screen.conference.security }
options = {{
...securityScreenOptions,
title: t('security.header')
}} />
<ConferenceStack.Screen <ConferenceStack.Screen
component = { SpeakerStats } component = { SpeakerStats }
name = { screen.conference.speakerStats } name = { screen.conference.speakerStats }
options = {{ options = {{
...speakerStatsScreenOptions ...speakerStatsScreenOptions,
title: t('speakerStats.speakerStats')
}} /> }} />
<ConferenceStack.Screen <ConferenceStack.Screen
component = { LobbyScreen } component = { LobbyScreen }

View File

@ -222,6 +222,13 @@ export const speakerStatsScreenOptions = {
...presentationScreenOptions ...presentationScreenOptions
}; };
/**
* Screen options for security options modal.
*/
export const securityScreenOptions = {
...presentationScreenOptions
};
/** /**
* Screen options for shared document. * Screen options for shared document.
*/ */

View File

@ -21,6 +21,7 @@ export const screen = {
polls: 'Polls' polls: 'Polls'
} }
}, },
security: 'Security Options',
speakerStats: 'Speaker Stats', speakerStats: 'Speaker Stats',
participants: 'Participants', participants: 'Participants',
invite: 'Invite', invite: 'Invite',

View File

@ -147,8 +147,8 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
<TouchableRipple <TouchableRipple
disabled = { this._isAddDisabled() } disabled = { this._isAddDisabled() }
rippleColor = { palette.screen01Header } > rippleColor = { palette.screen01Header } >
<Text <Text style = { styles.headerSendInvite }>
style = { styles.headerSendInvite }>{ t('inviteDialog.send') } { t('inviteDialog.send') }
</Text> </Text>
</TouchableRipple> </TouchableRipple>
) )
@ -171,9 +171,8 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
disabled = { this._isAddDisabled() } disabled = { this._isAddDisabled() }
onPress = { this._onInvite } onPress = { this._onInvite }
rippleColor = { palette.screen01Header } > rippleColor = { palette.screen01Header } >
<Text <Text style = { styles.headerSendInvite }>
/* eslint-disable-next-line react-native/no-inline-styles */ { t('inviteDialog.send') }
style = { styles.headerSendInvite }>{ t('inviteDialog.send') }
</Text> </Text>
</TouchableRipple> </TouchableRipple>
) )

View File

@ -1,12 +1,12 @@
// @flow // @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_THUMB_COLOR = BaseTheme.palette.action04;
export const ENABLED_TRACK_COLOR = ColorPalette.blue; export const ENABLED_TRACK_COLOR = BaseTheme.palette.screen01Header;
export const DISABLED_THUMB_COLOR = ColorPalette.darkGrey; export const DISABLED_THUMB_COLOR = BaseTheme.palette.icon04;
export default { export default {
button: { button: {
@ -61,7 +61,7 @@ export default {
}, },
fieldError: { fieldError: {
color: ColorPalette.warning, color: BaseTheme.palette.warning07,
fontSize: 10 fontSize: 10
}, },
@ -165,7 +165,7 @@ export default {
lobbySwitchContainer: { lobbySwitchContainer: {
flexDirection: 'column', flexDirection: 'column',
marginTop: 16 marginTop: BaseTheme.spacing[2]
}, },
lobbySwitchIcon: { lobbySwitchIcon: {

View File

@ -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<any>}
*/
function RoomLockSwitch(
{
locked,
disabled,
onToggleRoomLock,
toggleRoomLock,
t
}: Props) {
return (
<View style = { styles.roomLockSwitchContainer }>
<Text>
{
locked === LOCKED_REMOTELY
&& t('passwordSetRemotely')
}
</Text>
<Switch
disabled = { disabled }
onValueChange = { onToggleRoomLock }
thumbColor = {
toggleRoomLock
? ENABLED_THUMB_COLOR
: DISABLED_THUMB_COLOR
}
trackColor = {{ true: ENABLED_TRACK_COLOR }}
value = { toggleRoomLock } />
</View>
);
}
export default translate(connect()(RoomLockSwitch));

View File

@ -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
}
};

View File

@ -9,15 +9,11 @@ import {
MEETING_PASSWORD_ENABLED, MEETING_PASSWORD_ENABLED,
SECURITY_OPTIONS_ENABLED SECURITY_OPTIONS_ENABLED
} from '../../../base/flags'; } from '../../../base/flags';
import { translate } from '../../../base/i18n';
import { IconSecurityOff, IconSecurityOn } from '../../../base/icons'; import { IconSecurityOff, IconSecurityOn } from '../../../base/icons';
import { isLocalParticipantModerator } from '../../../base/participants'; import { isLocalParticipantModerator } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { toggleSecurityDialog } from '../../actions';
export type Props = AbstractButtonProps & {
type Props = AbstractButtonProps & {
/** /**
* Whether the shared document is being edited or not. * 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<Props, *> { export default class AbstractSecurityDialogButton<P: Props, S:*>
extends AbstractButton<P, S> {
accessibilityLabel = 'toolbar.accessibilityLabel.security'; accessibilityLabel = 'toolbar.accessibilityLabel.security';
icon = IconSecurityOff; icon = IconSecurityOff;
label = 'toolbar.security'; label = 'toolbar.security';
@ -42,13 +39,24 @@ class SecurityDialogButton extends AbstractButton<Props, *> {
tooltip = 'toolbar.security'; 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 * @private
* @returns {void} * @returns {void}
*/ */
_handleClick() { _handleClick() {
const { _locked, dispatch, handleClick } = this.props; const { _locked, handleClick } = this.props;
if (handleClick) { if (handleClick) {
handleClick(); handleClick();
@ -57,7 +65,7 @@ class SecurityDialogButton extends AbstractButton<Props, *> {
} }
sendAnalytics(createToolbarEvent('toggle.security', { enable: !_locked })); sendAnalytics(createToolbarEvent('toggle.security', { enable: !_locked }));
dispatch(toggleSecurityDialog()); this._handleClickSecurityButton();
} }
/** /**
@ -77,7 +85,7 @@ class SecurityDialogButton extends AbstractButton<Props, *> {
* @param {Object} state - The redux store/state. * @param {Object} state - The redux store/state.
* @returns {Props} * @returns {Props}
*/ */
function mapStateToProps(state: Object) { export function _mapStateToProps(state: Object) {
const { conference } = state['features/base/conference']; const { conference } = state['features/base/conference'];
const { hideLobbyButton } = state['features/base/config']; const { hideLobbyButton } = state['features/base/config'];
const { locked } = state['features/base/conference']; const { locked } = state['features/base/conference'];
@ -93,5 +101,3 @@ function mapStateToProps(state: Object) {
visible: enabledFlag || (enabledLobbyModeFlag || enabledMeetingPassFlag) visible: enabledFlag || (enabledLobbyModeFlag || enabledMeetingPassFlag)
}; };
} }
export default translate(connect(mapStateToProps)(SecurityDialogButton));

View File

@ -1,35 +1,39 @@
// @flow // @flow
import Clipboard from '@react-native-community/clipboard';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { import {
KeyboardAvoidingView,
Platform,
Text, Text,
TextInput, TextInput,
View View
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { TouchableRipple } from 'react-native-paper';
import type { Dispatch } from 'redux'; import type { Dispatch } from 'redux';
import { ColorSchemeRegistry } from '../../../../base/color-scheme'; import { ColorSchemeRegistry } from '../../../../base/color-scheme';
import { import { FIELD_UNDERLINE } from '../../../../base/dialog';
FIELD_UNDERLINE,
CustomSubmitDialog
} from '../../../../base/dialog';
import { getFeatureFlag, MEETING_PASSWORD_ENABLED } from '../../../../base/flags'; import { getFeatureFlag, MEETING_PASSWORD_ENABLED } from '../../../../base/flags';
import { translate } from '../../../../base/i18n'; import { translate } from '../../../../base/i18n';
import { IconClose } from '../../../../base/icons';
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
import { isLocalParticipantModerator } from '../../../../base/participants'; import { isLocalParticipantModerator } from '../../../../base/participants';
import { connect } from '../../../../base/redux';
import { StyleType } from '../../../../base/styles'; import { StyleType } from '../../../../base/styles';
import BaseTheme from '../../../../base/ui/components/BaseTheme';
import { isInBreakoutRoom } from '../../../../breakout-rooms/functions'; 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 { toggleLobbyMode } from '../../../../lobby/actions.any';
import LobbyModeSwitch import LobbyModeSwitch
from '../../../../lobby/components/native/LobbyModeSwitch'; from '../../../../lobby/components/native/LobbyModeSwitch';
import { LOCKED_LOCALLY } from '../../../../room-lock'; import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../../../room-lock';
import { import {
endRoomLockRequest, endRoomLockRequest,
unlockRoom unlockRoom
} from '../../../../room-lock/actions'; } 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 * The style of the {@link TextInput} rendered by {@code SecurityDialog}. As it
@ -93,20 +97,20 @@ type Props = {
_passwordNumberOfDigits: number, _passwordNumberOfDigits: number,
/** /**
* Whether the room lock switch is available or not. * Whether setting a room password is available or not.
*/ */
_roomLockSwitchVisible: boolean, _roomPasswordControls: boolean,
/**
* The color-schemed stylesheet of the security dialog feature.
*/
_securityDialogStyles: StyleType,
/** /**
* Redux store dispatch function. * Redux store dispatch function.
*/ */
dispatch: Dispatch<any>, dispatch: Dispatch<any>,
/**
* Default prop for navigation between screen components(React Navigation).
*/
navigation: Object,
/** /**
* Invoked to obtain translated strings. * Invoked to obtain translated strings.
*/ */
@ -150,9 +154,31 @@ class SecurityDialog extends PureComponent<Props, State> {
}; };
this._onChangeText = this._onChangeText.bind(this); 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._onSubmit = this._onSubmit.bind(this);
this._onToggleLobbyMode = this._onToggleLobbyMode.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: () => (
<HeaderNavigationButton
onPress = { goBack }
src = { IconClose }
style = { styles.headerCloseButton } />
)
});
} }
/** /**
@ -162,19 +188,10 @@ class SecurityDialog extends PureComponent<Props, State> {
*/ */
render() { render() {
return ( return (
<CustomSubmitDialog <JitsiScreen style = { styles.securityDialogContainer }>
onSubmit = { this._onSubmit }>
<KeyboardAvoidingView
behavior =
{
Platform.OS === 'ios'
? 'padding' : 'height'
}
enabled = { true }>
{ this._renderLobbyMode() } { this._renderLobbyMode() }
{ this._renderRoomLock() } { this._renderSetRoomPassword() }
</KeyboardAvoidingView> </JitsiScreen>
</CustomSubmitDialog>
); );
} }
@ -188,7 +205,6 @@ class SecurityDialog extends PureComponent<Props, State> {
const { const {
_lobbyEnabled, _lobbyEnabled,
_lobbyModeSwitchVisible, _lobbyModeSwitchVisible,
_securityDialogStyles,
t t
} = this.props; } = this.props;
@ -197,55 +213,151 @@ class SecurityDialog extends PureComponent<Props, State> {
} }
return ( return (
<View> <View style = { styles.lobbyModeContainer }>
<Text style = { _securityDialogStyles.title } > <View style = { styles.lobbyModeContent } >
{ t('lobby.dialogTitle') } <Text>
</Text>
<Text style = { _securityDialogStyles.text } >
{ t('lobby.enableDialogText') } { t('lobby.enableDialogText') }
</Text> </Text>
<View style = { styles.lobbyModeSection }>
<Text style = { styles.lobbyModeLabel } >
{ t('lobby.toggleLabel') }
</Text>
<LobbyModeSwitch <LobbyModeSwitch
lobbyEnabled = { _lobbyEnabled } lobbyEnabled = { _lobbyEnabled }
onToggleLobbyMode = { this._onToggleLobbyMode } /> onToggleLobbyMode = { this._onToggleLobbyMode } />
</View> </View>
</View>
</View>
); );
} }
/** /**
* Renders room lock. * Renders setting the password.
* *
* @returns {ReactElement} * @returns {ReactElement}
* @private * @private
*/ */
_renderRoomLock() { _renderSetRoomPassword() {
const { const {
_isModerator, _isModerator,
_locked, _locked,
_lockedConference, _lockedConference,
_roomLockSwitchVisible, _password,
_securityDialogStyles, _roomPasswordControls,
t t
} = this.props; } = this.props;
const { showElement } = this.state; const { showElement } = this.state;
let setPasswordControls;
if (!_roomLockSwitchVisible) { if (!_roomPasswordControls) {
return null; return null;
} }
return ( if (_locked && showElement) {
<View> setPasswordControls = (
<Text style = { _securityDialogStyles.title } > <>
{ t('dialog.lockRoom') } <TouchableRipple
onPress = { this._onCancel }
rippleColor = { BaseTheme.palette.field02 } >
<Text style = { styles.passwordSetupButton }>
{ t('dialog.Remove') }
</Text> </Text>
<Text style = { _securityDialogStyles.text } > </TouchableRipple>
{
_password
&& <TouchableRipple
onPress = { this._onCopy }
rippleColor = { BaseTheme.palette.field02 } >
<Text style = { styles.passwordSetupButton }>
{ t('dialog.copy') }
</Text>
</TouchableRipple>
}
</>
);
} else if (!_lockedConference && showElement) {
setPasswordControls = (
<>
<TouchableRipple
onPress = { this._onCancel }
rippleColor = { BaseTheme.palette.field02 } >
<Text style = { styles.passwordSetupButton }>
{ t('dialog.Cancel') }
</Text>
</TouchableRipple>
<TouchableRipple
onPress = { this._onSubmit }
rippleColor = { BaseTheme.palette.field02 } >
<Text style = { styles.passwordSetupButton }>
{ t('dialog.add') }
</Text>
</TouchableRipple>
</>
);
} else if (!_lockedConference && !showElement) {
setPasswordControls = (
<TouchableRipple
disabled = { !_isModerator }
onPress = { this._onAddPassword }
rippleColor = { BaseTheme.palette.field02 } >
<Text style = { styles.passwordSetupButton }>
{ t('info.addPassword') }
</Text>
</TouchableRipple>
);
}
if (_locked === LOCKED_REMOTELY) {
if (_isModerator) {
setPasswordControls = (
<View style = { styles.passwordSetRemotelyContainer }>
<Text style = { styles.passwordSetRemotelyText }>
{ t('passwordSetRemotely') }
</Text>
<TouchableRipple
onPress = { this._onCancel }
rippleColor = { BaseTheme.palette.field02 } >
<Text style = { styles.passwordSetupButton }>
{ t('dialog.Remove') }
</Text>
</TouchableRipple>
</View>
);
} else {
setPasswordControls = (
<View style = { styles.passwordSetRemotelyContainer }>
<Text style = { styles.passwordSetRemotelyTextDisabled }>
{ t('passwordSetRemotely') }
</Text>
<TouchableRipple
disabled = { !_isModerator }
onPress = { this._onAddPassword }
rippleColor = { BaseTheme.palette.field02 } >
<Text style = { styles.passwordSetupButton }>
{ t('info.addPassword') }
</Text>
</TouchableRipple>
</View>
);
}
}
return (
<View
style = { styles.passwordContainer } >
<Text>
{ t('security.about') } { t('security.about') }
</Text> </Text>
<RoomLockSwitch <View
disabled = { !_isModerator } style = {
locked = { _locked } _locked !== LOCKED_REMOTELY
onToggleRoomLock = { this._onToggleRoomLock } && styles.passwordContainerControls
toggleRoomLock = { showElement || _lockedConference } /> }>
{ this._renderRoomLockMessage() } <View>
{ this._setRoomPasswordMessage() }
</View>
{ setPasswordControls }
</View>
</View> </View>
); );
} }
@ -256,14 +368,13 @@ class SecurityDialog extends PureComponent<Props, State> {
* @returns {ReactElement} * @returns {ReactElement}
* @private * @private
*/ */
_renderRoomLockMessage() { _setRoomPasswordMessage() {
let textInputProps = _TEXT_INPUT_PROPS; let textInputProps = _TEXT_INPUT_PROPS;
const { const {
_isModerator, _isModerator,
_locked, _locked,
_password, _password,
_passwordNumberOfDigits, _passwordNumberOfDigits,
_securityDialogStyles,
t t
} = this.props; } = this.props;
const { passwordInputValue, showElement } = this.state; const { passwordInputValue, showElement } = this.state;
@ -284,9 +395,12 @@ class SecurityDialog extends PureComponent<Props, State> {
if (typeof _locked === 'undefined') { if (typeof _locked === 'undefined') {
return ( return (
<TextInput <TextInput
autoFocus = { true }
onChangeText = { this._onChangeText } onChangeText = { this._onChangeText }
placeholder = { t('lobby.passwordField') } placeholder = { t('lobby.passwordField') }
style = { _securityDialogStyles.field } placeholderTextColor = { BaseTheme.palette.text03 }
selectionColor = { BaseTheme.palette.action03Active }
style = { styles.passwordInput }
underlineColorAndroid = { FIELD_UNDERLINE } underlineColorAndroid = { FIELD_UNDERLINE }
value = { passwordInputValue } value = { passwordInputValue }
{ ...textInputProps } /> { ...textInputProps } />
@ -294,13 +408,14 @@ class SecurityDialog extends PureComponent<Props, State> {
} else if (_locked) { } else if (_locked) {
if (_locked === LOCKED_LOCALLY && typeof _password !== 'undefined') { if (_locked === LOCKED_LOCALLY && typeof _password !== 'undefined') {
return ( return (
<TextInput <View style = { styles.savedPasswordContainer }>
onChangeText = { this._onChangeText } <Text style = { styles.savedPasswordLabel }>
placeholder = { _password } { t('info.password') }
style = { _securityDialogStyles.field } </Text>
underlineColorAndroid = { FIELD_UNDERLINE } <Text style = { styles.savedPassword }>
value = { passwordInputValue } { passwordInputValue }
{ ...textInputProps } /> </Text>
</View>
); );
} }
} }
@ -325,28 +440,19 @@ class SecurityDialog extends PureComponent<Props, State> {
} }
} }
_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} * @returns {void}
*/ */
_onToggleRoomLock() { _onAddPassword() {
const { _isModerator, _locked, dispatch } = this.props;
const { showElement } = this.state; const { showElement } = this.state;
this.setState({ this.setState({
showElement: !showElement showElement: !showElement
}); });
if (_locked && _isModerator) {
dispatch(unlockRoom());
this.setState({
showElement: false
});
}
} }
/** /**
@ -389,14 +495,41 @@ class SecurityDialog extends PureComponent<Props, State> {
}); });
} }
_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. * Submits value typed in text input.
* *
* @returns {boolean} False because we do not want to hide this * @returns {void}
* dialog/prompt as the hiding will be handled inside endRoomLockRequest
* after setting the password is resolved.
*/ */
_onSubmit() { _onSubmit() {
const { const {
@ -406,8 +539,6 @@ class SecurityDialog extends PureComponent<Props, State> {
const { passwordInputValue } = this.state; const { passwordInputValue } = this.state;
dispatch(endRoomLockRequest(_conference, passwordInputValue)); dispatch(endRoomLockRequest(_conference, passwordInputValue));
return false;
} }
} }
@ -436,8 +567,7 @@ function _mapStateToProps(state: Object): Object {
_lockedConference: Boolean(conference && locked), _lockedConference: Boolean(conference && locked),
_password: password, _password: password,
_passwordNumberOfDigits: roomPasswordNumberOfDigits, _passwordNumberOfDigits: roomPasswordNumberOfDigits,
_roomLockSwitchVisible: visible, _roomPasswordControls: visible
_securityDialogStyles: ColorSchemeRegistry.get(state, 'SecurityDialog')
}; };
} }

View File

@ -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<P: Props, S:*> extends AbstractSecurityDialogButton<P, S> {
/**
* Opens / closes the security screen.
*
* @private
* @returns {void}
*/
_handleClickSecurityButton() {
navigate(screen.conference.security);
}
}
export default translate(connect(_abstractMapStateToProps)(SecurityDialogButton));

View File

@ -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
}
};

View File

@ -77,7 +77,7 @@ function SecurityDialog({
<Dialog <Dialog
hideCancelButton = { true } hideCancelButton = { true }
submitDisabled = { true } submitDisabled = { true }
titleKey = 'security.securityOptions' titleKey = 'security.header'
width = { 'small' }> width = { 'small' }>
<div className = 'security-dialog'> <div className = 'security-dialog'>
<LobbySection /> <LobbySection />

View File

@ -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<P: Props, S:*> extends AbstractSecurityDialogButton<P, S> {
/**
* Opens / closes the security dialog.
*
* @private
* @returns {void}
*/
_handleClickSecurityButton() {
const { dispatch } = this.props;
dispatch(toggleSecurityDialog());
}
}
export default translate(connect(_abstractMapStateToProps)(SecurityDialogButton));

View File

@ -15,7 +15,6 @@ import style from './styles';
*/ */
const SpeakerStats = () => ( const SpeakerStats = () => (
<JitsiScreen <JitsiScreen
hasTabNavigator = { false }
style = { style.speakerStatsContainer }> style = { style.speakerStatsContainer }>
<SpeakerStatsLabels /> <SpeakerStatsLabels />
<SpeakerStatsList /> <SpeakerStatsList />

View File

@ -13,7 +13,8 @@ import { ParticipantsPaneButton } from '../../../participants-pane/components/na
import { ReactionMenu } from '../../../reactions/components'; import { ReactionMenu } from '../../../reactions/components';
import { isReactionsEnabled } from '../../../reactions/functions.any'; import { isReactionsEnabled } from '../../../reactions/functions.any';
import { LiveStreamButton, RecordButton } from '../../../recording'; 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 { SharedVideoButton } from '../../../shared-video/components';
import SpeakerStatsButton from '../../../speaker-stats/components/native/SpeakerStatsButton'; import SpeakerStatsButton from '../../../speaker-stats/components/native/SpeakerStatsButton';
import { ClosedCaptionButton } from '../../../subtitles'; import { ClosedCaptionButton } from '../../../subtitles';

View File

@ -54,7 +54,7 @@ import {
ShareAudioButton, ShareAudioButton,
startScreenShareFlow startScreenShareFlow
} from '../../../screen-share/'; } 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 { SettingsButton } from '../../../settings';
import { SharedVideoButton } from '../../../shared-video/components'; import { SharedVideoButton } from '../../../shared-video/components';
import { SpeakerStatsButton } from '../../../speaker-stats/components/web'; import { SpeakerStatsButton } from '../../../speaker-stats/components/web';