feat(dialog) start recording/live stream screens, new AlertDialog
This commit is contained in:
parent
f38702bb65
commit
8a6b6f2942
|
@ -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!",
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -135,8 +135,7 @@ class ConfirmDialog extends AbstractDialog<Props> {
|
|||
|
||||
return (
|
||||
<View>
|
||||
<Dialog.Container
|
||||
visible = { true }>
|
||||
<Dialog.Container visible = { true }>
|
||||
{
|
||||
title && <Dialog.Title>
|
||||
{ t(title) }
|
||||
|
|
|
@ -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));
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -24,6 +24,8 @@ export const screen = {
|
|||
}
|
||||
},
|
||||
security: 'Security Options',
|
||||
recording: 'Recording',
|
||||
liveStream: 'Live stream',
|
||||
speakerStats: 'Speaker Stats',
|
||||
participants: 'Participants',
|
||||
invite: 'Invite',
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue