feat(dialog) start recording/live stream screens, new AlertDialog

This commit is contained in:
Calin Chitu 2022-02-08 13:25:32 +02:00 committed by Calinteodor
parent f38702bb65
commit 8a6b6f2942
22 changed files with 390 additions and 340 deletions

View File

@ -367,6 +367,7 @@
"shareYourScreenDisabled": "Screen sharing disabled.",
"sharedVideoDialogError": "Error: Invalid URL",
"sharedVideoLinkPlaceholder": "YouTube link or direct video link",
"start": "Start ",
"startLiveStreaming": "Start live stream",
"startRecording": "Start recording",
"startRemoteControlErrorMessage": "An error occurred while trying to start the remote control session!",

View File

@ -1,15 +1,15 @@
// @flow
import React from 'react';
import { Text } from 'react-native';
import { View } from 'react-native';
import Dialog from 'react-native-dialog';
import { translate } from '../../../i18n';
import { connect } from '../../../redux';
import { _abstractMapStateToProps } from '../../functions';
import AbstractDialog, { type Props as AbstractProps } from '../AbstractDialog';
import { renderHTML } from '../functions.native';
import { type Props as AbstractProps } from './BaseDialog';
import BaseSubmitDialog from './BaseSubmitDialog';
type Props = AbstractProps & {
@ -21,30 +21,45 @@ type Props = AbstractProps & {
* {@code translate(string, Object)} for more details.
*/
contentKey: string | { key: string, params: Object},
/**
* Translation function.
*/
t: Function
};
/**
* Implements an alert dialog, to simply show an error or a message, then disappear on dismiss.
* Implements an alert dialog, to simply show an error or a message,
* then disappear on dismiss.
*/
class AlertDialog extends BaseSubmitDialog<Props, *> {
class AlertDialog extends AbstractDialog<Props> {
/**
* Implements {@code BaseSubmitDialog._renderSubmittable}.
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
_renderSubmittable() {
const { _dialogStyles, contentKey, t } = this.props;
render() {
const { contentKey, t } = this.props;
const content
= typeof contentKey === 'string'
? t(contentKey)
: renderHTML(t(contentKey.key, contentKey.params));
return (
<Text style = { _dialogStyles.text }>
{ content }
</Text>
<View>
<Dialog.Container visible = { true }>
<Dialog.Description>
{ content }
</Dialog.Description>
<Dialog.Button
label = { t('dialog.Ok') }
onPress = { this._onSubmit } />
</Dialog.Container>
</View>
);
}
_onSubmit: () => boolean;
}
export default translate(connect(_abstractMapStateToProps)(AlertDialog));

View File

@ -1,99 +0,0 @@
// @flow
import React from 'react';
import {
KeyboardAvoidingView,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
View
} from 'react-native';
import { Icon, IconClose } from '../../../icons';
import { StyleType } from '../../../styles';
import AbstractDialog, {
type Props as AbstractProps,
type State
} from '../AbstractDialog';
import { brandedDialog as styles } from './styles';
export type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_dialogStyles: StyleType,
t: Function
}
/**
* Component to render a custom dialog.
*/
class BaseDialog<P: Props, S: State> extends AbstractDialog<P, S> {
/**
* Initializes a new {@code FeedbackDialog} instance.
*
* @inheritdoc
*/
constructor(props: P) {
super(props);
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { _dialogStyles, style, t, titleKey } = this.props;
return (
<TouchableWithoutFeedback>
<KeyboardAvoidingView
behavior = 'height'
style = { [
styles.overlay
] }>
<View
pointerEvents = 'box-none'
style = { [
_dialogStyles.dialog,
style
] }>
<View style = { styles.headerWrapper }>
<Text style = { styles.dialogTitle }>
{ titleKey ? t(titleKey) : ' ' }
</Text>
<TouchableOpacity
onPress = { this._onCancel }
style = { styles.closeWrapper }>
<Icon
src = { IconClose }
style = { _dialogStyles.closeStyle } />
</TouchableOpacity>
</View>
{ this._renderContent() }
</View>
</KeyboardAvoidingView>
</TouchableWithoutFeedback>
);
}
_onCancel: () => void;
_onSubmit: ?string => boolean;
/**
* Renders the content of the dialog.
*
* @returns {ReactElement}
*/
_renderContent: () => Object;
}
export default BaseDialog;

View File

@ -1,97 +0,0 @@
// @flow
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { StyleType } from '../../../styles';
import BaseDialog, { type Props as BaseProps } from './BaseDialog';
import {
brandedDialog
} from './styles';
type Props = BaseProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_dialogStyles: StyleType,
t: Function
}
/**
* Abstract dialog to submit something. E.g. A confirmation or a form.
*/
class BaseSubmitDialog<P: Props, S: *> extends BaseDialog<P, S> {
/**
* Returns the title key of the submit button.
*
* NOTE: Please do not change this, this should be consistent across the
* application. This method is here to be able to be overridden ONLY by the
* {@code ConfirmDialog}.
*
* @returns {string}
*/
_getSubmitButtonKey() {
return this.props.okKey || 'dialog.Ok';
}
/**
* Renders additional buttons, if any - may be overwritten by children.
*
* @returns {?ReactElement}
*/
_renderAdditionalButtons() {
return null;
}
/**
* Implements {@code BaseDialog._renderContent}.
*
* @inheritdoc
*/
_renderContent() {
const { _dialogStyles, t } = this.props;
const additionalButtons = this._renderAdditionalButtons();
return (
<View>
<View style = { brandedDialog.mainWrapper }>
{ this._renderSubmittable() }
</View>
<View style = { brandedDialog.buttonWrapper }>
{ additionalButtons }
<TouchableOpacity
disabled = { this.props.okDisabled }
onPress = { this._onSubmit }
style = { [
_dialogStyles.button,
additionalButtons
? null : brandedDialog.buttonFarLeft,
brandedDialog.buttonFarRight
] }>
<Text style = { _dialogStyles.buttonLabel }>
{ t(this._getSubmitButtonKey()) }
</Text>
</TouchableOpacity>
</View>
</View>
);
}
_onCancel: () => void;
_onSubmit: () => boolean;
/** .
* Renders the actual content of the dialog defining what is about to be
* submitted. E.g. A simple confirmation (text, properly wrapped) or a
* complex form.
*
* @returns {Object}
*/
_renderSubmittable: () => Object;
}
export default BaseSubmitDialog;

View File

@ -135,8 +135,7 @@ class ConfirmDialog extends AbstractDialog<Props> {
return (
<View>
<Dialog.Container
visible = { true }>
<Dialog.Container visible = { true }>
{
title && <Dialog.Title>
{ t(title) }

View File

@ -1,29 +0,0 @@
// @flow
import { translate } from '../../../i18n';
import { connect } from '../../../redux';
import { _abstractMapStateToProps } from '../../functions';
import { type Props as BaseProps } from './BaseDialog';
import BaseSubmitDialog from './BaseSubmitDialog';
type Props = BaseProps & {
t: Function
}
/**
* Implements a submit dialog component that can have free content.
*/
class CustomSubmitDialog extends BaseSubmitDialog<Props, *> {
/**
* Implements {@code BaseSubmitDialog._renderSubmittable}.
*
* @inheritdoc
*/
_renderSubmittable() {
return this.props.children;
}
}
export default translate(
connect(_abstractMapStateToProps)(CustomSubmitDialog));

View File

@ -5,7 +5,6 @@ export { default as ConfirmDialog } from './ConfirmDialog';
export { default as DialogContainer } from './DialogContainer';
export { default as AlertDialog } from './AlertDialog';
export { default as InputDialog } from './InputDialog';
export { default as CustomSubmitDialog } from './CustomSubmitDialog';
// NOTE: Some dialogs reuse the style of these base classes for consistency
// and as we're in a /native namespace, it's safe to export the styles.

View File

@ -55,7 +55,6 @@ export const bottomSheetStyles = {
};
export default {
dialogButton: {
...BaseTheme.typography.labelButton
},
@ -76,10 +75,6 @@ export const brandedDialog = {
fontWeight: 'bold'
},
buttonFarLeft: {
borderBottomLeftRadius: BORDER_RADIUS
},
buttonFarRight: {
borderBottomRightRadius: BORDER_RADIUS
},
@ -90,21 +85,6 @@ export const brandedDialog = {
flexDirection: 'row'
},
closeWrapper: {
padding: BoxModel.padding
},
dialogTitle: {
fontWeight: 'bold',
paddingLeft: BoxModel.padding * 2
},
headerWrapper: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between'
},
mainWrapper: {
alignSelf: 'stretch',
padding: BoxModel.padding * 2,
@ -114,15 +94,6 @@ export const brandedDialog = {
paddingBottom: BoxModel.padding * 3
},
overlay: {
...StyleSheet.absoluteFillObject,
alignItems: 'center',
backgroundColor: 'rgba(127, 127, 127, 0.6)',
flexDirection: 'row',
justifyContent: 'center',
padding: 30
},
overlayTouchable: {
...StyleSheet.absoluteFillObject
}

View File

@ -15,6 +15,9 @@ 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 { StartLiveStreamDialog } from '../../../../../recording';
import { StartRecordingDialog }
from '../../../../../recording/components/Recording/native';
import SecurityDialog
from '../../../../../security/components/security-dialog/native/SecurityDialog';
import SpeakerStats
@ -24,9 +27,11 @@ import {
chatScreenOptions,
conferenceScreenOptions,
inviteScreenOptions,
liveStreamScreenOptions,
lobbyScreenOptions,
navigationContainerTheme,
participantsScreenOptions,
recordingScreenOptions,
securityScreenOptions,
sharedDocumentScreenOptions,
speakerStatsScreenOptions
@ -93,6 +98,18 @@ const ConferenceNavigationContainer = () => {
...securityScreenOptions,
title: t('security.header')
}} />
<ConferenceStack.Screen
component = { StartRecordingDialog }
name = { screen.conference.recording }
options = {{
...recordingScreenOptions
}} />
<ConferenceStack.Screen
component = { StartLiveStreamDialog }
name = { screen.conference.liveStream }
options = {{
...liveStreamScreenOptions
}} />
<ConferenceStack.Screen
component = { SpeakerStats }
name = { screen.conference.speakerStats }

View File

@ -24,6 +24,8 @@ export const screen = {
}
},
security: 'Security Options',
recording: 'Recording',
liveStream: 'Live stream',
speakerStats: 'Speaker Stats',
participants: 'Participants',
invite: 'Invite',

View File

@ -245,6 +245,20 @@ export const securityScreenOptions = {
...presentationScreenOptions
};
/**
* Screen options for recording modal.
*/
export const recordingScreenOptions = {
...presentationScreenOptions
};
/**
* Screen options for live stream modal.
*/
export const liveStreamScreenOptions = {
...presentationScreenOptions
};
/**
* Screen options for shared document.
*/

View File

@ -1,6 +1,5 @@
// @flow
import { openDialog } from '../../../base/dialog';
import { IconLiveStreaming } from '../../../base/icons';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import {
@ -13,10 +12,6 @@ import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
import { FEATURES } from '../../../jaas/constants';
import { getActiveSession } from '../../functions';
import {
StartLiveStreamDialog,
StopLiveStreamDialog
} from './_';
/**
* The type of the React {@code Component} props of
@ -24,16 +19,16 @@ import {
*/
export type Props = AbstractButtonProps & {
/**
* True if there is a running active live stream, false otherwise.
*/
_isLiveStreamRunning: boolean,
/**
* True if the button needs to be disabled.
*/
_disabled: Boolean,
/**
* True if there is a running active live stream, false otherwise.
*/
_isLiveStreamRunning: boolean,
/**
* The tooltip to display when hovering over the button.
*/
@ -69,6 +64,17 @@ export default class AbstractLiveStreamButton<P: Props> extends AbstractButton<P
return this.props._tooltip || '';
}
/**
* Helper function to be implemented by subclasses, which should be used
* to handle the live stream button being clicked / pressed.
*
* @protected
* @returns {void}
*/
_onHandleClick() {
// To be implemented by subclass.
}
/**
* Handles clicking / pressing the button.
*
@ -77,14 +83,12 @@ export default class AbstractLiveStreamButton<P: Props> extends AbstractButton<P
* @returns {void}
*/
async _handleClick() {
const { _isLiveStreamRunning, dispatch } = this.props;
const { dispatch } = this.props;
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
if (!dialogShown) {
dispatch(openDialog(
_isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog
));
this._onHandleClick();
}
}

View File

@ -1,9 +1,41 @@
// @flow
import { openDialog } from '../../../../base/dialog';
import { LIVE_STREAMING_ENABLED, getFeatureFlag } from '../../../../base/flags';
import { translate } from '../../../../base/i18n';
import { connect } from '../../../../base/redux';
import AbstractLiveStreamButton, { _mapStateToProps as _abstractMapStateToProps } from '../AbstractLiveStreamButton';
import { navigate }
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../../mobile/navigation/routes';
import AbstractLiveStreamButton,
{ _mapStateToProps as _abstractMapStateToProps } from '../AbstractLiveStreamButton';
import type { Props } from '../AbstractStartLiveStreamDialog';
import { StopLiveStreamDialog } from './index';
/**
* Button for opening the live stream settings screen.
*/
class LiveStreamButton extends AbstractLiveStreamButton<Props> {
/**
* Handles clicking / pressing the button.
*
* @override
* @protected
* @returns {void}
*/
_onHandleClick() {
const { _isLiveStreamRunning, dispatch } = this.props;
if (_isLiveStreamRunning) {
dispatch(openDialog(StopLiveStreamDialog));
} else {
navigate(screen.conference.liveStream);
}
}
}
/**
* Maps (parts of) the redux state to the associated props for this component.
@ -24,4 +56,4 @@ export function mapStateToProps(state: Object, ownProps: Object) {
};
}
export default translate(connect(mapStateToProps)(AbstractLiveStreamButton));
export default translate(connect(mapStateToProps)(LiveStreamButton));

View File

@ -1,12 +1,15 @@
// @flow
import React from 'react';
import { View } from 'react-native';
import { Text, TouchableRipple } from 'react-native-paper';
import { CustomSubmitDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
import { connect } from '../../../../base/redux';
import BaseTheme from '../../../../base/ui/components/BaseTheme';
import { googleApi } from '../../../../google-api';
import { goBack }
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { setLiveStreamKey } from '../../../actions';
import AbstractStartLiveStreamDialog,
{ _mapStateToProps, type Props } from '../AbstractStartLiveStreamDialog';
@ -30,12 +33,47 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onOkPress = this._onOkPress.bind(this);
this._onStreamKeyChangeNative
= this._onStreamKeyChangeNative.bind(this);
this._onStreamKeyPick = this._onStreamKeyPick.bind(this);
this._onUserChanged = this._onUserChanged.bind(this);
}
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after this component is mounted.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
const { navigation, t } = this.props;
navigation.setOptions({
headerRight: () => (
<TouchableRipple
onPress = { this._onOkPress }
rippleColor = { BaseTheme.palette.screen01Header } >
<Text style = { styles.startLiveStreamLabel }>
{ t('dialog.start') }
</Text>
</TouchableRipple>
)
});
}
_onOkPress: () => void;
/**
* Starts live stream session and goes back to the previous screen.
*
* @returns {void}
*/
_onOkPress() {
this._onSubmit() && goBack();
}
/**
* Implements {@code Component}'s render.
*
@ -43,23 +81,18 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
*/
render() {
return (
<CustomSubmitDialog
okKey = 'dialog.startLiveStreaming'
onCancel = { this._onCancel }
onSubmit = { this._onSubmit } >
<View style = { styles.startDialogWrapper }>
<GoogleSigninForm
onUserChanged = { this._onUserChanged } />
<StreamKeyPicker
broadcasts = { this.state.broadcasts }
onChange = { this._onStreamKeyPick } />
<StreamKeyForm
onChange = { this._onStreamKeyChangeNative }
value = {
this.state.streamKey || this.props._streamKey
} />
</View>
</CustomSubmitDialog>
<JitsiScreen style = { styles.startLiveStreamContainer }>
<GoogleSigninForm
onUserChanged = { this._onUserChanged } />
<StreamKeyPicker
broadcasts = { this.state.broadcasts }
onChange = { this._onStreamKeyPick } />
<StreamKeyForm
onChange = { this._onStreamKeyChangeNative }
value = {
this.state.streamKey || this.props._streamKey
} />
</JitsiScreen>
);
}

View File

@ -5,6 +5,7 @@ import {
ColorPalette,
createStyleSheet
} from '../../../../base/styles';
import BaseTheme from '../../../../base/ui/components/BaseTheme';
/**
* Opacity of the TouchableHighlight.
@ -57,10 +58,23 @@ export default createStyleSheet({
},
/**
* Wrapper for the StartLiveStreamDialog form.
* Label for the button that starts live stream.
*/
startDialogWrapper: {
flexDirection: 'column'
startLiveStreamLabel: {
color: BaseTheme.palette.text01,
marginRight: 12
},
/**
* Container for the live stream screen.
*/
startLiveStreamContainer: {
display: 'flex',
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
marginHorizontal: BaseTheme.spacing[2],
marginTop: BaseTheme.spacing[3]
},
/**

View File

@ -1,6 +1,7 @@
// @flow
import { getToolbarButtons } from '../../../../base/config';
import { openDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { connect } from '../../../../base/redux';
import AbstractLiveStreamButton, {
@ -8,6 +9,33 @@ import AbstractLiveStreamButton, {
type Props
} from '../AbstractLiveStreamButton';
import {
StartLiveStreamDialog,
StopLiveStreamDialog
} from './index';
/**
* Button for opening the live stream settings dialog.
*/
class LiveStreamButton extends AbstractLiveStreamButton<Props> {
/**
* Handles clicking / pressing the button.
*
* @override
* @protected
* @returns {void}
*/
_onHandleClick() {
const { _isLiveStreamRunning, dispatch } = this.props;
dispatch(openDialog(
_isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog
));
}
}
/**
* Maps (parts of) the redux state to the associated props for the
* {@code LiveStreamButton} component.
@ -37,4 +65,4 @@ function _mapStateToProps(state: Object, ownProps: Props) {
};
}
export default translate(connect(_mapStateToProps)(AbstractLiveStreamButton));
export default translate(connect(_mapStateToProps)(LiveStreamButton));

View File

@ -4,7 +4,6 @@ import {
createToolbarEvent,
sendAnalytics
} from '../../../analytics';
import { openDialog } from '../../../base/dialog';
import { IconToggleRecording } from '../../../base/icons';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import {
@ -17,7 +16,6 @@ import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
import { FEATURES } from '../../../jaas/constants';
import { getActiveSession } from '../../functions';
import { StartRecordingDialog, StopRecordingDialog } from './_';
/**
* The type of the React {@code Component} props of
@ -70,6 +68,17 @@ export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *>
return this.props._tooltip || '';
}
/**
* Helper function to be implemented by subclasses, which should be used
* to handle the start recoding button being clicked / pressed.
*
* @protected
* @returns {void}
*/
_onHandleClick() {
// To be implemented by subclass.
}
/**
* Handles clicking / pressing the button.
*
@ -86,13 +95,10 @@ export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *>
'is_recording': _isRecordingRunning,
type: JitsiRecordingConstants.mode.FILE
}));
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));
if (!dialogShown) {
dispatch(openDialog(
_isRecordingRunning ? StopRecordingDialog : StartRecordingDialog
));
this._onHandleClick();
}
}

View File

@ -18,7 +18,7 @@ import { toggleRequestingSubtitles } from '../../../subtitles';
import { setSelectedRecordingService } from '../../actions';
import { RECORDING_TYPES } from '../../constants';
type Props = {
export type Props = {
/**
* Requests subtitles when recording is turned on.

View File

@ -2,10 +2,42 @@
import { Platform } from 'react-native';
import { openDialog } from '../../../../base/dialog';
import { IOS_RECORDING_ENABLED, RECORDING_ENABLED, getFeatureFlag } from '../../../../base/flags';
import { translate } from '../../../../base/i18n';
import { connect } from '../../../../base/redux';
import AbstractRecordButton, { _mapStateToProps as _abstractMapStateToProps } from '../AbstractRecordButton';
import { navigate }
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../../mobile/navigation/routes';
import type { Props } from '../../LiveStream/AbstractStartLiveStreamDialog';
import AbstractRecordButton,
{ _mapStateToProps as _abstractMapStateToProps } from '../AbstractRecordButton';
import { StopRecordingDialog } from './index';
/**
* Button for opening a screen where a recording session can be started.
*/
class RecordButton extends AbstractRecordButton<Props> {
/**
* Handles clicking / pressing the button.
*
* @override
* @protected
* @returns {void}
*/
_onHandleClick() {
const { _isRecordingRunning, dispatch } = this.props;
if (_isRecordingRunning) {
dispatch(openDialog(StopRecordingDialog));
} else {
navigate(screen.conference.recording);
}
}
}
/**
* Maps (parts of) the redux state to the associated props for this component.
@ -27,4 +59,4 @@ export function mapStateToProps(state: Object, ownProps: Object) {
};
}
export default translate(connect(mapStateToProps)(AbstractRecordButton));
export default translate(connect(mapStateToProps)(RecordButton));

View File

@ -1,14 +1,20 @@
// @flow
import React from 'react';
import { Text, TouchableRipple } from 'react-native-paper';
import { CustomSubmitDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
import { connect } from '../../../../base/redux';
import BaseTheme from '../../../../base/ui/components/BaseTheme';
import { goBack } from
'../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import AbstractStartRecordingDialog, {
type Props,
mapStateToProps
} from '../AbstractStartRecordingDialog';
import StartRecordingDialogContent from '../StartRecordingDialogContent';
import styles from '../styles.native';
/**
* React Component for getting confirmation to start a file recording session in
@ -16,7 +22,72 @@ import StartRecordingDialogContent from '../StartRecordingDialogContent';
*
* @augments Component
*/
class StartRecordingDialog extends AbstractStartRecordingDialog {
class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onOkPress = this._onOkPress.bind(this);
}
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after this component is mounted.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
const {
_fileRecordingsServiceEnabled,
_isDropboxEnabled,
navigation,
t
} = this.props;
const {
isTokenValid,
isValidating
} = this.state;
// disable ok button id recording service is shown only, when
// validating dropbox token, if that is not enabled we either always
// show the ok button or if just dropbox is enabled ok is available
// when there is token
const isOkDisabled
= _fileRecordingsServiceEnabled ? isValidating
: _isDropboxEnabled ? !isTokenValid : false;
navigation.setOptions({
headerRight: () => (
<TouchableRipple
disabled = { isOkDisabled }
onPress = { this._onOkPress }
rippleColor = { BaseTheme.palette.screen01Header } >
<Text style = { styles.startRecordingLabel }>
{ t('dialog.start') }
</Text>
</TouchableRipple>
)
});
}
_onOkPress: () => void;
/**
* Starts recording session and goes back to the previous screen.
*
* @returns {void}
*/
_onOkPress() {
this._onSubmit() && goBack();
}
/**
* Implements React's {@link Component#render()}.
*
@ -33,22 +104,11 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
} = this.state;
const {
_fileRecordingsServiceEnabled,
_fileRecordingsServiceSharingEnabled,
_isDropboxEnabled
_fileRecordingsServiceSharingEnabled
} = this.props;
// disable ok button id recording service is shown only, when
// validating dropbox token, if that is not enabled we either always
// show the ok button or if just dropbox is enabled ok is available
// when there is token
const isOkDisabled
= _fileRecordingsServiceEnabled ? isValidating
: _isDropboxEnabled ? !isTokenValid : false;
return (
<CustomSubmitDialog
okDisabled = { isOkDisabled }
onSubmit = { this._onSubmit } >
<JitsiScreen style = { styles.startRecodingContainer }>
<StartRecordingDialogContent
fileRecordingsServiceEnabled = { _fileRecordingsServiceEnabled }
fileRecordingsServiceSharingEnabled = { _fileRecordingsServiceSharingEnabled }
@ -61,7 +121,7 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
sharingSetting = { sharingEnabled }
spaceLeft = { spaceLeft }
userName = { userName } />
</CustomSubmitDialog>
</JitsiScreen>
);
}

View File

@ -1,7 +1,8 @@
// @flow
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { BoxModel, ColorPalette } from '../../../base/styles';
import { BoxModel } from '../../../base/styles';
import BaseTheme from '../../../base/ui/components/BaseTheme';
export const DROPBOX_LOGO = require('../../../../../images/dropboxLogo_square.png');
export const ICON_CLOUD = require('../../../../../images/icon-cloud.png');
@ -11,6 +12,28 @@ export const JITSI_LOGO = require('../../../../../images/jitsiLogo_square.png');
// the special case(s) of the recording feature below.
const _PADDING = BoxModel.padding * 1.5;
export default {
/**
* Container for the StartRecordingDialog screen.
*/
startRecodingContainer: {
display: 'flex',
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
marginHorizontal: BaseTheme.spacing[2],
marginTop: BaseTheme.spacing[3]
},
/**
* Label for the start recording button.
*/
startRecordingLabel: {
color: BaseTheme.palette.text01,
marginRight: 12
}
};
/**
* Color schemed styles for the @{code StartRecordingDialogContent} component.
*/
@ -39,20 +62,20 @@ ColorSchemeRegistry.register('StartRecordingDialogContent', {
},
recordingIcon: {
width: 24,
height: 24
width: BaseTheme.spacing[4],
height: BaseTheme.spacing[4]
},
signButton: {
backgroundColor: ColorPalette.blue,
color: ColorPalette.white,
backgroundColor: BaseTheme.palette.screen01Header,
color: BaseTheme.palette.ui12,
fontSize: 16,
borderRadius: 5,
borderRadius: BaseTheme.shape.borderRadius,
padding: BoxModel.padding * 0.5
},
switch: {
color: ColorPalette.white
color: BaseTheme.palette.ui12
},
title: {

View File

@ -1,6 +1,7 @@
// @flow
import { getToolbarButtons } from '../../../../base/config';
import { openDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { connect } from '../../../../base/redux';
import AbstractRecordButton, {
@ -8,6 +9,30 @@ import AbstractRecordButton, {
type Props
} from '../AbstractRecordButton';
import { StartRecordingDialog, StopRecordingDialog } from './index';
/**
* Button for opening a dialog where a recording session can be started.
*/
class RecordingButton extends AbstractRecordButton<Props> {
/**
* Handles clicking / pressing the button.
*
* @override
* @protected
* @returns {void}
*/
_onHandleClick() {
const { _isRecordingRunning, dispatch } = this.props;
dispatch(openDialog(
_isRecordingRunning ? StopRecordingDialog : StartRecordingDialog
));
}
}
/**
* Maps (parts of) the redux state to the associated props for the
* {@code RecordButton} component.
@ -37,4 +62,4 @@ export function _mapStateToProps(state: Object, ownProps: Props): Object {
};
}
export default translate(connect(_mapStateToProps)(AbstractRecordButton));
export default translate(connect(_mapStateToProps)(RecordingButton));