You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Toolbox.js 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. // @flow
  2. import React, { Component } from 'react';
  3. import { View } from 'react-native';
  4. import { connect } from 'react-redux';
  5. import { Container } from '../../../base/react';
  6. import {
  7. isNarrowAspectRatio,
  8. makeAspectRatioAware
  9. } from '../../../base/responsive-ui';
  10. import { InviteButton } from '../../../invite';
  11. import AudioMuteButton from '../AudioMuteButton';
  12. import HangupButton from '../HangupButton';
  13. import OverflowMenuButton from './OverflowMenuButton';
  14. import styles, {
  15. hangupButtonStyles,
  16. toolbarButtonStyles,
  17. toolbarToggledButtonStyles
  18. } from './styles';
  19. import VideoMuteButton from '../VideoMuteButton';
  20. /**
  21. * The number of buttons other than {@link HangupButton} to render in
  22. * {@link Toolbox}.
  23. *
  24. * @private
  25. * @type number
  26. */
  27. const _BUTTON_COUNT = 4;
  28. /**
  29. * Factor relating the hangup button and other toolbar buttons.
  30. *
  31. * @private
  32. * @type number
  33. */
  34. const _BUTTON_SIZE_FACTOR = 0.85;
  35. /**
  36. * The type of {@link Toolbox}'s React {@code Component} props.
  37. */
  38. type Props = {
  39. /**
  40. * The indicator which determines whether the toolbox is visible.
  41. */
  42. _visible: boolean,
  43. /**
  44. * The redux {@code dispatch} function.
  45. */
  46. dispatch: Function
  47. };
  48. /**
  49. * The type of {@link Toolbox}'s React {@code Component} state.
  50. */
  51. type State = {
  52. /**
  53. * The detected width for this component.
  54. */
  55. width: number
  56. };
  57. /**
  58. * Implements the conference toolbox on React Native.
  59. */
  60. class Toolbox extends Component<Props, State> {
  61. state = {
  62. width: 0
  63. };
  64. /**
  65. * Initializes a new {@code Toolbox} instance.
  66. *
  67. * @inheritdoc
  68. */
  69. constructor(props: Props) {
  70. super(props);
  71. // Bind event handlers so they are only bound once per instance.
  72. this._onLayout = this._onLayout.bind(this);
  73. }
  74. /**
  75. * Implements React's {@link Component#render()}.
  76. *
  77. * @inheritdoc
  78. * @returns {ReactElement}
  79. */
  80. render() {
  81. const toolboxStyle
  82. = isNarrowAspectRatio(this)
  83. ? styles.toolboxNarrow
  84. : styles.toolboxWide;
  85. return (
  86. <Container
  87. onLayout = { this._onLayout }
  88. style = { toolboxStyle }
  89. visible = { this.props._visible }>
  90. { this._renderToolbar() }
  91. </Container>
  92. );
  93. }
  94. /**
  95. * Calculates how large our toolbar buttons can be, given the available
  96. * width. In the future we might want to have a size threshold, and once
  97. * it's passed a completely different style could be used, akin to the web.
  98. *
  99. * @private
  100. * @returns {number}
  101. */
  102. _calculateButtonSize() {
  103. const { width } = this.state;
  104. if (width <= 0) {
  105. // We don't know how much space is allocated to the toolbar yet.
  106. return width;
  107. }
  108. const hangupButtonSize = styles.hangupButton.width;
  109. const { style } = toolbarButtonStyles;
  110. let buttonSize
  111. = (width
  112. // Account for HangupButton without its margin which is not
  113. // included in _BUTTON_COUNT:
  114. - hangupButtonSize
  115. // Account for the horizontal margins of all buttons:
  116. - ((_BUTTON_COUNT + 1) * style.marginHorizontal * 2))
  117. / _BUTTON_COUNT;
  118. // Well, don't return a non-positive button size.
  119. if (buttonSize <= 0) {
  120. buttonSize = style.width;
  121. }
  122. // The button should be at most _BUTTON_SIZE_FACTOR of the hangup
  123. // button's size.
  124. buttonSize
  125. = Math.min(buttonSize, hangupButtonSize * _BUTTON_SIZE_FACTOR);
  126. // Make sure it's an even number.
  127. return 2 * Math.round(buttonSize / 2);
  128. }
  129. _onLayout: (Object) => void;
  130. /**
  131. * Handles the "on layout" View's event and stores the width as state.
  132. *
  133. * @param {Object} event - The "on layout" event object/structure passed
  134. * by react-native.
  135. * @private
  136. * @returns {void}
  137. */
  138. _onLayout({ nativeEvent: { layout: { width } } }) {
  139. this.setState({ width });
  140. }
  141. /**
  142. * Renders the toolbar. In order to avoid a weird visual effect in which the
  143. * toolbar is (visually) rendered and then visibly changes its size, it is
  144. * rendered only after we've figured out the width available to the toolbar.
  145. *
  146. * @returns {React$Node}
  147. */
  148. _renderToolbar() {
  149. const buttonSize = this._calculateButtonSize();
  150. let buttonStyles = toolbarButtonStyles;
  151. let toggledButtonStyles = toolbarToggledButtonStyles;
  152. if (buttonSize > 0) {
  153. const extraButtonStyle = {
  154. borderRadius: buttonSize / 2,
  155. height: buttonSize,
  156. width: buttonSize
  157. };
  158. // XXX The following width equality checks attempt to minimize
  159. // unnecessary objects and possibly re-renders.
  160. if (buttonStyles.style.width !== extraButtonStyle.width) {
  161. buttonStyles = {
  162. ...buttonStyles,
  163. style: [ buttonStyles.style, extraButtonStyle ]
  164. };
  165. }
  166. if (toggledButtonStyles.style.width !== extraButtonStyle.width) {
  167. toggledButtonStyles = {
  168. ...toggledButtonStyles,
  169. style: [ toggledButtonStyles.style, extraButtonStyle ]
  170. };
  171. }
  172. } else {
  173. // XXX In order to avoid a weird visual effect in which the toolbar
  174. // is (visually) rendered and then visibly changes its size, it is
  175. // rendered only after we've figured out the width available to the
  176. // toolbar.
  177. return null;
  178. }
  179. return (
  180. <View
  181. pointerEvents = 'box-none'
  182. style = { styles.toolbar }>
  183. <InviteButton styles = { buttonStyles } />
  184. <AudioMuteButton
  185. styles = { buttonStyles }
  186. toggledStyles = { toggledButtonStyles } />
  187. <HangupButton styles = { hangupButtonStyles } />
  188. <VideoMuteButton
  189. styles = { buttonStyles }
  190. toggledStyles = { toggledButtonStyles } />
  191. <OverflowMenuButton
  192. styles = { buttonStyles }
  193. toggledStyles = { toggledButtonStyles } />
  194. </View>
  195. );
  196. }
  197. }
  198. /**
  199. * Maps parts of the redux state to {@link Toolbox} (React {@code Component})
  200. * props.
  201. *
  202. * @param {Object} state - The redux state of which parts are to be mapped to
  203. * {@code Toolbox} props.
  204. * @private
  205. * @returns {{
  206. * _visible: boolean
  207. * }}
  208. */
  209. function _mapStateToProps(state: Object): Object {
  210. const { alwaysVisible, enabled, visible } = state['features/toolbox'];
  211. return {
  212. _visible: enabled && (alwaysVisible || visible)
  213. };
  214. }
  215. export default connect(_mapStateToProps)(makeAspectRatioAware(Toolbox));