VolumeSlider combined with volume mute button in VolumeBar

This commit is contained in:
NikolaBorislavovHristov 2018-12-19 11:08:08 +02:00
parent cc83ac40cc
commit 1bb91b3db8
8 changed files with 162 additions and 126 deletions

View file

@ -1,13 +1,20 @@
import React, { Component, Fragment } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Icon from 'stremio-icons/dom';
import { Popup } from 'stremio-common';
import SeekBar from './SeekBar';
import VolumeSlider from './VolumeSlider';
import VolumeBar from './VolumeBar';
import SubtitlesPicker from './SubtitlesPicker';
import styles from './styles';
//TODO move this in separate file
const ControlBarButton = React.forwardRef(({ active, icon, onClick }, ref) => (
<div ref={ref} className={classnames(styles['control-bar-button'], { [styles['active']]: active })} onClick={onClick}>
<Icon className={styles['icon']} icon={icon} />
</div>
));
class ControlBar extends Component {
constructor(props) {
super(props);
@ -42,11 +49,15 @@ class ControlBar extends Component {
this.props.setSelectedSubtitleTrackId(selectedSubtitleTrackId);
}
toogleVolumeMute = () => {
this.props.volume === 0 ? this.props.unmute() : this.props.mute();
mute = () => {
this.props.mute();
}
onPlayPauseButtonClick = () => {
unmute = () => {
this.props.unmute();
}
togglePaused = () => {
this.props.paused ? this.props.play() : this.props.pause();
}
@ -84,32 +95,23 @@ class ControlBar extends Component {
const icon = this.props.paused ? 'ic_play' : 'ic_pause';
return (
<div className={styles['control-bar-button']} onClick={this.onPlayPauseButtonClick}>
<Icon className={styles['icon']} icon={icon} />
</div>
<ControlBarButton
icon={icon}
onClick={this.togglePaused}
/>
);
}
renderVolumeBar() {
if (this.props.volume === null) {
return null;
}
const icon = this.props.volume === 0 ? 'ic_volume0' :
this.props.volume < 50 ? 'ic_volume1' :
this.props.volume < 100 ? 'ic_volume2' :
'ic_volume3';
return (
<Fragment>
<div className={styles['control-bar-button']} onClick={this.toogleVolumeMute}>
<Icon className={styles['icon']} icon={icon} />
</div>
<VolumeSlider
className={styles['volume-slider']}
volume={this.props.volume}
setVolume={this.setVolume}
/>
</Fragment>
<VolumeBar
className={styles['volume-bar']}
toggleButtonComponent={ControlBarButton}
volume={this.props.volume}
setVolume={this.setVolume}
mute={this.mute}
unmute={this.unmute}
/>
);
}
@ -117,9 +119,7 @@ class ControlBar extends Component {
return (
<Popup className={styles['popup-container']} border={true} onOpen={this.onSharePopupOpen} onClose={this.onSharePopupClose}>
<Popup.Label>
<div className={classnames(styles['control-bar-button'], { [styles['active']]: this.state.sharePopupOpen })}>
<Icon className={styles['icon']} icon={'ic_share'} />
</div>
<ControlBarButton active={this.state.sharePopupOpen} icon={'ic_share'} />
</Popup.Label>
<Popup.Menu>
<div className={classnames(styles['popup-content'], styles['share-popup-content'])} />
@ -136,9 +136,7 @@ class ControlBar extends Component {
return (
<Popup className={styles['popup-container']} border={true} onOpen={this.onSubtitlesPopupOpen} onClose={this.onSubtitlesPopupClose}>
<Popup.Label>
<div className={classnames(styles['control-bar-button'], { [styles['active']]: this.state.subtitlesPopupOpen })}>
<Icon className={styles['icon']} icon={'ic_sub'} />
</div>
<ControlBarButton active={this.state.subtitlesPopupOpen} icon={'ic_sub'} />
</Popup.Label>
<Popup.Menu>
<SubtitlesPicker
@ -153,10 +151,6 @@ class ControlBar extends Component {
}
render() {
if (['paused', 'time', 'duration', 'volume', 'subtitleTracks'].every(propName => this.props[propName] === null)) {
return null;
}
return (
<div className={classnames(styles['control-bar-container'], this.props.className)}>
{this.renderSeekBar()}

View file

@ -0,0 +1,109 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import debounce from 'lodash.debounce';
import { Slider } from 'stremio-common';
import styles from './styles';
class VolumeBar extends Component {
constructor(props) {
super(props);
this.state = {
volume: null
};
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.volume !== this.state.volume ||
nextProps.volume !== this.props.volume ||
nextProps.className !== this.props.className;
}
componentWillUnmount() {
this.resetVolumeDebounced.cancel();
}
toogleVolumeMute = () => {
this.props.volume === 0 ? this.props.unmute() : this.props.mute();
}
resetVolumeDebounced = debounce(() => {
this.setState({ volume: null });
}, 100)
onSlide = (volume) => {
this.resetVolumeDebounced.cancel();
this.setState({ volume });
}
onComplete = (volume) => {
this.resetVolumeDebounced();
this.setState({ volume });
this.props.setVolume(volume);
}
onCancel = () => {
this.resetVolumeDebounced.cancel();
this.setState({ volume: null });
}
render() {
if (this.props.volume === null) {
return null;
}
const volume = this.state.volume !== null ? this.state.volume : this.props.volume;
const icon = volume === 0 ? 'ic_volume0' :
volume < 30 ? 'ic_volume1' :
volume < 70 ? 'ic_volume2' :
'ic_volume3';
return (
<div className={classnames(styles['volume-bar-container'], this.props.className)}>
{React.createElement(this.props.toggleButtonComponent, { icon, onClick: this.toogleVolumeMute }, null)}
<Slider
className={styles['slider']}
value={volume}
minimumValue={0}
maximumValue={100}
orientation={'horizontal'}
onSlide={this.onSlide}
onComplete={this.onComplete}
onCancel={this.onCancel}
/>
</div>
);
}
}
VolumeBar.propTypes = {
className: PropTypes.string,
volume: PropTypes.number,
toggleButtonComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
setVolume: PropTypes.func.isRequired
};
export default VolumeBar;
// if (this.props.volume === null) {
// return null;
// }
// const icon = this.props.volume === 0 ? 'ic_volume0' :
// this.props.volume < 50 ? 'ic_volume1' :
// this.props.volume < 100 ? 'ic_volume2' :
// 'ic_volume3';
// return (
// <Fragment>
// <div className={styles['control-bar-button']} onClick={this.toogleVolumeMute}>
// <Icon className={styles['icon']} icon={icon} />
// </div>
// <VolumeSlider
// className={styles['volume-slider']}
// volume={this.props.volume}
// setVolume={this.setVolume}
// />
// </Fragment>
// );

View file

@ -0,0 +1,3 @@
import VolumeBar from './VolumeBar';
export default VolumeBar;

View file

@ -0,0 +1,15 @@
.volume-bar-container {
display: flex;
flex-direction: row;
align-items: center;
.slider {
--thumb-size: var(--volume-bar-thumb-size);
--track-color: var(--color-surfacelight);
--thumb-color: var(--color-surfacelight);
--thumb-active-color: var(--color-surfacelighter);
--track-active-color: var(--color-surfacelighter);
flex: 1;
margin: 0 calc(var(--volume-slider-thumb-size) * 0.5);
}
}

View file

@ -1,74 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import debounce from 'lodash.debounce';
import { Slider } from 'stremio-common';
import styles from './styles';
class VolumeSlider extends Component {
constructor(props) {
super(props);
this.state = {
volume: null
};
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.volume !== this.state.volume ||
nextProps.volume !== this.props.volume ||
nextProps.className !== this.props.className;
}
componentWillUnmount() {
this.resetVolumeDebounced.cancel();
}
resetVolumeDebounced = debounce(() => {
this.setState({ volume: null });
}, 100)
onSlide = (volume) => {
this.resetVolumeDebounced.cancel();
this.setState({ volume });
}
onComplete = (volume) => {
this.resetVolumeDebounced();
this.setState({ volume });
this.props.setVolume(volume);
}
onCancel = () => {
this.resetVolumeDebounced.cancel();
this.setState({ volume: null });
}
render() {
if (this.props.volume === null) {
return null;
}
const volume = this.state.volume !== null ? this.state.volume : this.props.volume;
return (
<Slider
className={classnames(styles['slider'], this.props.className)}
value={volume}
minimumValue={0}
maximumValue={100}
orientation={'horizontal'}
onSlide={this.onSlide}
onComplete={this.onComplete}
onCancel={this.onCancel}
/>
);
}
}
VolumeSlider.propTypes = {
className: PropTypes.string,
volume: PropTypes.number,
setVolume: PropTypes.func.isRequired
};
export default VolumeSlider;

View file

@ -1,3 +0,0 @@
import VolumeSlider from './VolumeSlider';
export default VolumeSlider;

View file

@ -1,8 +0,0 @@
.slider {
--thumb-size: var(--volume-slider-thumb-size);
--track-color: var(--color-surfacelight);
--thumb-color: var(--color-surfacelight);
--thumb-active-color: var(--color-surfacelighter);
--track-active-color: var(--color-surfacelighter);
margin: 0 calc(var(--volume-slider-thumb-size) * 0.5);
}

View file

@ -52,10 +52,10 @@
}
}
.volume-slider {
--volume-slider-thumb-size: calc(var(--control-bar-button-height) * 0.3);
height: var(--volume-slider-thumb-size);
width: calc(var(--control-bar-button-height) * 3);
.volume-bar {
--volume-bar-thumb-size: calc(var(--control-bar-button-height) * 0.3);
height: var(--control-bar-button-height);
width: calc(var(--control-bar-button-height) * 4);
}
.flex-spacing {
@ -69,7 +69,7 @@
bottom: 0;
left: 0;
z-index: -1;
box-shadow: 0 0 calc(var(--control-bar-button-height) * 2) calc(var(--control-bar-button-height) * 2) var(--color-backgrounddarker);
box-shadow: 0 0 calc(var(--control-bar-button-height) * 2) calc(var(--control-bar-button-height) * 2.3) var(--color-backgrounddarker);
content: "";
}
}