diff --git a/react/features/base/react/components/native/SideBar.js b/react/features/base/react/components/native/SideBar.js deleted file mode 100644 index 4086df6d2..000000000 --- a/react/features/base/react/components/native/SideBar.js +++ /dev/null @@ -1,178 +0,0 @@ -// @flow - -import React, { PureComponent, type Node } from 'react'; -import { Animated, TouchableWithoutFeedback, View } from 'react-native'; - -import styles, { SIDEBAR_WIDTH } from './styles'; - -/** - * The type of the React {@code Component} props of {@link SideBar}. - */ -type Props = { - - /** - * The children of {@code SideBar}. - */ - children: Node, - - /** - * Callback to notify the containing {@code Component} that the sidebar is - * closing. - */ - onHide: Function, - - /** - * Whether the menu (of the {@code SideBar}?) is displayed/rendered/shown. - */ - show: boolean -}; - -/** - * The type of the React {@code Component} state of {@link SideBar}. - */ -type State = { - - /** - * Whether the side overlay should be displayed/rendered/shown. - */ - showOverlay: boolean, - - /** - * The native animation object. - */ - sliderAnimation: Animated.Value -}; - -/** - * A generic animated side bar to be used for left-side, hamburger-style menus. - */ -export default class SideBar extends PureComponent { - /** - * Implements React's {@link Component#getDerivedStateFromProps()}. - * - * @inheritdoc - */ - static getDerivedStateFromProps(props: Props, prevState: State) { - return { - showOverlay: props.show || prevState.showOverlay - }; - } - - /** - * Initializes a new {@code SideBar} instance. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this.state = { - showOverlay: false, - sliderAnimation: new Animated.Value(0) - }; - - // Bind event handlers so they are only bound once per instance. - this._onHideMenu = this._onHideMenu.bind(this); - } - - /** - * Implements React's {@link Component#componentDidMount()}. - * - * @inheritdoc - */ - componentDidMount() { - this._setShow(this.props.show); - } - - /** - * Implements React's {@link Component#componentDidUpdate()}. - * - * @inheritdoc - */ - componentDidUpdate() { - this._setShow(this.props.show); - } - - /** - * Implements React's {@link Component#render()}. - * - * @inheritdoc - */ - render() { - return ( - - { - this.state.showOverlay - && - - - } - - { this.props.children } - - - ); - } - - _getContentStyle: () => Array; - - /** - * Assembles a style array for the sidebar content. - * - * @private - * @returns {Array} - */ - _getContentStyle() { - return [ - styles.sideMenuContent, - { transform: [ { translateX: this.state.sliderAnimation } ] } - ]; - } - - _onHideMenu: () => void; - - /** - * Hides the side menu. - * - * @private - * @returns {void} - */ - _onHideMenu() { - this._setShow(false); - - const { onHide } = this.props; - - onHide && onHide(); - } - - _setShow: (boolean) => void; - - /** - * Shows/hides the side menu. - * - * @param {boolean} show - If the side menu is to be made visible, - * {@code true}; otherwise, {@code false}. - * @private - * @returns {void} - */ - _setShow(show) { - Animated - .timing( - /* value */ this.state.sliderAnimation, - /* config */ { - toValue: show ? SIDEBAR_WIDTH : 0, - useNativeDriver: true - }) - .start(({ finished }) => { - finished && !show && this.setState({ showOverlay: false }); - - // XXX Technically, the arrow function can further be simplified - // by removing the {} and returning the boolean expression - // above. Practically and unfortunately though, Flow freaks out - // and states that Animated.timing doesn't exist!? - }); - } -} diff --git a/react/features/base/react/components/native/SlidingView.js b/react/features/base/react/components/native/SlidingView.js new file mode 100644 index 000000000..2a2079f5c --- /dev/null +++ b/react/features/base/react/components/native/SlidingView.js @@ -0,0 +1,265 @@ +// @flow + +import React, { PureComponent, type Node } from 'react'; +import { + Animated, + Dimensions, + TouchableWithoutFeedback, + View +} from 'react-native'; + +import { type StyleType } from '../../../styles'; + +import styles from './slidingviewstyles'; + +/** + * The type of the React {@code Component} props of {@link SlidingView}. + */ +type Props = { + + /** + * The children of {@code SlidingView}. + */ + children: Node, + + /** + * Callback to notify the containing {@code Component} that the view is + * closing. + */ + onHide: Function, + + /** + * Position of the SlidingView: 'left', 'right', 'top', 'bottom'. + * later). + */ + position: string, + + /** + * Whether the {@code SlidingView} is to be displayed/rendered/shown or not. + */ + show: boolean, + + /** + * Style of the animated view. + */ + style: StyleType +}; + +/** + * The type of the React {@code Component} state of {@link SlidingView}. + */ +type State = { + + /** + * Whether the sliding overlay should be displayed/rendered/shown. + */ + showOverlay: boolean, + + /** + * The native animation object. + */ + sliderAnimation: Animated.Value, + + /** + * Offset to move the view out of the screen. + */ + positionOffset: number +}; + +/** + * A generic animated slider view to be used for animated menus. + */ +export default class SlidingView extends PureComponent { + /** + * True if the component is mounted. + */ + _mounted: boolean; + + /** + * Implements React's {@link Component#getDerivedStateFromProps()}. + * + * @inheritdoc + */ + static getDerivedStateFromProps(props: Props, prevState: State) { + return { + showOverlay: props.show || prevState.showOverlay + }; + } + + /** + * Initializes a new {@code SlidingView} instance. + * + * @inheritdoc + */ + constructor(props: Props) { + super(props); + + const { height, width } = Dimensions.get('window'); + const { position } = props; + + let positionOffset = height; + + if (position === 'left' || position === 'right') { + positionOffset = width; + } + + this.state = { + showOverlay: false, + sliderAnimation: new Animated.Value(0), + positionOffset + }; + + // Bind event handlers so they are only bound once per instance. + this._onHideMenu = this._onHideMenu.bind(this); + } + + /** + * Implements React's {@link Component#componentDidMount()}. + * + * @inheritdoc + */ + componentDidMount() { + this._mounted = true; + this._setShow(this.props.show); + } + + /** + * Implements React's {@link Component#componentDidUpdate()}. + * + * @inheritdoc + */ + componentDidUpdate() { + this._setShow(this.props.show); + } + + /** + * Implements React's {@link Component#componentWillUnmount()}. + * + * @inheritdoc + */ + componentWillUnmount() { + this._mounted = false; + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + */ + render() { + const { showOverlay } = this.state; + + if (!showOverlay) { + return null; + } + + return ( + + + + + + { this.props.children } + + + ); + } + + _getContentStyle: () => Array; + + /** + * Assembles a style array for the SlideView content. + * + * @private + * @returns {Array} + */ + _getContentStyle() { + const style = { + ...this.props.style, + ...styles.sliderViewContent + }; + const { positionOffset } = this.state; + + switch (this.props.position) { + case 'bottom': + Object.assign(style, { + bottom: -positionOffset, + left: 0, + right: 0, + top: positionOffset + }, { + transform: [ { translateY: this.state.sliderAnimation } ] + }); + break; + case 'left': + Object.assign(style, { + bottom: 0, + left: -positionOffset, + right: positionOffset, + top: 0 + }, { + transform: [ { translateX: this.state.sliderAnimation } ] + }); + break; + } + + return style; + } + + _onHideMenu: () => void; + + /** + * Hides the slider. + * + * @private + * @returns {void} + */ + _onHideMenu() { + this._setShow(false); + + const { onHide } = this.props; + + onHide && onHide(); + } + + _setShow: (boolean) => void; + + /** + * Shows/hides the slider menu. + * + * @param {boolean} show - If the slider view is to be made visible, + * {@code true}; otherwise, {@code false}. + * @private + * @returns {void} + */ + _setShow(show) { + if (!this._mounted) { + return; + } + + const { positionOffset } = this.state; + const { position } = this.props; + let toValue = positionOffset; + + if (position === 'bottom' || position === 'right') { + toValue = -positionOffset; + } + + Animated + .timing( + /* value */ this.state.sliderAnimation, + /* config */ { + duration: 200, + toValue: show ? toValue : 0, + useNativeDriver: true + }) + .start(({ finished }) => { + finished && this._mounted && !show + && this.setState({ showOverlay: false }); + }); + } +} diff --git a/react/features/base/react/components/native/index.js b/react/features/base/react/components/native/index.js index 1389be607..c9828ee8e 100644 --- a/react/features/base/react/components/native/index.js +++ b/react/features/base/react/components/native/index.js @@ -20,7 +20,7 @@ export { default as NavigateSectionListSectionHeader } export { default as PagedList } from './PagedList'; export { default as Pressable } from './Pressable'; export { default as SectionList } from './SectionList'; -export { default as SideBar } from './SideBar'; +export { default as SlidingView } from './SlidingView'; export { default as Switch } from './Switch'; export { default as Text } from './Text'; export { default as TintedView } from './TintedView'; diff --git a/react/features/base/react/components/native/slidingviewstyles.js b/react/features/base/react/components/native/slidingviewstyles.js new file mode 100644 index 000000000..3167d7d95 --- /dev/null +++ b/react/features/base/react/components/native/slidingviewstyles.js @@ -0,0 +1,31 @@ +// @flow + +import { StyleSheet } from 'react-native'; + +import { OVERLAY_Z_INDEX } from '../../constants'; + +export default { + /** + * The topmost container of the side bar. + */ + sliderViewContainer: { + ...StyleSheet.absoluteFillObject, + zIndex: OVERLAY_Z_INDEX + }, + + /** + * The container of the actual content of the side menu. + */ + sliderViewContent: { + position: 'absolute' + }, + + /** + * The opaque area that covers the rest of the screen, when the side bar is + * open. + */ + sliderViewShadow: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0, 0, 0, 0.5)' + } +}; diff --git a/react/features/base/react/components/native/styles.js b/react/features/base/react/components/native/styles.js index b67e527ca..63a702aaf 100644 --- a/react/features/base/react/components/native/styles.js +++ b/react/features/base/react/components/native/styles.js @@ -1,7 +1,5 @@ // @flow -import { StyleSheet } from 'react-native'; - import { BoxModel, ColorPalette, createStyleSheet } from '../../../styles'; const AVATAR_OPACITY = 0.4; @@ -9,7 +7,6 @@ const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)'; const SECONDARY_ACTION_BUTTON_SIZE = 30; export const AVATAR_SIZE = 65; -export const SIDEBAR_WIDTH = 250; export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)'; /** @@ -241,35 +238,6 @@ const SECTION_LIST_STYLES = { } }; -const SIDEBAR_STYLES = { - /** - * The topmost container of the side bar. - */ - sideMenuContainer: { - ...StyleSheet.absoluteFillObject - }, - - /** - * The container of the actual content of the side menu. - */ - sideMenuContent: { - bottom: 0, - left: -SIDEBAR_WIDTH, - position: 'absolute', - top: 0, - width: SIDEBAR_WIDTH - }, - - /** - * The opaque area that covers the rest of the screen, when the side bar is - * open. - */ - sideMenuShadow: { - ...StyleSheet.absoluteFillObject, - backgroundColor: 'rgba(0, 0, 0, 0.5)' - } -}; - export const TINTED_VIEW_DEFAULT = { backgroundColor: ColorPalette.appBackground, opacity: 0.8 @@ -281,6 +249,5 @@ export const TINTED_VIEW_DEFAULT = { */ export default createStyleSheet({ ...PAGED_LIST_STYLES, - ...SECTION_LIST_STYLES, - ...SIDEBAR_STYLES + ...SECTION_LIST_STYLES }); diff --git a/react/features/base/react/constants.js b/react/features/base/react/constants.js new file mode 100644 index 000000000..bd26d927d --- /dev/null +++ b/react/features/base/react/constants.js @@ -0,0 +1,7 @@ +// @flow + +/** + * Z-index for components that are to be rendered like an overlay, to be over + * everything, such as modal-type of components, or dialogs. + */ +export const OVERLAY_Z_INDEX = 1000; diff --git a/react/features/chat/components/native/Chat.js b/react/features/chat/components/native/Chat.js index 420afeb23..b3d91c56e 100644 --- a/react/features/chat/components/native/Chat.js +++ b/react/features/chat/components/native/Chat.js @@ -1,31 +1,28 @@ // @flow import React from 'react'; -import { SafeAreaView } from 'react-native'; +import { SafeAreaView, View } from 'react-native'; import { GiftedChat } from 'react-native-gifted-chat'; import { translate } from '../../../base/i18n'; -import { BackButton, Header, HeaderLabel, Modal } from '../../../base/react'; + +import { + BackButton, + Header, + HeaderLabel, + SlidingView +} from '../../../base/react'; import { connect } from '../../../base/redux'; import AbstractChat, { _mapDispatchToProps, - _mapStateToProps as _abstractMapStateToProps, - type Props as AbstractProps + _mapStateToProps, + type Props } from '../AbstractChat'; import ChatMessage from './ChatMessage'; import styles from './styles'; -type Props = AbstractProps & { - - /** - * True if the chat window should have a solid BG render. - */ - _solidBackground: boolean -} - - /** * Implements a React native component that renders the chat window (modal) of * the mobile client. @@ -55,32 +52,24 @@ class Chat extends AbstractChat { // of messages. const messages = this.props._messages.map(this._transformMessage).reverse(); - const modalStyle = [ - styles.modalBackdrop - ]; - - if (this.props._solidBackground) { - // We only use a transparent background, when we are in a video - // meeting to give a user a glympse of what's happening. Otherwise - // we use a non-transparent background. - modalStyle.push(styles.solidModalBackdrop); - } return ( - -
- - -
- - - -
+ + +
+ + +
+ + + +
+
); } @@ -146,21 +135,4 @@ class Chat extends AbstractChat { } } -/** - * Maps part of the Redux state to the props of this component. - * - * @param {Object} state - The Redux state. - * @returns {{ - * _solidBackground: boolean - * }} - */ -function _mapStateToProps(state) { - const abstractReduxProps = _abstractMapStateToProps(state); - - return { - ...abstractReduxProps, - _solidBackground: state['features/base/conference'].audioOnly - }; -} - export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat)); diff --git a/react/features/chat/components/native/ChatMessage.js b/react/features/chat/components/native/ChatMessage.js index d8a394677..f4ddd5b8c 100644 --- a/react/features/chat/components/native/ChatMessage.js +++ b/react/features/chat/components/native/ChatMessage.js @@ -8,8 +8,8 @@ import { Avatar } from '../../../base/participants'; import { connect } from '../../../base/redux'; import AbstractChatMessage, { - _mapStateToProps as _abstractMapStateToProps, - type Props as AbstractProps + _mapStateToProps, + type Props } from '../AbstractChatMessage'; import styles from './styles'; @@ -23,14 +23,6 @@ const AVATAR_SIZE = 32; */ const TIMESTAMP_FORMAT = 'H:mm'; -type Props = AbstractProps & { - - /** - * True if the chat window has a solid BG so then we have to adopt in style. - */ - _solidBackground: boolean -} - /** * Renders a single chat message. */ @@ -54,9 +46,6 @@ class ChatMessage extends AbstractChatMessage { const textWrapperStyle = [ styles.textWrapper ]; - const timeTextStyles = [ - styles.timeText - ]; if (localMessage) { // The wrapper needs to be aligned to the right. @@ -69,10 +58,6 @@ class ChatMessage extends AbstractChatMessage { textWrapperStyle.push(styles.systemTextWrapper); } - if (this.props._solidBackground) { - timeTextStyles.push(styles.solidBGTimeText); - } - return ( { @@ -92,7 +77,7 @@ class ChatMessage extends AbstractChatMessage { { message.text } - + { timeStamp } @@ -133,20 +118,4 @@ class ChatMessage extends AbstractChatMessage { } } -/** - * Maps part of the Redux state to the props of this component. - * - * @param {Object} state - The Redux state. - * @param {Props} ownProps - The own props of the component. - * @returns {{ - * _solidBackground: boolean - * }} - */ -function _mapStateToProps(state, ownProps) { - return { - ..._abstractMapStateToProps(state, ownProps), - _solidBackground: state['features/base/conference'].audioOnly - }; -} - export default translate(connect(_mapStateToProps)(ChatMessage)); diff --git a/react/features/chat/components/native/styles.js b/react/features/chat/components/native/styles.js index ee3e2b09b..4154d70e6 100644 --- a/react/features/chat/components/native/styles.js +++ b/react/features/chat/components/native/styles.js @@ -1,9 +1,6 @@ // @flow -import { - ColorPalette, - createStyleSheet -} from '../../../base/styles'; +import { ColorPalette } from '../../../base/styles'; /** * The styles of the feature chat. @@ -13,7 +10,7 @@ import { * need to extract the brand colors and sizes into a branding feature (planned * for the future). */ -export default createStyleSheet({ +export default { /** * Wrapper View for the avatar. @@ -22,6 +19,19 @@ export default createStyleSheet({ marginRight: 8 }, + /** + * Background of the chat screen. + */ + backdrop: { + backgroundColor: ColorPalette.white, + flex: 1 + }, + + chatContainer: { + flex: 1, + flexDirection: 'column' + }, + /** * Wrapper for the details together, such as name, message and time. */ @@ -58,16 +68,6 @@ export default createStyleSheet({ marginVertical: 4 }, - /** - * Background of the chat screen. Currently it's set to a transparent value - * as the idea is that the participant would still want to see at least a - * part of the video when he/she is in the chat window. - */ - modalBackdrop: { - backgroundColor: 'rgba(127, 127, 127, 0.8)', - flex: 1 - }, - /** * Style modifier for the {@code detailsWrapper} for own messages. */ @@ -84,17 +84,6 @@ export default createStyleSheet({ borderTopRightRadius: 0 }, - solidBGTimeText: { - color: 'rgb(164, 184, 209)' - }, - - /** - * Style modifier for the chat window when we're in audio only mode. - */ - solidModalBackdrop: { - backgroundColor: ColorPalette.white - }, - /** * Style modifier for system (error) messages. */ @@ -118,7 +107,7 @@ export default createStyleSheet({ * Text node for the timestamp. */ timeText: { - color: ColorPalette.white, + color: 'rgb(164, 184, 209)', fontSize: 13 } -}); +}; diff --git a/react/features/welcome/components/WelcomePageSideBar.native.js b/react/features/welcome/components/WelcomePageSideBar.native.js index a045f6f5f..a6cd8d0a0 100644 --- a/react/features/welcome/components/WelcomePageSideBar.native.js +++ b/react/features/welcome/components/WelcomePageSideBar.native.js @@ -11,7 +11,7 @@ import { } from '../../base/participants'; import { Header, - SideBar + SlidingView } from '../../base/react'; import { connect } from '../../base/redux'; import { setSettingsViewVisible } from '../../settings'; @@ -83,9 +83,11 @@ class WelcomePageSideBar extends Component { */ render() { return ( - + position = 'left' + show = { this.props._visible } + style = { styles.sideBar } >
{ url = { SEND_FEEDBACK_URL } /> - + ); } diff --git a/react/features/welcome/components/styles.js b/react/features/welcome/components/styles.js index 44c4c565b..f86912a7b 100644 --- a/react/features/welcome/components/styles.js +++ b/react/features/welcome/components/styles.js @@ -171,6 +171,13 @@ export default createStyleSheet({ flexDirection: 'column' }, + /** + * Container of the side bar. + */ + sideBar: { + width: 250 + }, + /** * The body of the side bar where the items are. */