279 lines
8.3 KiB
JavaScript
Executable file
279 lines
8.3 KiB
JavaScript
Executable file
/* @flow */
|
|
|
|
import React from 'react';
|
|
import Flux from '../lib/flux';
|
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
|
import lodash from 'lodash';
|
|
import i18n from '../i18n';
|
|
import TransitionGroup from '../lib/transitions/TransitionGroup';
|
|
import Animated from '../lib/animated';
|
|
import {createSound} from '../utils/SoundUtils';
|
|
import type {Sound} from '../utils/SoundUtils';
|
|
import Flex from '../uikit/Flex';
|
|
import Button from '../uikit/Button';
|
|
import CallAvatar from '../uikit/CallAvatar';
|
|
import createExperimentContainer from './experiments/ExperimentContainer';
|
|
import Draggable from './common/Draggable';
|
|
import UserSettingsStore from '../stores/UserSettingsStore';
|
|
import WindowStore from '../stores/WindowStore';
|
|
import StreamerModeStore from '../stores/StreamerModeStore';
|
|
import IncomingCallStore from '../stores/IncomingCallStore';
|
|
import NotificationStore from '../stores/NotificationStore';
|
|
import PrivateChannelCallStore from '../stores/views/PrivateChannelCallStore';
|
|
import VideoCallExperimentStore from '../stores/experiments/VideoCallExperimentStore';
|
|
import AudioActionCreators from '../actions/AudioActionCreators';
|
|
import IncomingCallActionCreators from '../actions/IncomingCallActionCreators';
|
|
import CallActionCreators from '../actions/CallActionCreators';
|
|
import SelectedChannelActionCreators from '../actions/SelectedChannelActionCreators';
|
|
import ChannelRecord from '../records/ChannelRecord';
|
|
import ComponentDispatchMixin from '../mixins/ComponentDispatchMixin';
|
|
import {ComponentActions, ExperimentBuckets, ExperimentTypes, CallModes} from '../Constants';
|
|
import '../styles/incoming_calls.styl';
|
|
|
|
const IncomingCallModal = React.createClass({
|
|
mixins: [Flux.LazyStoreListenerMixin(PrivateChannelCallStore), PureRenderMixin, ComponentDispatchMixin],
|
|
|
|
getInitialState() {
|
|
return {
|
|
animate: new Animated.Value(0),
|
|
...this.getStateFromStores(),
|
|
};
|
|
},
|
|
|
|
getStateFromStores() {
|
|
return {
|
|
mode: PrivateChannelCallStore.getCallMode(this.props.channel.id),
|
|
};
|
|
},
|
|
|
|
getSubscriptions() {
|
|
return {
|
|
[ComponentActions.CALL_ACCEPT]: this.handleAcceptWithAudio,
|
|
[ComponentActions.CALL_DECLINE]: this.handleDeclineCall,
|
|
};
|
|
},
|
|
|
|
componentWillEnter(callback) {
|
|
Animated.timing(this.state.animate, {
|
|
toValue: 1,
|
|
duration: 170,
|
|
easing: Animated.Easing.inOut(Animated.Easing.back()),
|
|
}).start(callback);
|
|
},
|
|
|
|
componentDidAppear() {
|
|
this.state.animate.setValue(1);
|
|
},
|
|
|
|
componentWillLeave(callback) {
|
|
Animated.timing(this.state.animate, {
|
|
toValue: 0,
|
|
duration: 170,
|
|
easing: Animated.Easing.quad,
|
|
}).start(callback);
|
|
},
|
|
|
|
getStyle() {
|
|
const {animate} = this.state;
|
|
if (this.props.shouldAnimate) {
|
|
return Animated.accelerate({
|
|
transform: [
|
|
{
|
|
scale: animate.interpolate({
|
|
inputRange: [0, 1],
|
|
outputRange: ['0.7', '1'],
|
|
}),
|
|
},
|
|
],
|
|
opacity: animate,
|
|
});
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
renderAllActions() {
|
|
return [
|
|
<Button
|
|
key="video"
|
|
className="action-button margin-top-4"
|
|
onClick={this.handleAcceptWithVideo}
|
|
color={Button.Colors.GREEN}>
|
|
<div className="flex-horizontal flex-align-center">
|
|
<img className="action-icon" src={require('../images/calls/ic_videocam_white_16px.svg')} />
|
|
{i18n.Messages.VIDEO}
|
|
</div>
|
|
</Button>,
|
|
<Button
|
|
key="audio"
|
|
className="action-button margin-top-4"
|
|
onClick={this.handleAcceptWithAudio}
|
|
color={Button.Colors.GREEN}>
|
|
<div className="flex-horizontal flex-align-center">
|
|
<img className="action-icon" src={require('../images/calls/ic_mic_white_16px.svg')} />
|
|
{i18n.Messages.VOICE}
|
|
</div>
|
|
</Button>,
|
|
];
|
|
},
|
|
|
|
renderAudioOnlyActions() {
|
|
return (
|
|
<Button className="action-button margin-top-8" onClick={this.handleAcceptWithAudio} color={Button.Colors.GREEN}>
|
|
<div className="flex-horizontal flex-align-center">
|
|
<img className="action-icon" src={require('../images/calls/ic_mic_white_16px.svg')} />
|
|
{i18n.Messages.JOIN_CALL}
|
|
</div>
|
|
</Button>
|
|
);
|
|
},
|
|
|
|
renderActions() {
|
|
return (
|
|
<div className="action-buttons margin-top-20">
|
|
{this.props.video && this.state.mode === CallModes.VIDEO
|
|
? this.renderAllActions()
|
|
: this.renderAudioOnlyActions()}
|
|
<Button
|
|
className="action-button action-button-decline margin-top-8"
|
|
onClick={this.handleDeclineCall}
|
|
color={Button.Colors.TRANSPARENT}>
|
|
<div className="flex-horizontal flex-align-center">
|
|
{i18n.Messages.DECLINE}
|
|
</div>
|
|
</Button>
|
|
</div>
|
|
);
|
|
},
|
|
|
|
render() {
|
|
const {x, y, channel, shouldAnimate} = this.props;
|
|
const avatarURL = channel.getIconURL();
|
|
|
|
return (
|
|
<Draggable
|
|
className="incoming-call elevation-high"
|
|
selector=".incoming-call-inner"
|
|
initialX={x}
|
|
initialY={y}
|
|
maxX={window.innerWidth}
|
|
maxY={window.innerHeight}
|
|
onDragEnd={this.handleStop}
|
|
disableFlip
|
|
dragAnywhere>
|
|
<Animated.div className="incoming-call-inner" style={this.getStyle()}>
|
|
<Flex direction={Flex.Direction.VERTICAL} align={Flex.Align.CENTER}>
|
|
<CallAvatar className="margin-top-20" ringing={shouldAnimate} src={avatarURL || ''} id={channel.id} />
|
|
<div className="members margin-top-20">{channel.toString()}</div>
|
|
<div className="subtitle margin-top-8">
|
|
{this.state.mode === CallModes.VOICE
|
|
? i18n.Messages.INCOMING_CALL_ELLIPSIS
|
|
: i18n.Messages.INCOMING_VIDEO_CALL_ELLIPSIS}
|
|
</div>
|
|
{this.renderActions()}
|
|
</Flex>
|
|
</Animated.div>
|
|
</Draggable>
|
|
);
|
|
},
|
|
|
|
handleStop(x, y) {
|
|
IncomingCallActionCreators.move(x, y);
|
|
},
|
|
|
|
handleDeclineCall() {
|
|
CallActionCreators.stopRinging(this.props.channel.id);
|
|
},
|
|
|
|
handleAcceptWithAudio() {
|
|
const {channel} = this.props;
|
|
SelectedChannelActionCreators.selectChannel(null, channel.id);
|
|
SelectedChannelActionCreators.selectVoiceChannel(null, channel.id);
|
|
},
|
|
|
|
handleAcceptWithVideo() {
|
|
AudioActionCreators.setVideoEnabled(true, this.props.channel.id);
|
|
this.handleAcceptWithAudio();
|
|
},
|
|
});
|
|
|
|
type IncomingCallsState = {
|
|
incomingCalls: {[key: string]: {channel: ChannelRecord, x: number, y: number}},
|
|
ringing: boolean,
|
|
isFocused: boolean,
|
|
disableSounds: boolean,
|
|
ringingSound: Sound,
|
|
};
|
|
|
|
/**
|
|
* Get all incoming calls and render modals
|
|
*/
|
|
const IncomingCalls = React.createClass({
|
|
mixins: [
|
|
Flux.LazyStoreListenerMixin(IncomingCallStore, StreamerModeStore, WindowStore, UserSettingsStore),
|
|
PureRenderMixin,
|
|
],
|
|
|
|
getInitialState() {
|
|
return {
|
|
ringingSound: createSound(lodash.random(1, 1000) === 500 ? 'call_ringing_beat' : 'call_ringing'),
|
|
...this.getStateFromStores(),
|
|
};
|
|
},
|
|
|
|
getStateFromStores() {
|
|
return {
|
|
incomingCalls: IncomingCallStore.getIncomingCalls(),
|
|
ringing: IncomingCallStore.hasIncomingCalls(),
|
|
isFocused: WindowStore.isFocused(),
|
|
disableSounds: StreamerModeStore.disableSounds,
|
|
theme: UserSettingsStore.theme,
|
|
};
|
|
},
|
|
|
|
componentWillUpdate(nextProps: any, nextState: IncomingCallsState) {
|
|
const {ringing} = nextState;
|
|
if (this.state.ringing !== ringing) {
|
|
if (ringing) {
|
|
this.startRinging();
|
|
} else {
|
|
this.stopRinging();
|
|
}
|
|
}
|
|
},
|
|
|
|
render() {
|
|
const {incomingCalls, isFocused, theme} = this.state;
|
|
return (
|
|
<TransitionGroup className={`theme-${theme}`}>
|
|
{lodash.map(incomingCalls, props =>
|
|
<IncomingCallModal key={props.channel.id} shouldAnimate={isFocused} video={this.props.video} {...props} />
|
|
)}
|
|
</TransitionGroup>
|
|
);
|
|
},
|
|
|
|
// Utils
|
|
|
|
startRinging() {
|
|
if (this.state.disableSounds || NotificationStore.isSoundDisabled('call_ringing')) return;
|
|
this.state.ringingSound.loop();
|
|
},
|
|
|
|
stopRinging() {
|
|
this.state.ringingSound.stop();
|
|
},
|
|
});
|
|
|
|
export default createExperimentContainer(VideoCallExperimentStore, {
|
|
[ExperimentTypes.NONE]: props => <IncomingCalls {...props} />,
|
|
|
|
[ExperimentTypes.DEVELOPER]: {
|
|
[ExperimentBuckets.CONTROL]: props => <IncomingCalls video {...props} />,
|
|
},
|
|
});
|
|
|
|
|
|
|
|
// WEBPACK FOOTER //
|
|
// ./discord_app/components/IncomingCalls.js
|