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 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. // @flow
  2. import React, { Component } from 'react';
  3. import { View } from 'react-native';
  4. import { Container } from '../../../base/react';
  5. import { ColorSchemeRegistry } from '../../../base/color-scheme';
  6. import { connect } from '../../../base/redux';
  7. import { StyleType } from '../../../base/styles';
  8. import { ChatButton } from '../../../chat';
  9. import { isToolboxVisible } from '../../functions';
  10. import { HANGUP_BUTTON_SIZE } from '../../constants';
  11. import AudioMuteButton from '../AudioMuteButton';
  12. import HangupButton from '../HangupButton';
  13. import OverflowMenuButton from './OverflowMenuButton';
  14. import styles from './styles';
  15. import VideoMuteButton from '../VideoMuteButton';
  16. /**
  17. * The number of buttons other than {@link HangupButton} to render in
  18. * {@link Toolbox}.
  19. *
  20. * @private
  21. * @type number
  22. */
  23. const _BUTTON_COUNT = 4;
  24. /**
  25. * Factor relating the hangup button and other toolbar buttons.
  26. *
  27. * @private
  28. * @type number
  29. */
  30. const _BUTTON_SIZE_FACTOR = 0.85;
  31. /**
  32. * The type of {@link Toolbox}'s React {@code Component} props.
  33. */
  34. type Props = {
  35. /**
  36. * The color-schemed stylesheet of the feature.
  37. */
  38. _styles: StyleType,
  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. return (
  82. <Container
  83. onLayout = { this._onLayout }
  84. style = { styles.toolbox }
  85. visible = { this.props._visible }>
  86. { this._renderToolbar() }
  87. </Container>
  88. );
  89. }
  90. /**
  91. * Calculates how large our toolbar buttons can be, given the available
  92. * width. In the future we might want to have a size threshold, and once
  93. * it's passed a completely different style could be used, akin to the web.
  94. *
  95. * @private
  96. * @returns {number}
  97. */
  98. _calculateButtonSize() {
  99. const { _styles } = this.props;
  100. const { width } = this.state;
  101. if (width <= 0) {
  102. // We don't know how much space is allocated to the toolbar yet.
  103. return width;
  104. }
  105. const hangupButtonSize = HANGUP_BUTTON_SIZE;
  106. const { style } = _styles.buttonStyles;
  107. let buttonSize
  108. = (width
  109. // Account for HangupButton without its margin which is not
  110. // included in _BUTTON_COUNT:
  111. - hangupButtonSize
  112. // Account for the horizontal margins of all buttons:
  113. - ((_BUTTON_COUNT + 1) * style.marginHorizontal * 2))
  114. / _BUTTON_COUNT;
  115. // Well, don't return a non-positive button size.
  116. if (buttonSize <= 0) {
  117. buttonSize = style.width;
  118. }
  119. // The button should be at most _BUTTON_SIZE_FACTOR of the hangup
  120. // button's size.
  121. buttonSize
  122. = Math.min(buttonSize, hangupButtonSize * _BUTTON_SIZE_FACTOR);
  123. // Make sure it's an even number.
  124. return 2 * Math.round(buttonSize / 2);
  125. }
  126. /**
  127. * Constructs the toggled style of the chat button. This cannot be done by
  128. * simple style inheritance due to the size calculation done in this
  129. * component.
  130. *
  131. * @param {Object} baseStyle - The base style that was originally
  132. * calculated.
  133. * @returns {Object | Array}
  134. */
  135. _getChatButtonToggledStyle(baseStyle) {
  136. const { _styles } = this.props;
  137. if (Array.isArray(baseStyle.style)) {
  138. return {
  139. ...baseStyle,
  140. style: [
  141. ...baseStyle.style,
  142. _styles.chatButtonOverride.toggled
  143. ]
  144. };
  145. }
  146. return {
  147. ...baseStyle,
  148. style: [
  149. baseStyle.style,
  150. _styles.chatButtonOverride.toggled
  151. ]
  152. };
  153. }
  154. _onLayout: (Object) => void;
  155. /**
  156. * Handles the "on layout" View's event and stores the width as state.
  157. *
  158. * @param {Object} event - The "on layout" event object/structure passed
  159. * by react-native.
  160. * @private
  161. * @returns {void}
  162. */
  163. _onLayout({ nativeEvent: { layout: { width } } }) {
  164. this.setState({ width });
  165. }
  166. /**
  167. * Renders the toolbar. In order to avoid a weird visual effect in which the
  168. * toolbar is (visually) rendered and then visibly changes its size, it is
  169. * rendered only after we've figured out the width available to the toolbar.
  170. *
  171. * @returns {React$Node}
  172. */
  173. _renderToolbar() {
  174. const { _styles } = this.props;
  175. const buttonSize = this._calculateButtonSize();
  176. let { buttonStyles, toggledButtonStyles } = _styles;
  177. if (buttonSize > 0) {
  178. const extraButtonStyle = {
  179. borderRadius: buttonSize / 2,
  180. height: buttonSize,
  181. width: buttonSize
  182. };
  183. // XXX The following width equality checks attempt to minimize
  184. // unnecessary objects and possibly re-renders.
  185. if (buttonStyles.style.width !== extraButtonStyle.width) {
  186. buttonStyles = {
  187. ...buttonStyles,
  188. style: [ buttonStyles.style, extraButtonStyle ]
  189. };
  190. }
  191. if (toggledButtonStyles.style.width !== extraButtonStyle.width) {
  192. toggledButtonStyles = {
  193. ...toggledButtonStyles,
  194. style: [ toggledButtonStyles.style, extraButtonStyle ]
  195. };
  196. }
  197. } else {
  198. // XXX In order to avoid a weird visual effect in which the toolbar
  199. // is (visually) rendered and then visibly changes its size, it is
  200. // rendered only after we've figured out the width available to the
  201. // toolbar.
  202. return null;
  203. }
  204. return (
  205. <View
  206. pointerEvents = 'box-none'
  207. style = { styles.toolbar }>
  208. <ChatButton
  209. styles = { buttonStyles }
  210. toggledStyles = {
  211. this._getChatButtonToggledStyle(toggledButtonStyles)
  212. } />
  213. <AudioMuteButton
  214. styles = { buttonStyles }
  215. toggledStyles = { toggledButtonStyles } />
  216. <HangupButton
  217. styles = { _styles.hangupButtonStyles } />
  218. <VideoMuteButton
  219. styles = { buttonStyles }
  220. toggledStyles = { toggledButtonStyles } />
  221. <OverflowMenuButton
  222. styles = { buttonStyles }
  223. toggledStyles = { toggledButtonStyles } />
  224. </View>
  225. );
  226. }
  227. }
  228. /**
  229. * Maps parts of the redux state to {@link Toolbox} (React {@code Component})
  230. * props.
  231. *
  232. * @param {Object} state - The redux state of which parts are to be mapped to
  233. * {@code Toolbox} props.
  234. * @private
  235. * @returns {{
  236. * _styles: StyleType,
  237. * _visible: boolean
  238. * }}
  239. */
  240. function _mapStateToProps(state: Object): Object {
  241. return {
  242. _styles: ColorSchemeRegistry.get(state, 'Toolbox'),
  243. _visible: isToolboxVisible(state)
  244. };
  245. }
  246. export default connect(_mapStateToProps)(Toolbox);