您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

WelcomePage.native.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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 { getName } from '../../app';
  12. import { ColorSchemeRegistry } from '../../base/color-scheme';
  13. import { translate } from '../../base/i18n';
  14. import { Icon, IconMenu } from '../../base/icons';
  15. import { MEDIA_TYPE } from '../../base/media';
  16. import { Header, LoadingIndicator, Text } from '../../base/react';
  17. import { connect } from '../../base/redux';
  18. import { ColorPalette } from '../../base/styles';
  19. import {
  20. createDesiredLocalTracks,
  21. destroyLocalTracks
  22. } from '../../base/tracks';
  23. import { DialInSummary } from '../../invite';
  24. import { SettingsView } from '../../settings';
  25. import { setSideBarVisible } from '../actions';
  26. import {
  27. AbstractWelcomePage,
  28. _mapStateToProps as _abstractMapStateToProps
  29. } from './AbstractWelcomePage';
  30. import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
  31. import styles, { PLACEHOLDER_TEXT_COLOR } from './styles';
  32. import VideoSwitch from './VideoSwitch';
  33. import WelcomePageLists from './WelcomePageLists';
  34. import WelcomePageSideBar from './WelcomePageSideBar';
  35. /**
  36. * The native container rendering the welcome page.
  37. *
  38. * @extends AbstractWelcomePage
  39. */
  40. class WelcomePage extends AbstractWelcomePage {
  41. /**
  42. * Constructor of the Component.
  43. *
  44. * @inheritdoc
  45. */
  46. constructor(props) {
  47. super(props);
  48. this.state._fieldFocused = false;
  49. this.state.hintBoxAnimation = new Animated.Value(0);
  50. // Bind event handlers so they are only bound once per instance.
  51. this._onFieldFocusChange = this._onFieldFocusChange.bind(this);
  52. this._onShowSideBar = this._onShowSideBar.bind(this);
  53. this._renderHintBox = this._renderHintBox.bind(this);
  54. // Specially bind functions to avoid function definition on render.
  55. this._onFieldBlur = this._onFieldFocusChange.bind(this, false);
  56. this._onFieldFocus = this._onFieldFocusChange.bind(this, true);
  57. }
  58. /**
  59. * Implements React's {@link Component#componentDidMount()}. Invoked
  60. * immediately after mounting occurs. Creates a local video track if none
  61. * is available and the camera permission was already granted.
  62. *
  63. * @inheritdoc
  64. * @returns {void}
  65. */
  66. componentDidMount() {
  67. super.componentDidMount();
  68. const { dispatch } = this.props;
  69. if (this.props._settings.startAudioOnly) {
  70. dispatch(destroyLocalTracks());
  71. } else {
  72. // Make sure we don't request the permission for the camera from
  73. // the start. We will, however, create a video track iff the user
  74. // already granted the permission.
  75. navigator.permissions.query({ name: 'camera' }).then(response => {
  76. response === 'granted'
  77. && dispatch(createDesiredLocalTracks(MEDIA_TYPE.VIDEO));
  78. });
  79. }
  80. }
  81. /**
  82. * Implements React's {@link Component#render()}. Renders a prompt for
  83. * entering a room name.
  84. *
  85. * @inheritdoc
  86. * @returns {ReactElement}
  87. */
  88. render() {
  89. // We want to have the welcome page support the reduced UI layout,
  90. // but we ran into serious issues enabling it so we disable it
  91. // until we have a proper fix in place. We leave the code here though, because
  92. // this part should be fine when the bug is fixed.
  93. //
  94. // NOTE: when re-enabling, don't forget to uncomment the respective _mapStateToProps line too
  95. /*
  96. const { _reducedUI } = this.props;
  97. if (_reducedUI) {
  98. return this._renderReducedUI();
  99. }
  100. */
  101. return this._renderFullUI();
  102. }
  103. /**
  104. * Constructs a style array to handle the hint box animation.
  105. *
  106. * @private
  107. * @returns {Array<Object>}
  108. */
  109. _getHintBoxStyle() {
  110. return [
  111. styles.hintContainer,
  112. {
  113. opacity: this.state.hintBoxAnimation
  114. }
  115. ];
  116. }
  117. /**
  118. * Callback for when the room field's focus changes so the hint box
  119. * must be rendered or removed.
  120. *
  121. * @private
  122. * @param {boolean} focused - The focused state of the field.
  123. * @returns {void}
  124. */
  125. _onFieldFocusChange(focused) {
  126. focused
  127. && this.setState({
  128. _fieldFocused: true
  129. });
  130. Animated.timing(
  131. this.state.hintBoxAnimation,
  132. {
  133. duration: 300,
  134. toValue: focused ? 1 : 0
  135. })
  136. .start(animationState =>
  137. animationState.finished
  138. && !focused
  139. && this.setState({
  140. _fieldFocused: false
  141. }));
  142. }
  143. /**
  144. * Toggles the side bar.
  145. *
  146. * @private
  147. * @returns {void}
  148. */
  149. _onShowSideBar() {
  150. Keyboard.dismiss();
  151. this.props.dispatch(setSideBarVisible(true));
  152. }
  153. /**
  154. * Renders the hint box if necessary.
  155. *
  156. * @private
  157. * @returns {React$Node}
  158. */
  159. _renderHintBox() {
  160. if (this.state._fieldFocused) {
  161. const { t } = this.props;
  162. return (
  163. <Animated.View style = { this._getHintBoxStyle() }>
  164. <View style = { styles.hintTextContainer } >
  165. <Text style = { styles.hintText }>
  166. { t('welcomepage.roomnameHint') }
  167. </Text>
  168. </View>
  169. <View style = { styles.hintButtonContainer } >
  170. { this._renderJoinButton() }
  171. </View>
  172. </Animated.View>
  173. );
  174. }
  175. return null;
  176. }
  177. /**
  178. * Renders the join button.
  179. *
  180. * @private
  181. * @returns {ReactElement}
  182. */
  183. _renderJoinButton() {
  184. const { t } = this.props;
  185. let children;
  186. if (this.state.joining) {
  187. // TouchableHighlight is picky about what its children can be, so
  188. // wrap it in a native component, i.e. View to avoid having to
  189. // modify non-native children.
  190. children = (
  191. <View>
  192. <LoadingIndicator
  193. color = { styles.buttonText.color }
  194. size = 'small' />
  195. </View>
  196. );
  197. } else {
  198. children = (
  199. <Text style = { styles.buttonText }>
  200. { this.props.t('welcomepage.join') }
  201. </Text>
  202. );
  203. }
  204. const buttonDisabled = this._isJoinDisabled();
  205. return (
  206. <TouchableHighlight
  207. accessibilityLabel =
  208. { t('welcomepage.accessibilityLabel.join') }
  209. disabled = { buttonDisabled }
  210. onPress = { this._onJoin }
  211. style = { [
  212. styles.button,
  213. buttonDisabled ? styles.buttonDisabled : null
  214. ] }
  215. underlayColor = { ColorPalette.white }>
  216. { children }
  217. </TouchableHighlight>
  218. );
  219. }
  220. /**
  221. * Renders the full welcome page.
  222. *
  223. * @returns {ReactElement}
  224. */
  225. _renderFullUI() {
  226. const roomnameAccLabel = 'welcomepage.accessibilityLabel.roomname';
  227. const { _headerStyles, t } = this.props;
  228. return (
  229. <LocalVideoTrackUnderlay style = { styles.welcomePage }>
  230. <View style = { _headerStyles.page }>
  231. <Header style = { styles.header }>
  232. <TouchableOpacity onPress = { this._onShowSideBar } >
  233. <Icon
  234. src = { IconMenu }
  235. style = { _headerStyles.headerButtonIcon } />
  236. </TouchableOpacity>
  237. <VideoSwitch />
  238. </Header>
  239. <SafeAreaView style = { styles.roomContainer } >
  240. <View style = { styles.joinControls } >
  241. <TextInput
  242. accessibilityLabel = { t(roomnameAccLabel) }
  243. autoCapitalize = 'none'
  244. autoComplete = 'off'
  245. autoCorrect = { false }
  246. autoFocus = { false }
  247. onBlur = { this._onFieldBlur }
  248. onChangeText = { this._onRoomChange }
  249. onFocus = { this._onFieldFocus }
  250. onSubmitEditing = { this._onJoin }
  251. placeholder = { t('welcomepage.roomname') }
  252. placeholderTextColor = {
  253. PLACEHOLDER_TEXT_COLOR
  254. }
  255. returnKeyType = { 'go' }
  256. style = { styles.textInput }
  257. underlineColorAndroid = 'transparent'
  258. value = { this.state.room } />
  259. {
  260. this._renderHintBox()
  261. }
  262. </View>
  263. </SafeAreaView>
  264. <WelcomePageLists disabled = { this.state._fieldFocused } />
  265. <SettingsView />
  266. <DialInSummary />
  267. </View>
  268. <WelcomePageSideBar />
  269. </LocalVideoTrackUnderlay>
  270. );
  271. }
  272. /**
  273. * Renders a "reduced" version of the welcome page.
  274. *
  275. * @returns {ReactElement}
  276. */
  277. _renderReducedUI() {
  278. const { t } = this.props;
  279. return (
  280. <View style = { styles.reducedUIContainer }>
  281. <Text style = { styles.reducedUIText }>
  282. { t('welcomepage.reducedUIText', { app: getName() }) }
  283. </Text>
  284. </View>
  285. );
  286. }
  287. }
  288. /**
  289. * Maps part of the Redux state to the props of this component.
  290. *
  291. * @param {Object} state - The Redux state.
  292. * @returns {Object}
  293. */
  294. function _mapStateToProps(state) {
  295. return {
  296. ..._abstractMapStateToProps(state),
  297. _headerStyles: ColorSchemeRegistry.get(state, 'Header')
  298. // _reducedUI: state['features/base/responsive-ui'].reducedUI
  299. };
  300. }
  301. export default translate(connect(_mapStateToProps)(WelcomePage));