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.

WelcomePage.native.js 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import React from 'react';
  2. import {
  3. Animated,
  4. Keyboard,
  5. SafeAreaView,
  6. TextInput,
  7. TouchableHighlight,
  8. TouchableOpacity,
  9. View
  10. } from 'react-native';
  11. import Permissions from 'react-native-permissions';
  12. import { connect } from 'react-redux';
  13. import { translate } from '../../base/i18n';
  14. import { Icon } from '../../base/font-icons';
  15. import { MEDIA_TYPE } from '../../base/media';
  16. import { Header, LoadingIndicator, Text } from '../../base/react';
  17. import { ColorPalette } from '../../base/styles';
  18. import {
  19. createDesiredLocalTracks,
  20. destroyLocalTracks
  21. } from '../../base/tracks';
  22. import { SettingsView } from '../../settings';
  23. import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
  24. import { setSideBarVisible } from '../actions';
  25. import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
  26. import styles, { PLACEHOLDER_TEXT_COLOR } from './styles';
  27. import VideoSwitch from './VideoSwitch';
  28. import WelcomePageLists from './WelcomePageLists';
  29. import WelcomePageSideBar from './WelcomePageSideBar';
  30. /**
  31. * The native container rendering the welcome page.
  32. *
  33. * @extends AbstractWelcomePage
  34. */
  35. class WelcomePage extends AbstractWelcomePage {
  36. /**
  37. * Constructor of the Component.
  38. *
  39. * @inheritdoc
  40. */
  41. constructor(props) {
  42. super(props);
  43. this.state._fieldFocused = false;
  44. this.state.hintBoxAnimation = new Animated.Value(0);
  45. // Bind event handlers so they are only bound once per instance.
  46. this._onFieldFocusChange = this._onFieldFocusChange.bind(this);
  47. this._onShowSideBar = this._onShowSideBar.bind(this);
  48. this._renderHintBox = this._renderHintBox.bind(this);
  49. // Specially bind functions to avoid function definition on render.
  50. this._onFieldBlur = this._onFieldFocusChange.bind(this, false);
  51. this._onFieldFocus = this._onFieldFocusChange.bind(this, true);
  52. }
  53. /**
  54. * Implements React's {@link Component#componentWillMount()}. Invoked
  55. * immediately before mounting occurs. Creates a local video track if none
  56. * is available and the camera permission was already granted.
  57. *
  58. * @inheritdoc
  59. * @returns {void}
  60. */
  61. componentWillMount() {
  62. super.componentWillMount();
  63. const { dispatch } = this.props;
  64. if (this.props._settings.startAudioOnly) {
  65. dispatch(destroyLocalTracks());
  66. } else {
  67. // Make sure we don't request the permission for the camera from
  68. // the start. We will, however, create a video track iff the user
  69. // already granted the permission.
  70. Permissions.check('camera').then(response => {
  71. response === 'authorized'
  72. && dispatch(createDesiredLocalTracks(MEDIA_TYPE.VIDEO));
  73. });
  74. }
  75. }
  76. /**
  77. * Implements React's {@link Component#render()}. Renders a prompt for
  78. * entering a room name.
  79. *
  80. * @inheritdoc
  81. * @returns {ReactElement}
  82. */
  83. render() {
  84. const { buttonStyle, pageStyle } = Header;
  85. const roomnameAccLabel = 'welcomepage.accessibilityLabel.roomname';
  86. const { t } = this.props;
  87. return (
  88. <LocalVideoTrackUnderlay style = { styles.welcomePage }>
  89. <View style = { pageStyle }>
  90. <Header style = { styles.header }>
  91. <TouchableOpacity onPress = { this._onShowSideBar } >
  92. <Icon
  93. name = 'menu'
  94. style = { buttonStyle } />
  95. </TouchableOpacity>
  96. <VideoSwitch />
  97. </Header>
  98. <SafeAreaView style = { styles.roomContainer } >
  99. <View style = { styles.joinControls } >
  100. <TextInput
  101. accessibilityLabel = { t(roomnameAccLabel) }
  102. autoCapitalize = 'none'
  103. autoComplete = { false }
  104. autoCorrect = { false }
  105. autoFocus = { false }
  106. onBlur = { this._onFieldBlur }
  107. onChangeText = { this._onRoomChange }
  108. onFocus = { this._onFieldFocus }
  109. onSubmitEditing = { this._onJoin }
  110. placeholder = { t('welcomepage.roomname') }
  111. placeholderTextColor = {
  112. PLACEHOLDER_TEXT_COLOR
  113. }
  114. returnKeyType = { 'go' }
  115. style = { styles.textInput }
  116. underlineColorAndroid = 'transparent'
  117. value = { this.state.room } />
  118. {
  119. this._renderHintBox()
  120. }
  121. </View>
  122. </SafeAreaView>
  123. <WelcomePageLists disabled = { this.state._fieldFocused } />
  124. <SettingsView />
  125. </View>
  126. <WelcomePageSideBar />
  127. </LocalVideoTrackUnderlay>
  128. );
  129. }
  130. /**
  131. * Constructs a style array to handle the hint box animation.
  132. *
  133. * @private
  134. * @returns {Array<Object>}
  135. */
  136. _getHintBoxStyle() {
  137. return [
  138. styles.hintContainer,
  139. {
  140. opacity: this.state.hintBoxAnimation
  141. }
  142. ];
  143. }
  144. /**
  145. * Callback for when the room field's focus changes so the hint box
  146. * must be rendered or removed.
  147. *
  148. * @private
  149. * @param {boolean} focused - The focused state of the field.
  150. * @returns {void}
  151. */
  152. _onFieldFocusChange(focused) {
  153. focused
  154. && this.setState({
  155. _fieldFocused: true
  156. });
  157. Animated.timing(
  158. this.state.hintBoxAnimation,
  159. {
  160. duration: 300,
  161. toValue: focused ? 1 : 0
  162. })
  163. .start(animationState =>
  164. animationState.finished
  165. && !focused
  166. && this.setState({
  167. _fieldFocused: false
  168. }));
  169. }
  170. /**
  171. * Toggles the side bar.
  172. *
  173. * @private
  174. * @returns {void}
  175. */
  176. _onShowSideBar() {
  177. Keyboard.dismiss();
  178. this.props.dispatch(setSideBarVisible(true));
  179. }
  180. /**
  181. * Renders the hint box if necessary.
  182. *
  183. * @private
  184. * @returns {React$Node}
  185. */
  186. _renderHintBox() {
  187. if (this.state._fieldFocused) {
  188. const { t } = this.props;
  189. return (
  190. <Animated.View style = { this._getHintBoxStyle() }>
  191. <View style = { styles.hintTextContainer } >
  192. <Text style = { styles.hintText }>
  193. { t('welcomepage.roomnameHint') }
  194. </Text>
  195. </View>
  196. <View style = { styles.hintButtonContainer } >
  197. { this._renderJoinButton() }
  198. </View>
  199. </Animated.View>
  200. );
  201. }
  202. return null;
  203. }
  204. /**
  205. * Renders the join button.
  206. *
  207. * @private
  208. * @returns {ReactElement}
  209. */
  210. _renderJoinButton() {
  211. const { t } = this.props;
  212. let children;
  213. /* eslint-disable no-extra-parens */
  214. if (this.state.joining) {
  215. // TouchableHighlight is picky about what its children can be, so
  216. // wrap it in a native component, i.e. View to avoid having to
  217. // modify non-native children.
  218. children = (
  219. <View>
  220. <LoadingIndicator
  221. color = { styles.buttonText.color }
  222. size = 'small' />
  223. </View>
  224. );
  225. } else {
  226. children = (
  227. <Text style = { styles.buttonText }>
  228. { this.props.t('welcomepage.join') }
  229. </Text>
  230. );
  231. }
  232. /* eslint-enable no-extra-parens */
  233. const buttonDisabled = this._isJoinDisabled();
  234. return (
  235. <TouchableHighlight
  236. accessibilityLabel =
  237. { t('welcomepage.accessibilityLabel.join') }
  238. disabled = { buttonDisabled }
  239. onPress = { this._onJoin }
  240. style = { [
  241. styles.button,
  242. buttonDisabled ? styles.buttonDisabled : null
  243. ] }
  244. underlayColor = { ColorPalette.white }>
  245. { children }
  246. </TouchableHighlight>
  247. );
  248. }
  249. }
  250. export default translate(connect(_mapStateToProps)(WelcomePage));