[RN] Make feature dialogs branded: recording

This commit is contained in:
Bettenbuk Zoltan 2018-10-18 10:32:12 +02:00 committed by Saúl Ibarra Corretgé
parent 4bc09dd8b9
commit 62e7fd7e8e
23 changed files with 285 additions and 217 deletions

View File

@ -3,6 +3,7 @@
import React, { Component } from 'react';
import { Container, Text } from '../../react';
import { type StyleType } from '../../styles';
import styles from './styles';
@ -11,7 +12,9 @@ type Props = {
/**
* Children of the component.
*/
children: string | React$Node
children: string | React$Node,
style: ?StyleType
};
/**
@ -25,10 +28,10 @@ export default class DialogContent extends Component<Props> {
* @inheritdoc
*/
render() {
const { children } = this.props;
const { children, style } = this.props;
const childrenComponent = typeof children === 'string'
? <Text>{ children }</Text>
? <Text style = { style }>{ children }</Text>
: children;
return (

View File

@ -38,6 +38,7 @@ export default createStyleSheet({
borderColor: ColorPalette.lightGrey,
borderRadius: 3,
borderWidth: 1,
color: ColorPalette.white,
height: BUTTON_HEIGHT,
justifyContent: 'center'
},

View File

@ -1,12 +1,11 @@
// @flow
import React, { Component } from 'react';
import { Component } from 'react';
import {
createRecordingDialogEvent,
sendAnalytics
} from '../../../analytics';
import { Dialog } from '../../../base/dialog';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
/**
@ -140,27 +139,6 @@ export default class AbstractStartLiveStreamDialog<P: Props>
this._isMounted = false;
}
/**
* Implements {@code Component}'s render.
*
* @inheritdoc
*/
render() {
return (
<Dialog
cancelTitleKey = 'dialog.Cancel'
okTitleKey = 'dialog.startLiveStreaming'
onCancel = { this._onCancel }
onSubmit = { this._onSubmit }
titleKey = 'liveStreaming.start'
width = { 'small' }>
{
this._renderDialogContent()
}
</Dialog>
);
}
_onCancel: () => boolean;
/**
@ -257,13 +235,6 @@ export default class AbstractStartLiveStreamDialog<P: Props>
this.setState(newState);
}
}
/**
* Renders the platform specific dialog content.
*
* @returns {React$Component}
*/
_renderDialogContent: () => React$Component<*>
}
/**

View File

@ -1,8 +1,7 @@
// @flow
import React, { Component } from 'react';
import { Component } from 'react';
import { Dialog } from '../../../base/dialog';
import {
createRecordingDialogEvent,
sendAnalytics
@ -53,24 +52,6 @@ export default class AbstractStopLiveStreamDialog extends Component<Props> {
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<Dialog
okTitleKey = 'dialog.stopLiveStreaming'
onSubmit = { this._onSubmit }
titleKey = 'dialog.liveStreaming'
width = 'small'>
{ this._renderDialogContent() }
</Dialog>
);
}
_onSubmit: () => boolean;
/**
@ -90,14 +71,6 @@ export default class AbstractStopLiveStreamDialog extends Component<Props> {
return true;
}
/**
* Function to be implemented by the platform specific implementations.
*
* @private
* @returns {React$Component<*>}
*/
_renderDialogContent: () => React$Component<*>
}
/**

View File

@ -126,14 +126,16 @@ class GoogleSigninForm extends Component<Props> {
return null;
}
const userInfo = signedInUser
? `${t('liveStreaming.signedInAs')} ${signedInUser}`
: t('liveStreaming.signInCTA');
return (
<View style = { styles.formWrapper }>
<View style = { styles.helpText }>
{ signedInUser ? <Text>
{ `${t('liveStreaming.signedInAs')} ${signedInUser}` }
</Text> : <Text>
{ t('liveStreaming.signInCTA') }
</Text> }
<Text style = { styles.text }>
{ userInfo }
</Text>
</View>
<GoogleSignInButton
onClick = { this._onGoogleButtonPress }

View File

@ -4,6 +4,7 @@ import React from 'react';
import { View } from 'react-native';
import { connect } from 'react-redux';
import { CustomSubmitDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { googleApi } from '../../../../google-api';
@ -36,9 +37,39 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
= this._onStreamKeyChangeNative.bind(this);
this._onStreamKeyPick = this._onStreamKeyPick.bind(this);
this._onUserChanged = this._onUserChanged.bind(this);
this._renderDialogContent = this._renderDialogContent.bind(this);
}
/**
* Implements {@code Component}'s render.
*
* @inheritdoc
*/
render() {
return (
<CustomSubmitDialog
okTitleKey = '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>
);
}
_onCancel: () => boolean;
_onSubmit: () => boolean;
_onStreamKeyChange: string => void
_onStreamKeyChangeNative: string => void;
@ -102,29 +133,6 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
});
}
}
_renderDialogContent: () => React$Component<*>
/**
* Renders the platform specific dialog content.
*
* @returns {React$Component}
*/
_renderDialogContent() {
return (
<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>
);
}
}
export default translate(connect(_mapStateToProps)(StartLiveStreamDialog));

View File

@ -3,7 +3,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { DialogContent } from '../../../../base/dialog';
import { ConfirmDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import AbstractStopLiveStreamDialog, {
@ -19,19 +19,20 @@ import AbstractStopLiveStreamDialog, {
class StopLiveStreamDialog extends AbstractStopLiveStreamDialog {
/**
* Renders the platform specific {@code Dialog} content.
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
_renderDialogContent() {
render() {
return (
<DialogContent>
{
this.props.t('dialog.stopStreamingWarning')
}
</DialogContent>
<ConfirmDialog
contentKey = 'dialog.stopStreamingWarning'
onSubmit = { this._onSubmit } />
);
}
_onSubmit: () => boolean
}
export default translate(connect(_mapStateToProps)(StopLiveStreamDialog));

View File

@ -9,7 +9,7 @@ import AbstractStreamKeyForm, {
type Props
} from '../AbstractStreamKeyForm';
import styles from './styles';
import styles, { PLACEHOLDER_COLOR } from './styles';
/**
* A React Component for entering a key for starting a YouTube live stream.
@ -49,12 +49,13 @@ class StreamKeyForm extends AbstractStreamKeyForm {
<TextInput
onChangeText = { this._onInputChange }
placeholder = { t('liveStreaming.enterStreamKey') }
placeholderTextColor = { PLACEHOLDER_COLOR }
style = { styles.streamKeyInput }
value = { this.props.value } />
<TouchableOpacity
onPress = { this._onOpenHelp }
style = { styles.streamKeyHelp } >
<Text>
<Text style = { styles.text }>
{
t('liveStreaming.streamIdHelp')
}

View File

@ -74,7 +74,7 @@ class StreamKeyPicker extends Component<Props, State> {
return (
<View style = { styles.formWrapper }>
<View style = { styles.streamKeyPickerCta }>
<Text>
<Text style = { styles.text }>
{ this.props.t('liveStreaming.choose') }
</Text>
</View>
@ -90,7 +90,7 @@ class StreamKeyPicker extends Component<Props, State> {
? styles.streamKeyPickerItemHighlight : null
] }
underlayColor = { TOUCHABLE_UNDERLAY }>
<Text style = { styles.streamKeyPickerItemText }>
<Text style = { styles.text }>
{ broadcast.title }
</Text>
</TouchableHighlight>))

View File

@ -11,6 +11,11 @@ import {
*/
export const ACTIVE_OPACITY = 0.3;
/**
* Color for the key input field placeholder.
*/
export const PLACEHOLDER_COLOR = ColorPalette.lightGrey;
/**
* Underlay of the TouchableHighlight.
*/
@ -69,14 +74,19 @@ export default createStyleSheet({
*/
streamKeyInput: {
alignSelf: 'stretch',
height: 50
borderColor: ColorPalette.lightGrey,
borderBottomWidth: 1,
color: ColorPalette.white,
height: 40,
marginBottom: 5
},
/**
* Label for the previous field.
*/
streamKeyInputLabel: {
alignSelf: 'flex-start'
alignSelf: 'flex-start',
color: ColorPalette.white
},
/**
@ -108,7 +118,7 @@ export default createStyleSheet({
* Additional style for the selected item.
*/
streamKeyPickerItemHighlight: {
backgroundColor: ColorPalette.lighterGrey
backgroundColor: ColorPalette.darkGrey
},
/**
@ -119,6 +129,10 @@ export default createStyleSheet({
borderRadius: 3,
borderWidth: 1,
flexDirection: 'column'
},
text: {
color: ColorPalette.white
}
});

View File

@ -4,6 +4,7 @@ import Spinner from '@atlaskit/spinner';
import React from 'react';
import { connect } from 'react-redux';
import { Dialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import {
@ -59,8 +60,6 @@ class StartLiveStreamDialog
this._onRequestGoogleSignIn = this._onRequestGoogleSignIn.bind(this);
this._onYouTubeBroadcastIDSelected
= this._onYouTubeBroadcastIDSelected.bind(this);
this._renderDialogContent = this._renderDialogContent.bind(this);
}
/**
@ -78,6 +77,39 @@ class StartLiveStreamDialog
}
}
/**
* Implements {@code Component}'s render.
*
* @inheritdoc
*/
render() {
const { _googleApiApplicationClientID } = this.props;
return (
<Dialog
cancelTitleKey = 'dialog.Cancel'
okTitleKey = 'dialog.startLiveStreaming'
onCancel = { this._onCancel }
onSubmit = { this._onSubmit }
titleKey = 'liveStreaming.start'
width = { 'small' }>
<div className = 'live-stream-dialog'>
{ _googleApiApplicationClientID
? this._renderYouTubePanel() : null }
<StreamKeyForm
onChange = { this._onStreamKeyChange }
value = {
this.state.streamKey || this.props._streamKey
} />
</div>
</Dialog>
);
}
_onCancel: () => boolean;
_onSubmit: () => boolean;
_onInitializeGoogleApi: () => Promise<*>;
/**
@ -221,27 +253,6 @@ class StartLiveStreamDialog
});
}
_renderDialogContent: () => React$Component<*>
/**
* Renders the platform specific dialog content.
*
* @returns {React$Component}
*/
_renderDialogContent() {
const { _googleApiApplicationClientID } = this.props;
return (
<div className = 'live-stream-dialog'>
{ _googleApiApplicationClientID
? this._renderYouTubePanel() : null }
<StreamKeyForm
onChange = { this._onStreamKeyChange }
value = { this.state.streamKey || this.props._streamKey } />
</div>
);
}
/**
* Renders a React Element for authenticating with the Google web client.
*

View File

@ -1,7 +1,9 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { Dialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import AbstractStopLiveStreamDialog, {
@ -17,13 +19,24 @@ import AbstractStopLiveStreamDialog, {
class StopLiveStreamDialog extends AbstractStopLiveStreamDialog {
/**
* Renders the platform specific {@code Dialog} content.
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
_renderDialogContent() {
return this.props.t('dialog.stopStreamingWarning');
render() {
return (
<Dialog
okTitleKey = 'dialog.stopLiveStreaming'
onSubmit = { this._onSubmit }
titleKey = 'dialog.liveStreaming'
width = 'small'>
{ this.props.t('dialog.stopStreamingWarning') }
</Dialog>
);
}
_onSubmit: () => boolean;
}
export default translate(connect(_mapStateToProps)(StopLiveStreamDialog));

View File

@ -18,8 +18,7 @@ import {
import { getActiveSession } from '../../functions';
import StartRecordingDialog from './StartRecordingDialog';
import { StopRecordingDialog } from './_';
import { StartRecordingDialog, StopRecordingDialog } from './_';
/**
* The type of the React {@code Component} props of

View File

@ -1,21 +1,17 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Component } from 'react';
import {
createRecordingDialogEvent,
sendAnalytics
} from '../../../analytics';
import { Dialog } from '../../../base/dialog';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import {
getDropboxData,
isEnabled as isDropboxEnabled
} from '../../../dropbox';
import StartRecordingDialogContent from './StartRecordingDialogContent';
type Props = {
/**
@ -75,7 +71,7 @@ type State = {
/**
* Component for the recording start dialog.
*/
class StartRecordingDialog extends Component<Props, State> {
class AbstractStartRecordingDialog extends Component<Props, State> {
/**
* Initializes a new {@code StartRecordingDialog} instance.
*
@ -93,6 +89,8 @@ class StartRecordingDialog extends Component<Props, State> {
userName: undefined,
spaceLeft: undefined
};
this._onSubmit = this._onSubmit.bind(this);
}
/**
@ -113,7 +111,7 @@ class StartRecordingDialog extends Component<Props, State> {
* @inheritdoc
* @returns {void}
*/
componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: Props) {
if (this.props._token !== prevProps._token) {
this._onTokenUpdated();
}
@ -158,33 +156,6 @@ class StartRecordingDialog extends Component<Props, State> {
}
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { isTokenValid, isValidating, spaceLeft, userName } = this.state;
const { _isDropboxEnabled } = this.props;
return (
<Dialog
okDisabled = { !isTokenValid && _isDropboxEnabled }
okTitleKey = 'dialog.confirm'
onSubmit = { this._onSubmit }
titleKey = 'dialog.recording'
width = 'small'>
<StartRecordingDialogContent
integrationsEnabled = { _isDropboxEnabled }
isTokenValid = { isTokenValid }
isValidating = { isValidating }
spaceLeft = { spaceLeft }
userName = { userName } />
</Dialog>
);
}
_onSubmit: () => boolean;
/**
@ -240,7 +211,7 @@ class StartRecordingDialog extends Component<Props, State> {
* _token: string
* }}
*/
function mapStateToProps(state: Object) {
export function mapStateToProps(state: Object) {
const { dropbox = {} } = state['features/base/config'];
return {
@ -251,4 +222,4 @@ function mapStateToProps(state: Object) {
};
}
export default connect(mapStateToProps)(StartRecordingDialog);
export default AbstractStartRecordingDialog;

View File

@ -1,12 +1,11 @@
// @flow
import React, { Component } from 'react';
import { Component } from 'react';
import {
createRecordingDialogEvent,
sendAnalytics
} from '../../../analytics';
import { Dialog } from '../../../base/dialog';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { getActiveSession } from '../../functions';
@ -53,24 +52,6 @@ export default class AbstractStopRecordingDialog<P: Props>
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<Dialog
okTitleKey = 'dialog.confirm'
onSubmit = { this._onSubmit }
titleKey = 'dialog.recording'
width = 'small'>
{ this._renderDialogContent() }
</Dialog>
);
}
_onSubmit: () => boolean;
/**
@ -90,14 +71,6 @@ export default class AbstractStopRecordingDialog<P: Props>
return true;
}
/**
* Renders the platform specific dialog content.
*
* @protected
* @returns {React$Component}
*/
_renderDialogContent: () => React$Component<*>
}
/**

View File

@ -85,7 +85,7 @@ class StartRecordingDialogContent extends Component<Props> {
* @returns {React$Component}
*/
render() {
if (this.props.integrationsEnabled) {
if (this.props.integrationsEnabled === true) { // explicit true needed
return this._renderIntegrationsContent();
}
@ -99,7 +99,7 @@ class StartRecordingDialogContent extends Component<Props> {
*/
_renderNoIntegrationsContent() {
return (
<DialogContent>
<DialogContent style = { styles.noIntegrationContent }>
{ this.props.t('recording.startRecordingBody') }
</DialogContent>
);
@ -195,12 +195,12 @@ class StartRecordingDialogContent extends Component<Props> {
className = 'logged-in-panel'
style = { styles.loggedIn }>
<Container>
<Text>
<Text style = { styles.text }>
{ t('recording.loggedIn', { userName }) }
</Text>
</Container>
<Container>
<Text>
<Text style = { styles.text }>
{
t('recording.availableSpace', {
spaceLeft,
@ -211,7 +211,9 @@ class StartRecordingDialogContent extends Component<Props> {
</Container>
</Container>
<Container style = { styles.startRecordingText }>
<Text>{ t('recording.startRecordingBody') }</Text>
<Text style = { styles.text }>
{ t('recording.startRecordingBody') }
</Text>
</Container>
</Container>
);

View File

@ -0,0 +1,54 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { translate } from '../../../../base/i18n';
import { ConfirmDialog, CustomSubmitDialog } from '../../../../base/dialog';
import AbstractStartRecordingDialog, {
mapStateToProps
} from '../AbstractStartRecordingDialog';
import StartRecordingDialogContent from '../StartRecordingDialogContent';
/**
* React Component for getting confirmation to start a file recording session in
* progress.
*
* @extends Component
*/
class StartRecordingDialog extends AbstractStartRecordingDialog {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
const { isTokenValid, isValidating, spaceLeft, userName } = this.state;
const { _isDropboxEnabled } = this.props;
if (!_isDropboxEnabled) {
return (
<ConfirmDialog
contentKey = 'recording.startRecordingBody'
onSubmit = { this._onSubmit } />
);
}
return (
<CustomSubmitDialog
okDisabled = { !isTokenValid }
onSubmit = { this._onSubmit } >
<StartRecordingDialogContent
isTokenValid = { isTokenValid }
isValidating = { isValidating }
spaceLeft = { spaceLeft }
userName = { userName } />
</CustomSubmitDialog>
);
}
_onSubmit: () => boolean
}
export default translate(connect(mapStateToProps)(StartRecordingDialog));

View File

@ -3,7 +3,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { DialogContent } from '../../../../base/dialog';
import { ConfirmDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import AbstractStopRecordingDialog, {
@ -20,19 +20,19 @@ import AbstractStopRecordingDialog, {
class StopRecordingDialog extends AbstractStopRecordingDialog<Props> {
/**
* Renders the platform specific dialog content.
* Implements {@code Component#render}.
*
* @inheritdoc
*/
_renderDialogContent() {
const { t } = this.props;
render() {
return (
<DialogContent>
{ t('dialog.stopRecordingWarning') }
</DialogContent>
<ConfirmDialog
contentKey = 'dialog.stopRecordingWarning'
onSubmit = { this._onSubmit } />
);
}
_onSubmit: () => boolean
}
export default translate(connect(_mapStateToProps)(StopRecordingDialog));

View File

@ -1,4 +1,5 @@
// @flow
export { default as RecordButton } from './RecordButton';
export { default as StartRecordingDialog } from './StartRecordingDialog';
export { default as StopRecordingDialog } from './StopRecordingDialog';

View File

@ -1,6 +1,6 @@
// @flow
import { BoxModel, createStyleSheet } from '../../../base/styles';
import { BoxModel, createStyleSheet, ColorPalette } from '../../../base/styles';
// XXX The "standard" {@code BoxModel.padding} has been deemed insufficient in
// the special case(s) of the recording feature bellow.
@ -28,16 +28,26 @@ export default createStyleSheet({
paddingBottom: _PADDING
},
noIntegrationContent: {
color: ColorPalette.white
},
startRecordingText: {
paddingBottom: _PADDING
},
switch: {
color: ColorPalette.white,
paddingRight: BoxModel.padding
},
title: {
color: ColorPalette.white,
fontSize: 16,
fontWeight: 'bold'
},
text: {
color: ColorPalette.white
}
});

View File

@ -0,0 +1,50 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { translate } from '../../../../base/i18n';
import { Dialog } from '../../../../base/dialog';
import AbstractStartRecordingDialog, {
mapStateToProps
} from '../AbstractStartRecordingDialog';
import StartRecordingDialogContent from '../StartRecordingDialogContent';
/**
* React Component for getting confirmation to start a file recording session in
* progress.
*
* @extends Component
*/
class StartRecordingDialog extends AbstractStartRecordingDialog {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
const { isTokenValid, isValidating, spaceLeft, userName } = this.state;
const { _isDropboxEnabled } = this.props;
return (
<Dialog
okDisabled = { !isTokenValid && _isDropboxEnabled }
okTitleKey = 'dialog.confirm'
onSubmit = { this._onSubmit }
titleKey = 'dialog.recording'
width = 'small'>
<StartRecordingDialogContent
integrationsEnabled = { _isDropboxEnabled }
isTokenValid = { isTokenValid }
isValidating = { isValidating }
spaceLeft = { spaceLeft }
userName = { userName } />
</Dialog>
);
}
_onSubmit: () => boolean
}
export default translate(connect(mapStateToProps)(StartRecordingDialog));

View File

@ -1,8 +1,10 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { translate } from '../../../../base/i18n';
import { Dialog } from '../../../../base/dialog';
import AbstractStopRecordingDialog, {
type Props,
@ -16,20 +18,27 @@ import AbstractStopRecordingDialog, {
* @extends Component
*/
class StopRecordingDialog extends AbstractStopRecordingDialog<Props> {
/**
* Renders the platform specific dialog content.
* Implements React's {@link Component#render()}.
*
* @protected
* @returns {React$Component}
* @inheritdoc
* @returns {ReactElement}
*/
_renderDialogContent() {
render() {
const { t } = this.props;
return (
t('dialog.stopRecordingWarning')
<Dialog
okTitleKey = 'dialog.confirm'
onSubmit = { this._onSubmit }
titleKey = 'dialog.recording'
width = 'small'>
{ t('dialog.stopRecordingWarning') }
</Dialog>
);
}
_onSubmit: () => boolean
}
export default translate(connect(_mapStateToProps)(StopRecordingDialog));

View File

@ -1,4 +1,5 @@
// @flow
export { default as RecordButton } from './RecordButton';
export { default as StartRecordingDialog } from './StartRecordingDialog';
export { default as StopRecordingDialog } from './StopRecordingDialog';