feat(recording): StartRecordingDialogContent web and native (#12009)

* feat(recording): StartRecordingDialogContent web and native
This commit is contained in:
Calinteodor 2022-08-23 11:56:02 +03:00 committed by GitHub
parent f07bd4a0d6
commit e458eed931
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1082 additions and 788 deletions

View File

@ -7,7 +7,7 @@ export interface ButtonProps {
/** /**
* Label used for accessibility. * Label used for accessibility.
*/ */
accessibilityLabel: string; accessibilityLabel?: string;
/** /**
* Whether or not the button is disabled. * Whether or not the button is disabled.

View File

@ -0,0 +1,360 @@
/* eslint-disable lines-around-comment */
import { Component } from 'react';
// @ts-ignore
import { WithTranslation } from 'react-i18next';
import {
createRecordingDialogEvent,
sendAnalytics
// @ts-ignore
} from '../../../analytics';
// @ts-ignore
import { ColorSchemeRegistry } from '../../../base/color-scheme';
// @ts-ignore
import {
_abstractMapStateToProps
// @ts-ignore
} from '../../../base/dialog';
// @ts-ignore
import { StyleType } from '../../../base/styles';
// @ts-ignore
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox';
// @ts-ignore
import { isVpaasMeeting } from '../../../jaas/functions';
// @ts-ignore
import { RECORDING_TYPES } from '../../constants';
// @ts-ignore
import { supportsLocalRecording } from '../../functions';
/**
* The type of the React {@code Component} props of
* {@link AbstractStartRecordingDialogContent}.
*/
export interface Props extends WithTranslation {
/**
* Style of the dialogs feature.
*/
_dialogStyles: StyleType,
/**
* Whether to hide the storage warning or not.
*/
_hideStorageWarning: boolean,
/**
* Whether local recording is available or not.
*/
_localRecordingAvailable: boolean,
/**
* Whether local recording is enabled or not.
*/
_localRecordingEnabled: boolean,
/**
* Whether we won't notify the other participants about the recording.
*/
_localRecordingNoNotification: boolean,
/**
* Whether self local recording is enabled or not.
*/
_localRecordingSelfEnabled: boolean,
/**
* The color-schemed stylesheet of this component.
*/
_styles: StyleType,
/**
* The redux dispatch function.
*/
dispatch: Function,
/**
* Whether to show file recordings service, even if integrations
* are enabled.
*/
fileRecordingsServiceEnabled: boolean,
/**
* Whether to show the possibility to share file recording with other people (e.g. Meeting participants), based on
* the actual implementation on the backend.
*/
fileRecordingsServiceSharingEnabled: boolean,
/**
* If true the content related to the integrations will be shown.
*/
integrationsEnabled: boolean,
/**
* <tt>true</tt> if we have valid oauth token.
*/
isTokenValid: boolean,
/**
* <tt>true</tt> if we are in process of validating the oauth token.
*/
isValidating: boolean,
/**
* Whether or not the current meeting is a vpaas one.
*/
isVpaas: boolean,
/**
* Whether or not we should only record the local streams.
*/
localRecordingOnlySelf: boolean,
/**
* The function will be called when there are changes related to the
* switches.
*/
onChange: Function,
/**
* Callback to change the local recording only self setting.
*/
onLocalRecordingSelfChange: Function,
/**
* Callback to be invoked on sharing setting change.
*/
onSharingSettingChanged: Function,
/**
* The currently selected recording service of type: RECORDING_TYPES.
*/
selectedRecordingService: string | null,
/**
* Boolean to set file recording sharing on or off.
*/
sharingSetting: boolean,
/**
* Number of MiB of available space in user's Dropbox account.
*/
spaceLeft: number | null,
/**
* The display name of the user's Dropbox account.
*/
userName: string | null
}
/**
* React Component for getting confirmation to start a file recording session.
*
* @augments Component
*/
class AbstractStartRecordingDialogContent<P extends Props> extends Component<P> {
/**
* Initializes a new {@code AbstractStartRecordingDialogContent} instance.
*
* @inheritdoc
*/
constructor(props: P) {
super(props);
// Bind event handler; it bounds once for every instance.
this._onSignIn = this._onSignIn.bind(this);
this._onSignOut = this._onSignOut.bind(this);
this._onDropboxSwitchChange = this._onDropboxSwitchChange.bind(this);
this._onRecordingServiceSwitchChange = this._onRecordingServiceSwitchChange.bind(this);
this._onLocalRecordingSwitchChange = this._onLocalRecordingSwitchChange.bind(this);
}
/**
* Implements the Component's componentDidMount method.
*
* @inheritdoc
*/
componentDidMount() {
if (!this._shouldRenderNoIntegrationsContent()
&& !this._shouldRenderIntegrationsContent()
&& !this._shouldRenderFileSharingContent()) {
this._onLocalRecordingSwitchChange();
}
}
/**
* Implements {@code Component#componentDidUpdate}.
*
* @inheritdoc
*/
componentDidUpdate(prevProps: P) {
// Auto sign-out when the use chooses another recording service.
if (prevProps.selectedRecordingService === RECORDING_TYPES.DROPBOX
&& this.props.selectedRecordingService !== RECORDING_TYPES.DROPBOX && this.props.isTokenValid) {
this._onSignOut();
}
}
/**
* Whether the file sharing content should be rendered or not.
*
* @returns {boolean}
*/
_shouldRenderFileSharingContent() {
const {
fileRecordingsServiceEnabled,
fileRecordingsServiceSharingEnabled,
isVpaas,
selectedRecordingService
} = this.props;
if (!fileRecordingsServiceEnabled
|| !fileRecordingsServiceSharingEnabled
|| isVpaas
|| selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE) {
return false;
}
return true;
}
/**
* Whether the no integrations content should be rendered or not.
*
* @returns {boolean}
*/
_shouldRenderNoIntegrationsContent() {
// show the non integrations part only if fileRecordingsServiceEnabled
// is enabled
if (!this.props.fileRecordingsServiceEnabled) {
return false;
}
return true;
}
/**
* Whether the integrations content should be rendered or not.
*
* @returns {boolean}
*/
_shouldRenderIntegrationsContent() {
if (!this.props.integrationsEnabled) {
return false;
}
return true;
}
/**
* Handler for onValueChange events from the Switch component.
*
* @returns {void}
*/
_onRecordingServiceSwitchChange() {
const {
onChange,
selectedRecordingService
} = this.props;
// act like group, cannot toggle off
if (selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) {
return;
}
onChange(RECORDING_TYPES.JITSI_REC_SERVICE);
}
/**
* Handler for onValueChange events from the Switch component.
*
* @returns {void}
*/
_onDropboxSwitchChange() {
const {
isTokenValid,
onChange,
selectedRecordingService
} = this.props;
// act like group, cannot toggle off
if (selectedRecordingService === RECORDING_TYPES.DROPBOX) {
return;
}
onChange(RECORDING_TYPES.DROPBOX);
if (!isTokenValid) {
this._onSignIn();
}
}
/**
* Handler for onValueChange events from the Switch component.
*
* @returns {void}
*/
_onLocalRecordingSwitchChange() {
const {
_localRecordingAvailable,
onChange,
selectedRecordingService
} = this.props;
if (!_localRecordingAvailable) {
return;
}
// act like group, cannot toggle off
if (selectedRecordingService
=== RECORDING_TYPES.LOCAL) {
return;
}
onChange(RECORDING_TYPES.LOCAL);
}
/**
* Sings in a user.
*
* @returns {void}
*/
_onSignIn() {
sendAnalytics(createRecordingDialogEvent('start', 'signIn.button'));
this.props.dispatch(authorizeDropbox());
}
/**
* Sings out an user from dropbox.
*
* @returns {void}
*/
_onSignOut() {
sendAnalytics(createRecordingDialogEvent('start', 'signOut.button'));
this.props.dispatch(updateDropboxToken());
}
}
/**
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
export function mapStateToProps(state: any) {
const { localRecording, recordingService } = state['features/base/config'];
const _localRecordingAvailable
= !localRecording?.disable && supportsLocalRecording();
return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
_hideStorageWarning: recordingService?.hideStorageWarning,
_localRecordingAvailable,
_localRecordingEnabled: !localRecording?.disable,
_localRecordingSelfEnabled: !localRecording?.disableSelfRecording,
_localRecordingNoNotification: !localRecording?.notifyAllParticipants,
_styles: ColorSchemeRegistry.get(state, 'StartRecordingDialogContent')
};
}
export default AbstractStartRecordingDialogContent;

View File

@ -1,781 +0,0 @@
import React, { Component } from 'react';
import {
createRecordingDialogEvent,
sendAnalytics
} from '../../../analytics';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import {
_abstractMapStateToProps
} from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import {
Container,
Image,
LoadingIndicator,
Switch,
Text
} from '../../../base/react';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import { Button } from '../../../base/ui';
import { BUTTON_TYPES } from '../../../base/ui/constants';
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox';
import { isVpaasMeeting } from '../../../jaas/functions';
import { RECORDING_TYPES } from '../../constants';
import { getRecordingDurationEstimation, supportsLocalRecording } from '../../functions';
import {
DROPBOX_LOGO,
ICON_CLOUD,
ICON_INFO,
ICON_USERS,
LOCAL_RECORDING,
TRACK_COLOR
} from './styles';
type Props = {
/**
* Style of the dialogs feature.
*/
_dialogStyles: StyleType,
/**
* Whether to hide the storage warning or not.
*/
_hideStorageWarning: boolean,
/**
* Whether local recording is enabled or not.
*/
_localRecordingEnabled: boolean,
/**
* Whether we won't notify the other participants about the recording.
*/
_localRecordingNoNotification: boolean,
/**
* Whether self local recording is enabled or not.
*/
_localRecordingSelfEnabled: boolean,
/**
* The color-schemed stylesheet of this component.
*/
_styles: StyleType,
/**
* The redux dispatch function.
*/
dispatch: Function,
/**
* Whether to show file recordings service, even if integrations
* are enabled.
*/
fileRecordingsServiceEnabled: boolean,
/**
* Whether to show the possibility to share file recording with other people (e.g. Meeting participants), based on
* the actual implementation on the backend.
*/
fileRecordingsServiceSharingEnabled: boolean,
/**
* If true the content related to the integrations will be shown.
*/
integrationsEnabled: boolean,
/**
* <tt>true</tt> if we have valid oauth token.
*/
isTokenValid: boolean,
/**
* <tt>true</tt> if we are in process of validating the oauth token.
*/
isValidating: boolean,
/**
* Whether or not the current meeting is a vpaas one.
*/
isVpaas: boolean,
/**
* Whether or not we should only record the local streams.
*/
localRecordingOnlySelf: boolean,
/**
* The function will be called when there are changes related to the
* switches.
*/
onChange: Function,
/**
* Callback to change the local recording only self setting.
*/
onLocalRecordingSelfChange: Function,
/**
* Callback to be invoked on sharing setting change.
*/
onSharingSettingChanged: Function,
/**
* The currently selected recording service of type: RECORDING_TYPES.
*/
selectedRecordingService: ?string,
/**
* Boolean to set file recording sharing on or off.
*/
sharingSetting: boolean,
/**
* Number of MiB of available space in user's Dropbox account.
*/
spaceLeft: ?number,
/**
* The translate function.
*/
t: Function,
/**
* The display name of the user's Dropbox account.
*/
userName: ?string
};
/**
* React Component for getting confirmation to start a file recording session.
*
* @augments Component
*/
class StartRecordingDialogContent extends Component<Props> {
_localRecordingAvailable: boolean;
/**
* Initializes a new {@code StartRecordingDialogContent} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
this._localRecordingAvailable = props._localRecordingEnabled && supportsLocalRecording();
// Bind event handler so it is only bound once for every instance.
this._onSignIn = this._onSignIn.bind(this);
this._onSignOut = this._onSignOut.bind(this);
this._onDropboxSwitchChange = this._onDropboxSwitchChange.bind(this);
this._onRecordingServiceSwitchChange = this._onRecordingServiceSwitchChange.bind(this);
this._onLocalRecordingSwitchChange = this._onLocalRecordingSwitchChange.bind(this);
}
/**
* Implements the Component's componentDidMount method.
*
* @inheritdoc
*/
componentDidMount() {
if (!this._shouldRenderNoIntegrationsContent()
&& !this._shouldRenderIntegrationsContent()
&& !this._shouldRenderFileSharingContent()) {
this._onLocalRecordingSwitchChange();
}
}
/**
* Implements {@code Component#componentDidUpdate}.
*
* @inheritdoc
*/
componentDidUpdate(prevProps) {
// Auto sign-out when the use chooses another recording service.
if (prevProps.selectedRecordingService === RECORDING_TYPES.DROPBOX
&& this.props.selectedRecordingService !== RECORDING_TYPES.DROPBOX && this.props.isTokenValid) {
this._onSignOut();
}
}
/**
* Renders the component.
*
* @protected
* @returns {React$Component}
*/
render() {
const { _styles: styles } = this.props;
return (
<Container
className = 'recording-dialog'
style = { styles.container }>
{ this._renderNoIntegrationsContent() }
{ this._renderFileSharingContent() }
{ this._renderUploadToTheCloudInfo() }
{ this._renderIntegrationsContent() }
{ this._renderLocalRecordingContent() }
</Container>
);
}
/**
* Whether the file sharing content should be rendered or not.
*
* @returns {boolean}
*/
_shouldRenderFileSharingContent() {
const {
fileRecordingsServiceEnabled,
fileRecordingsServiceSharingEnabled,
isVpaas,
selectedRecordingService
} = this.props;
if (!fileRecordingsServiceEnabled
|| !fileRecordingsServiceSharingEnabled
|| isVpaas
|| selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE) {
return false;
}
return true;
}
/**
* Renders the file recording service sharing options, if enabled.
*
* @returns {React$Component}
*/
_renderFileSharingContent() {
if (!this._shouldRenderFileSharingContent()) {
return null;
}
const {
_dialogStyles,
_styles: styles,
isValidating,
onSharingSettingChanged,
sharingSetting,
t
} = this.props;
return (
<Container
className = 'recording-header'
key = 'fileSharingSetting'
style = { styles.header }>
<Container className = 'recording-icon-container file-sharing-icon-container'>
<Image
className = 'recording-file-sharing-icon'
src = { ICON_USERS }
style = { styles.recordingIcon } />
</Container>
<Text
className = 'recording-title'
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.fileSharingdescription') }
</Text>
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange
= { onSharingSettingChanged }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { sharingSetting } />
</Container>
);
}
/**
* Renders the info in case recording is uploaded to the cloud.
*
* @returns {React$Component}
*/
_renderUploadToTheCloudInfo() {
const {
_dialogStyles,
_hideStorageWarning,
_styles: styles,
isVpaas,
selectedRecordingService,
t
} = this.props;
if (!(isVpaas && selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) || _hideStorageWarning) {
return null;
}
return (
<Container
className = 'recording-info'
key = 'cloudUploadInfo'
style = { styles.headerInfo }>
<Image
className = 'recording-info-icon'
src = { ICON_INFO }
style = { styles.recordingInfoIcon } />
<Text
className = 'recording-info-title'
style = {{
..._dialogStyles.text,
...styles.titleInfo
}}>
{ t('recording.serviceDescriptionCloudInfo') }
</Text>
</Container>
);
}
/**
* Whether the no integrations content should be rendered or not.
*
* @returns {boolean}
*/
_shouldRenderNoIntegrationsContent() {
// show the non integrations part only if fileRecordingsServiceEnabled
// is enabled
if (!this.props.fileRecordingsServiceEnabled) {
return false;
}
return true;
}
/**
* Renders the content in case no integrations were enabled.
*
* @returns {React$Component}
*/
_renderNoIntegrationsContent() {
if (!this._shouldRenderNoIntegrationsContent()) {
return null;
}
const { _dialogStyles, _styles: styles, isValidating, isVpaas, t } = this.props;
const switchContent
= this.props.integrationsEnabled || this.props._localRecordingEnabled
? (
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this._onRecordingServiceSwitchChange }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { this.props.selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE } />
) : null;
const label = isVpaas ? t('recording.serviceDescriptionCloud') : t('recording.serviceDescription');
const jitsiContentRecordingIconContainer
= this.props.integrationsEnabled || this.props._localRecordingEnabled
? 'jitsi-content-recording-icon-container-with-switch'
: 'jitsi-content-recording-icon-container-without-switch';
const contentRecordingClass = isVpaas
? 'cloud-content-recording-icon-container'
: jitsiContentRecordingIconContainer;
const jitsiRecordingHeaderClass = !isVpaas && 'jitsi-recording-header';
return (
<Container
className = { `recording-header ${jitsiRecordingHeaderClass}` }
key = 'noIntegrationSetting'
style = { styles.header }>
<Container className = { contentRecordingClass }>
<Image
className = 'content-recording-icon'
src = { ICON_CLOUD }
style = { styles.recordingIcon } />
</Container>
<Text
className = 'recording-title'
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ label }
</Text>
{ switchContent }
</Container>
);
}
/**
* Whether the integrations content should be rendered or not.
*
* @returns {boolean}
*/
_shouldRenderIntegrationsContent() {
if (!this.props.integrationsEnabled) {
return false;
}
return true;
}
/**
* Renders the content in case integrations were enabled.
*
* @protected
* @returns {React$Component}
*/
_renderIntegrationsContent() {
if (!this._shouldRenderIntegrationsContent()) {
return null;
}
const { _dialogStyles, _styles: styles, isTokenValid, isValidating, t } = this.props;
let content = null;
let switchContent = null;
if (isValidating) {
content = this._renderSpinner();
switchContent = <Container className = 'recording-switch' />;
} else if (isTokenValid) {
content = this._renderSignOut();
switchContent = (
<Container className = 'recording-switch'>
<Button
label = { t('recording.signOut') }
onClick = { this._onSignOut }
onPress = { this._onSignOut }
type = { BUTTON_TYPES.SECONDARY } />
</Container>
);
} else {
switchContent = (
<Container className = 'recording-switch'>
<Button
label = { t('recording.signIn') }
onClick = { this._onSignIn }
onPress = { this._onSignIn }
type = { BUTTON_TYPES.PRIMARY } />
</Container>
);
}
if (this.props.fileRecordingsServiceEnabled || this._localRecordingAvailable) {
switchContent = (
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this._onDropboxSwitchChange }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { this.props.selectedRecordingService
=== RECORDING_TYPES.DROPBOX } />
);
}
return (
<Container>
<Container
className = { `recording-header ${this._shouldRenderNoIntegrationsContent()
? 'recording-header-line' : ''}` }
style = { styles.headerIntegrations }>
<Container
className = 'recording-icon-container'>
<Image
className = 'recording-icon'
src = { DROPBOX_LOGO }
style = { styles.recordingIcon } />
</Container>
<Text
className = 'recording-title'
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.authDropboxText') }
</Text>
{ switchContent }
</Container>
<Container
className = 'authorization-panel'>
{ content }
</Container>
</Container>
);
}
_onDropboxSwitchChange: () => void;
_onRecordingServiceSwitchChange: () => void;
_onLocalRecordingSwitchChange: () => void;
/**
* Handler for onValueChange events from the Switch component.
*
* @returns {void}
*/
_onRecordingServiceSwitchChange() {
const {
onChange,
selectedRecordingService
} = this.props;
// act like group, cannot toggle off
if (selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) {
return;
}
onChange(RECORDING_TYPES.JITSI_REC_SERVICE);
}
/**
* Handler for onValueChange events from the Switch component.
*
* @returns {void}
*/
_onDropboxSwitchChange() {
const {
isTokenValid,
onChange,
selectedRecordingService
} = this.props;
// act like group, cannot toggle off
if (selectedRecordingService === RECORDING_TYPES.DROPBOX) {
return;
}
onChange(RECORDING_TYPES.DROPBOX);
if (!isTokenValid) {
this._onSignIn();
}
}
/**
* Handler for onValueChange events from the Switch component.
*
* @returns {void}
*/
_onLocalRecordingSwitchChange() {
const {
onChange,
selectedRecordingService
} = this.props;
if (!this._localRecordingAvailable) {
return;
}
// act like group, cannot toggle off
if (selectedRecordingService
=== RECORDING_TYPES.LOCAL) {
return;
}
onChange(RECORDING_TYPES.LOCAL);
}
/**
* Renders a spinner component.
*
* @returns {React$Component}
*/
_renderSpinner() {
return (
<LoadingIndicator
isCompleting = { false }
size = 'small' />
);
}
/**
* Renders the screen with the account information of a logged in user.
*
* @returns {React$Component}
*/
_renderSignOut() {
const { _styles: styles, spaceLeft, t, userName } = this.props;
const duration = getRecordingDurationEstimation(spaceLeft);
return (
<Container>
<Container
className = 'logged-in-panel'
style = { styles.loggedIn }>
<Container>
<Text
style = { [
styles.text,
styles.recordingText
] }>
{ t('recording.loggedIn', { userName }) }
</Text>
</Container>
<Container>
<Text
style = { [
styles.text,
styles.recordingText
] }>
{
t('recording.availableSpace', {
spaceLeft,
duration
})
}
</Text>
</Container>
</Container>
</Container>
);
}
_renderLocalRecordingContent: () => void;
/**
* Renders the content for local recordings.
*
* @protected
* @returns {React$Component}
*/
_renderLocalRecordingContent() {
const {
_styles: styles,
isValidating,
t,
_dialogStyles,
selectedRecordingService,
_localRecordingNoNotification
} = this.props;
if (!this._localRecordingAvailable) {
return null;
}
return (
<>
<Container>
<Container
className = 'recording-header recording-header-line'
style = { styles.header }>
<Container
className = 'recording-icon-container'>
<Image
className = 'recording-icon'
src = { LOCAL_RECORDING }
style = { styles.recordingIcon } />
</Container>
<Text
className = 'recording-title'
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.saveLocalRecording') }
</Text>
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this._onLocalRecordingSwitchChange }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { this.props.selectedRecordingService
=== RECORDING_TYPES.LOCAL } />
</Container>
</Container>
{selectedRecordingService === RECORDING_TYPES.LOCAL && (
<>
{this.props._localRecordingSelfEnabled && (
<Container>
<Container
className = 'recording-header space-top'
style = { styles.header }>
<Container className = 'recording-icon-container file-sharing-icon-container'>
<Image
className = 'recording-file-sharing-icon'
src = { ICON_USERS }
style = { styles.recordingIcon } />
</Container>
<Text
className = 'recording-title'
style = {{
..._dialogStyles.text,
...styles.title
}}>
{t('recording.onlyRecordSelf')}
</Text>
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this.props.onLocalRecordingSelfChange }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { this.props.localRecordingOnlySelf } />
</Container>
</Container>
)}
<Text className = 'local-recording-warning text'>
{t('recording.localRecordingWarning')}
</Text>
{_localRecordingNoNotification && !this.props.localRecordingOnlySelf
&& <Text className = 'local-recording-warning notification'>
{t('recording.localRecordingNoNotificationWarning')}
</Text>
}
</>
)}
</>
);
}
_onSignIn: () => void;
/**
* Sings in a user.
*
* @returns {void}
*/
_onSignIn() {
sendAnalytics(createRecordingDialogEvent('start', 'signIn.button'));
this.props.dispatch(authorizeDropbox());
}
_onSignOut: () => void;
/**
* Sings out an user from dropbox.
*
* @returns {void}
*/
_onSignOut() {
sendAnalytics(createRecordingDialogEvent('start', 'signOut.button'));
this.props.dispatch(updateDropboxToken());
}
}
/**
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
_hideStorageWarning: state['features/base/config'].recordingService?.hideStorageWarning,
_localRecordingEnabled: !state['features/base/config'].localRecording?.disable,
_localRecordingSelfEnabled: !state['features/base/config'].localRecording?.disableSelfRecording,
_localRecordingNoNotification: !state['features/base/config'].localRecording?.notifyAllParticipants,
_styles: ColorSchemeRegistry.get(state, 'StartRecordingDialogContent')
};
}
export default translate(connect(_mapStateToProps)(StartRecordingDialogContent));

View File

@ -1,5 +1,3 @@
// @flow
import React from 'react'; import React from 'react';
import { translate } from '../../../../base/i18n'; import { translate } from '../../../../base/i18n';
@ -14,9 +12,11 @@ import AbstractStartRecordingDialog, {
type Props, type Props,
mapStateToProps mapStateToProps
} from '../AbstractStartRecordingDialog'; } from '../AbstractStartRecordingDialog';
import StartRecordingDialogContent from '../StartRecordingDialogContent';
import styles from '../styles.native'; import styles from '../styles.native';
import StartRecordingDialogContent from './StartRecordingDialogContent';
/** /**
* React Component for getting confirmation to start a file recording session in * React Component for getting confirmation to start a file recording session in
* progress. * progress.

View File

@ -0,0 +1,314 @@
/* eslint-disable lines-around-comment */
import React from 'react';
import { Image, View } from 'react-native';
import { Switch, Text } from 'react-native-paper';
import { translate } from '../../../../base/i18n/functions';
// @ts-ignore
import { LoadingIndicator } from '../../../../base/react';
import { connect } from '../../../../base/redux/functions';
import Button from '../../../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../../../base/ui/constants';
// @ts-ignore
import { RECORDING_TYPES } from '../../../constants';
// @ts-ignore
import { getRecordingDurationEstimation } from '../../../functions';
import AbstractStartRecordingDialogContent, {
Props,
mapStateToProps
} from '../AbstractStartRecordingDialogContent';
import {
DROPBOX_LOGO,
ICON_CLOUD,
ICON_INFO,
ICON_USERS,
TRACK_COLOR
// @ts-ignore
} from '../styles.native';
/**
* The start recording dialog content for the mobile application.
*/
class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<Props> {
/**
* Renders the component.
*
* @protected
* @returns {React$Component}
*/
render() {
const { _styles: styles } = this.props;
return (
<View style = { styles.container }>
{ this._renderNoIntegrationsContent() }
{ this._renderFileSharingContent() }
{ this._renderUploadToTheCloudInfo() }
{ this._renderIntegrationsContent() }
</View>
);
}
/**
* Renders the content in case no integrations were enabled.
*
* @returns {React$Component}
*/
_renderNoIntegrationsContent() {
const {
_dialogStyles,
_styles: styles,
integrationsEnabled,
isValidating,
selectedRecordingService,
t
} = this.props;
if (!this._shouldRenderNoIntegrationsContent()) {
return null;
}
const switchContent
= integrationsEnabled
? (
<Switch
disabled = { isValidating }
onValueChange = { this._onRecordingServiceSwitchChange }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE } />
) : null;
return (
<View
key = 'noIntegrationSetting'
style = { styles.header }>
<Image
source = { ICON_CLOUD }
style = { styles.recordingIcon } />
<Text
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.serviceDescription') }
</Text>
{ switchContent }
</View>
);
}
/**
* Renders the file recording service sharing options, if enabled.
*
* @returns {React$Component}
*/
_renderFileSharingContent() {
if (!this._shouldRenderFileSharingContent()) {
return null;
}
const {
_dialogStyles,
_styles: styles,
isValidating,
onSharingSettingChanged,
sharingSetting,
t
} = this.props;
return (
<View
key = 'fileSharingSetting'
style = { styles.header }>
<Image
source = { ICON_USERS }
style = { styles.recordingIcon } />
<Text
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.fileSharingdescription') }
</Text>
<Switch
disabled = { isValidating }
// @ts-ignore
onValueChange = { onSharingSettingChanged }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { sharingSetting } />
</View>
);
}
/**
* Renders the info in case recording is uploaded to the cloud.
*
* @returns {React$Component}
*/
_renderUploadToTheCloudInfo() {
const {
_dialogStyles,
_hideStorageWarning,
_styles: styles,
isVpaas,
selectedRecordingService,
t
} = this.props;
if (!(isVpaas && selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) || _hideStorageWarning) {
return null;
}
return (
<View
key = 'cloudUploadInfo'
style = { styles.headerInfo }>
<Image
source = { ICON_INFO }
style = { styles.recordingInfoIcon } />
<Text
style = {{
..._dialogStyles.text,
...styles.titleInfo
}}>
{ t('recording.serviceDescriptionCloudInfo') }
</Text>
</View>
);
}
/**
* Renders a spinner component.
*
* @returns {React$Component}
*/
_renderSpinner() {
return (
<LoadingIndicator
isCompleting = { false }
size = 'small' />
);
}
/**
* Renders the screen with the account information of a logged in user.
*
* @returns {React$Component}
*/
_renderSignOut() {
const { _styles: styles, spaceLeft, t, userName } = this.props;
const duration = getRecordingDurationEstimation(spaceLeft);
return (
<View
style = { styles.loggedIn }>
<Text
style = { [
styles.text,
styles.recordingText
] }>
{ t('recording.loggedIn', { userName }) }
</Text>
<Text
style = { [
styles.text,
styles.recordingText
] }>
{
t('recording.availableSpace', {
spaceLeft,
duration
})
}
</Text>
</View>
);
}
/**
* Renders the content in case integrations were enabled.
*
* @protected
* @returns {React$Component}
*/
_renderIntegrationsContent() {
if (!this._shouldRenderIntegrationsContent()) {
return null;
}
const {
_dialogStyles,
_styles: styles,
fileRecordingsServiceEnabled,
isTokenValid,
isValidating,
selectedRecordingService,
t
} = this.props;
let content = null;
let switchContent = null;
if (isValidating) {
content = this._renderSpinner();
switchContent = <View />;
} else if (isTokenValid) {
content = this._renderSignOut();
switchContent = (
<Button
accessibilityLabel = 'recording.signOut'
labelKey = 'recording.signOut'
onClick = { this._onSignOut }
type = { BUTTON_TYPES.SECONDARY } />
);
} else {
switchContent = (
<Button
accessibilityLabel = 'recording.signIn'
labelKey = 'recording.signIn'
onClick = { this._onSignIn }
type = { BUTTON_TYPES.PRIMARY } />
);
}
if (fileRecordingsServiceEnabled) {
switchContent = (
<Switch
disabled = { isValidating }
onValueChange = { this._onDropboxSwitchChange }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { selectedRecordingService
=== RECORDING_TYPES.DROPBOX } />
);
}
return (
<View>
<View
style = { styles.headerIntegrations }>
<Image
source = { DROPBOX_LOGO }
style = { styles.recordingIcon } />
<Text
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.authDropboxText') }
</Text>
{ switchContent }
</View>
<View>
{ content }
</View>
</View>
);
}
}
export default translate(connect(mapStateToProps)(StartRecordingDialogContent));

View File

@ -1,5 +1,3 @@
// @flow
import React from 'react'; import React from 'react';
import { Dialog } from '../../../../base/dialog'; import { Dialog } from '../../../../base/dialog';
@ -11,7 +9,9 @@ import { RECORDING_TYPES } from '../../../constants';
import AbstractStartRecordingDialog, { import AbstractStartRecordingDialog, {
mapStateToProps as abstractMapStateToProps mapStateToProps as abstractMapStateToProps
} from '../AbstractStartRecordingDialog'; } from '../AbstractStartRecordingDialog';
import StartRecordingDialogContent from '../StartRecordingDialogContent';
import StartRecordingDialogContent from './StartRecordingDialogContent';
/** /**
* React Component for getting confirmation to start a file recording session in * React Component for getting confirmation to start a file recording session in

View File

@ -0,0 +1,401 @@
/* eslint-disable lines-around-comment */
import React from 'react';
import { translate } from '../../../../base/i18n/functions';
import {
Container,
Image,
LoadingIndicator,
Switch,
Text
// @ts-ignore
} from '../../../../base/react';
import { connect } from '../../../../base/redux/functions';
import Button from '../../../../base/ui/components/web/Button';
import { BUTTON_TYPES } from '../../../../base/ui/constants';
// @ts-ignore
import { RECORDING_TYPES } from '../../../constants';
// @ts-ignore
import { getRecordingDurationEstimation } from '../../../functions';
import AbstractStartRecordingDialogContent, {
Props,
mapStateToProps
} from '../AbstractStartRecordingDialogContent';
import {
DROPBOX_LOGO,
ICON_CLOUD,
ICON_INFO,
ICON_USERS,
LOCAL_RECORDING,
TRACK_COLOR
// @ts-ignore
} from '../styles.web';
/**
* The start recording dialog content for the mobile application.
*/
class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<Props> {
/**
* Renders the component.
*
* @protected
* @returns {React$Component}
*/
render() {
return (
<Container className = 'recording-dialog'>
{ this._renderNoIntegrationsContent() }
{ this._renderFileSharingContent() }
{ this._renderUploadToTheCloudInfo() }
{ this._renderIntegrationsContent() }
{ this._renderLocalRecordingContent() }
</Container>
);
}
/**
* Renders the content in case no integrations were enabled.
*
* @returns {React$Component}
*/
_renderNoIntegrationsContent() {
if (!this._shouldRenderNoIntegrationsContent()) {
return null;
}
const {
_localRecordingAvailable,
integrationsEnabled,
isValidating,
isVpaas,
selectedRecordingService,
t
} = this.props;
const switchContent
= integrationsEnabled || _localRecordingAvailable
? (
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this._onRecordingServiceSwitchChange }
trackColor = {{ false: TRACK_COLOR }}
value = { selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE } />
) : null;
const label = isVpaas ? t('recording.serviceDescriptionCloud') : t('recording.serviceDescription');
const jitsiContentRecordingIconContainer
= integrationsEnabled || _localRecordingAvailable
? 'jitsi-content-recording-icon-container-with-switch'
: 'jitsi-content-recording-icon-container-without-switch';
const contentRecordingClass = isVpaas
? 'cloud-content-recording-icon-container'
: jitsiContentRecordingIconContainer;
const jitsiRecordingHeaderClass = !isVpaas && 'jitsi-recording-header';
return (
<Container
className = { `recording-header ${jitsiRecordingHeaderClass}` }
key = 'noIntegrationSetting'>
<Container className = { contentRecordingClass }>
<Image
className = 'content-recording-icon'
src = { ICON_CLOUD } />
</Container>
<Text className = 'recording-title'>
{ label }
</Text>
{ switchContent }
</Container>
);
}
/**
* Renders the file recording service sharing options, if enabled.
*
* @returns {React$Component}
*/
_renderFileSharingContent() {
if (!this._shouldRenderFileSharingContent()) {
return null;
}
const {
isValidating,
onSharingSettingChanged,
sharingSetting,
t
// @ts-ignore
} = this.props;
return (
<Container
className = 'recording-header'
key = 'fileSharingSetting'>
<Container className = 'recording-icon-container file-sharing-icon-container'>
<Image
className = 'recording-file-sharing-icon'
src = { ICON_USERS } />
</Container>
<Text className = 'recording-title'>
{ t('recording.fileSharingdescription') }
</Text>
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { onSharingSettingChanged }
trackColor = {{ false: TRACK_COLOR }}
value = { sharingSetting } />
</Container>
);
}
/**
* Renders the info in case recording is uploaded to the cloud.
*
* @returns {React$Component}
*/
_renderUploadToTheCloudInfo() {
const {
_hideStorageWarning,
isVpaas,
selectedRecordingService,
t
} = this.props;
if (!(isVpaas && selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) || _hideStorageWarning) {
return null;
}
return (
<Container
className = 'recording-info'
key = 'cloudUploadInfo'>
<Image
className = 'recording-info-icon'
src = { ICON_INFO } />
<Text className = 'recording-info-title'>
{ t('recording.serviceDescriptionCloudInfo') }
</Text>
</Container>
);
}
/**
* Renders a spinner component.
*
* @returns {React$Component}
*/
_renderSpinner() {
return (
<LoadingIndicator
isCompleting = { false }
size = 'small' />
);
}
/**
* Renders the screen with the account information of a logged in user.
*
* @returns {React$Component}
*/
_renderSignOut() {
const {
spaceLeft,
t,
userName
} = this.props;
const duration = getRecordingDurationEstimation(spaceLeft);
return (
<Container>
<Container className = 'logged-in-panel'>
<Container>
<Text>
{ t('recording.loggedIn', { userName }) }
</Text>
</Container>
<Container>
<Text>
{
t('recording.availableSpace', {
spaceLeft,
duration
})
}
</Text>
</Container>
</Container>
</Container>
);
}
/**
* Renders the content in case integrations were enabled.
*
* @protected
* @returns {React$Component}
*/
_renderIntegrationsContent() {
if (!this._shouldRenderIntegrationsContent()) {
return null;
}
const {
_localRecordingAvailable,
fileRecordingsServiceEnabled,
isTokenValid,
isValidating,
selectedRecordingService,
t
} = this.props;
let content = null;
let switchContent = null;
if (isValidating) {
content = this._renderSpinner();
switchContent = <Container className = 'recording-switch' />;
} else if (isTokenValid) {
content = this._renderSignOut();
switchContent = (
<Container className = 'recording-switch'>
<Button
accessibilityLabel = { t('recording.signOut') }
labelKey = 'recording.signOut'
onClick = { this._onSignOut }
type = { BUTTON_TYPES.SECONDARY } />
</Container>
);
} else {
switchContent = (
<Container className = 'recording-switch'>
<Button
accessibilityLabel = { t('recording.signIn') }
labelKey = 'recording.signIn'
onClick = { this._onSignIn }
type = { BUTTON_TYPES.PRIMARY } />
</Container>
);
}
if (fileRecordingsServiceEnabled || _localRecordingAvailable) {
switchContent = (
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this._onDropboxSwitchChange }
trackColor = {{ false: TRACK_COLOR }}
value = { selectedRecordingService
=== RECORDING_TYPES.DROPBOX } />
);
}
return (
<Container>
<Container
className = { `recording-header ${this._shouldRenderNoIntegrationsContent()
? 'recording-header-line' : ''}` }>
<Container
className = 'recording-icon-container'>
<Image
className = 'recording-icon'
src = { DROPBOX_LOGO } />
</Container>
<Text className = 'recording-title'>
{ t('recording.authDropboxText') }
</Text>
{ switchContent }
</Container>
<Container className = 'authorization-panel'>
{ content }
</Container>
</Container>
);
}
/**
* Renders the content for local recordings.
*
* @protected
* @returns {React$Component}
*/
_renderLocalRecordingContent() {
const {
_localRecordingAvailable,
_localRecordingNoNotification,
_localRecordingSelfEnabled,
isValidating,
localRecordingOnlySelf,
onLocalRecordingSelfChange,
t,
selectedRecordingService
} = this.props;
if (!_localRecordingAvailable) {
return null;
}
return (
<>
<Container>
<Container
className = 'recording-header recording-header-line'>
<Container
className = 'recording-icon-container'>
<Image
className = 'recording-icon'
src = { LOCAL_RECORDING } />
</Container>
<Text className = 'recording-title'>
{ t('recording.saveLocalRecording') }
</Text>
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this._onLocalRecordingSwitchChange }
trackColor = {{ false: TRACK_COLOR }}
value = { selectedRecordingService
=== RECORDING_TYPES.LOCAL } />
</Container>
</Container>
{selectedRecordingService === RECORDING_TYPES.LOCAL && (
<>
{_localRecordingSelfEnabled && (
<Container>
<Container className = 'recording-header space-top'>
<Container className = 'recording-icon-container file-sharing-icon-container'>
<Image
className = 'recording-file-sharing-icon'
src = { ICON_USERS } />
</Container>
<Text className = 'recording-title'>
{t('recording.onlyRecordSelf')}
</Text>
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { onLocalRecordingSelfChange }
trackColor = {{ false: TRACK_COLOR }}
value = { localRecordingOnlySelf } />
</Container>
</Container>
)}
<Text className = 'local-recording-warning text'>
{t('recording.localRecordingWarning')}
</Text>
{_localRecordingNoNotification && !localRecordingOnlySelf
&& <Text className = 'local-recording-warning notification'>
{t('recording.localRecordingNoNotificationWarning')}
</Text>
}
</>
)}
</>
);
}
}
export default translate(connect(mapStateToProps)(StartRecordingDialogContent));