Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

WelcomePage.native.tsx 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. import React from 'react';
  2. import { Animated,
  3. NativeSyntheticEvent,
  4. SafeAreaView,
  5. TextInputFocusEventData,
  6. TextStyle,
  7. TouchableHighlight,
  8. View,
  9. ViewStyle
  10. } from 'react-native';
  11. import { connect } from 'react-redux';
  12. import { getName } from '../../app/functions.native';
  13. import { IReduxState } from '../../app/types';
  14. import { translate } from '../../base/i18n/functions';
  15. import Icon from '../../base/icons/components/Icon';
  16. import { IconWarning } from '../../base/icons/svg';
  17. import LoadingIndicator from '../../base/react/components/native/LoadingIndicator';
  18. import Text from '../../base/react/components/native/Text';
  19. import BaseTheme from '../../base/ui/components/BaseTheme.native';
  20. import Button from '../../base/ui/components/native/Button';
  21. import Input from '../../base/ui/components/native/Input';
  22. import { BUTTON_TYPES } from '../../base/ui/constants.native';
  23. import getUnsafeRoomText from '../../base/util/getUnsafeRoomText.native';
  24. import WelcomePageTabs
  25. from '../../mobile/navigation/components/welcome/components/WelcomePageTabs';
  26. import {
  27. IProps as AbstractProps,
  28. AbstractWelcomePage,
  29. _mapStateToProps as _abstractMapStateToProps
  30. } from './AbstractWelcomePage';
  31. import styles from './styles.native';
  32. interface IProps extends AbstractProps {
  33. /**
  34. * Function for getting the unsafe room text.
  35. */
  36. getUnsafeRoomTextFn: Function;
  37. /**
  38. * Default prop for navigating between screen components(React Navigation).
  39. */
  40. navigation: any;
  41. }
  42. /**
  43. * The native container rendering the welcome page.
  44. *
  45. * @augments AbstractWelcomePage
  46. */
  47. class WelcomePage extends AbstractWelcomePage<IProps> {
  48. _onFieldBlur: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;
  49. _onFieldFocus: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;
  50. /**
  51. * Constructor of the Component.
  52. *
  53. * @inheritdoc
  54. */
  55. constructor(props: IProps) {
  56. super(props);
  57. this.state._fieldFocused = false;
  58. this.state.isSettingsScreenFocused = false;
  59. this.state.roomNameInputAnimation = new Animated.Value(1);
  60. this.state.hintBoxAnimation = new Animated.Value(0);
  61. // Bind event handlers so they are only bound once per instance.
  62. this._onFieldFocusChange = this._onFieldFocusChange.bind(this);
  63. this._renderHintBox = this._renderHintBox.bind(this);
  64. // Specially bind functions to avoid function definition on render.
  65. this._onFieldBlur = this._onFieldFocusChange.bind(this, false);
  66. this._onFieldFocus = this._onFieldFocusChange.bind(this, true);
  67. this._onSettingsScreenFocused = this._onSettingsScreenFocused.bind(this);
  68. }
  69. /**
  70. * Implements React's {@link Component#componentDidMount()}. Invoked
  71. * immediately after mounting occurs. Creates a local video track if none
  72. * is available and the camera permission was already granted.
  73. *
  74. * @inheritdoc
  75. * @returns {void}
  76. */
  77. componentDidMount() {
  78. super.componentDidMount();
  79. const {
  80. navigation,
  81. t
  82. } = this.props;
  83. navigation.setOptions({
  84. headerTitle: t('welcomepage.headerTitle')
  85. });
  86. navigation.addListener('focus', () => {
  87. this._updateRoomName();
  88. });
  89. navigation.addListener('blur', () => {
  90. this._clearTimeouts();
  91. this.setState({
  92. generatedRoomName: '',
  93. insecureRoomName: false,
  94. room: ''
  95. });
  96. });
  97. }
  98. /**
  99. * Implements React's {@link Component#render()}. Renders a prompt for
  100. * entering a room name.
  101. *
  102. * @inheritdoc
  103. * @returns {ReactElement}
  104. */
  105. render() {
  106. // We want to have the welcome page support the reduced UI layout,
  107. // but we ran into serious issues enabling it so we disable it
  108. // until we have a proper fix in place. We leave the code here though, because
  109. // this part should be fine when the bug is fixed.
  110. //
  111. // NOTE: when re-enabling, don't forget to uncomment the respective _mapStateToProps line too
  112. /*
  113. const { _reducedUI } = this.props;
  114. if (_reducedUI) {
  115. return this._renderReducedUI();
  116. }
  117. */
  118. return this._renderFullUI();
  119. }
  120. /**
  121. * Renders the insecure room name warning.
  122. *
  123. * @inheritdoc
  124. */
  125. _doRenderInsecureRoomNameWarning() {
  126. return (
  127. <View
  128. style = { [
  129. styles.messageContainer,
  130. styles.insecureRoomNameWarningContainer as ViewStyle
  131. ] }>
  132. <Icon
  133. src = { IconWarning }
  134. style = { styles.insecureRoomNameWarningIcon } />
  135. <Text style = { styles.insecureRoomNameWarningText }>
  136. { this.props.getUnsafeRoomTextFn(this.props.t) }
  137. </Text>
  138. </View>
  139. );
  140. }
  141. /**
  142. * Constructs a style array to handle the hint box animation.
  143. *
  144. * @private
  145. * @returns {Array<Object>}
  146. */
  147. _getHintBoxStyle() {
  148. return [
  149. styles.messageContainer,
  150. styles.hintContainer,
  151. {
  152. opacity: this.state.hintBoxAnimation
  153. }
  154. ];
  155. }
  156. /**
  157. * Callback for when the room field's focus changes so the hint box
  158. * must be rendered or removed.
  159. *
  160. * @private
  161. * @param {boolean} focused - The focused state of the field.
  162. * @returns {void}
  163. */
  164. _onFieldFocusChange(focused: boolean) {
  165. if (focused) {
  166. // Stop placeholder animation.
  167. this._clearTimeouts();
  168. this.setState({
  169. _fieldFocused: true,
  170. roomPlaceholder: ''
  171. });
  172. } else {
  173. // Restart room placeholder animation.
  174. this._updateRoomName();
  175. }
  176. Animated.timing(
  177. this.state.hintBoxAnimation,
  178. {
  179. duration: 300,
  180. toValue: focused ? 1 : 0,
  181. useNativeDriver: true
  182. })
  183. .start(animationState =>
  184. animationState.finished
  185. && !focused
  186. && this.setState({
  187. _fieldFocused: false
  188. }));
  189. }
  190. /**
  191. * Callback for when the settings screen is focused.
  192. *
  193. * @private
  194. * @param {boolean} focused - The focused state of the screen.
  195. * @returns {void}
  196. */
  197. _onSettingsScreenFocused(focused: boolean) {
  198. this.setState({
  199. isSettingsScreenFocused: focused
  200. });
  201. this.props.navigation.setOptions({
  202. headerShown: !focused
  203. });
  204. Animated.timing(
  205. this.state.roomNameInputAnimation,
  206. {
  207. toValue: focused ? 0 : 1,
  208. duration: 500,
  209. useNativeDriver: true
  210. })
  211. .start();
  212. }
  213. /**
  214. * Renders the hint box if necessary.
  215. *
  216. * @private
  217. * @returns {React$Node}
  218. */
  219. _renderHintBox() {
  220. const { t } = this.props;
  221. if (this.state._fieldFocused) {
  222. return (
  223. <Animated.View style = { this._getHintBoxStyle() as ViewStyle }>
  224. <View style = { styles.hintTextContainer } >
  225. <Text style = { styles.hintText as TextStyle }>
  226. { t('welcomepage.roomnameHint') }
  227. </Text>
  228. </View>
  229. <View style = { styles.hintButtonContainer as ViewStyle } >
  230. { this._renderJoinButton() }
  231. </View>
  232. </Animated.View>
  233. );
  234. }
  235. return null;
  236. }
  237. /**
  238. * Renders the join button.
  239. *
  240. * @private
  241. * @returns {ReactElement}
  242. */
  243. _renderJoinButton() {
  244. const { t } = this.props;
  245. let joinButton;
  246. if (this.state.joining) {
  247. // TouchableHighlight is picky about what its children can be, so
  248. // wrap it in a native component, i.e. View to avoid having to
  249. // modify non-native children.
  250. joinButton = (
  251. <TouchableHighlight
  252. accessibilityLabel =
  253. { t('welcomepage.accessibilityLabel.join') }
  254. onPress = { this._onJoin }
  255. style = { styles.button as ViewStyle }>
  256. <View>
  257. <LoadingIndicator
  258. color = { BaseTheme.palette.icon01 }
  259. size = 'small' />
  260. </View>
  261. </TouchableHighlight>
  262. );
  263. } else {
  264. joinButton = (
  265. <Button
  266. accessibilityLabel = { 'welcomepage.accessibilityLabel.join' }
  267. labelKey = { 'welcomepage.join' }
  268. labelStyle = { styles.joinButtonLabel }
  269. onClick = { this._onJoin }
  270. type = { BUTTON_TYPES.PRIMARY } />
  271. );
  272. }
  273. return joinButton;
  274. }
  275. /**
  276. * Renders the room name input.
  277. *
  278. * @private
  279. * @returns {ReactElement}
  280. */
  281. _renderRoomNameInput() {
  282. const roomnameAccLabel = 'welcomepage.accessibilityLabel.roomname';
  283. const { t } = this.props;
  284. const { isSettingsScreenFocused } = this.state;
  285. return (
  286. <Animated.View
  287. style = { [
  288. isSettingsScreenFocused && styles.roomNameInputContainer,
  289. { opacity: this.state.roomNameInputAnimation }
  290. ] }>
  291. <SafeAreaView style = { styles.roomContainer as ViewStyle }>
  292. <View style = { styles.joinControls } >
  293. <Text style = { styles.enterRoomText }>
  294. { t('welcomepage.roomname') }
  295. </Text>
  296. <Input
  297. accessibilityLabel = { t(roomnameAccLabel) }
  298. autoCapitalize = { 'none' }
  299. autoFocus = { false }
  300. customStyles = {{ input: styles.customInput }}
  301. onBlur = { this._onFieldBlur }
  302. onChange = { this._onRoomChange }
  303. onFocus = { this._onFieldFocus }
  304. onSubmitEditing = { this._onJoin }
  305. placeholder = { this.state.roomPlaceholder }
  306. returnKeyType = { 'go' }
  307. value = { this.state.room } />
  308. {
  309. this._renderInsecureRoomNameWarning()
  310. }
  311. {
  312. this._renderHintBox()
  313. }
  314. </View>
  315. </SafeAreaView>
  316. </Animated.View>
  317. );
  318. }
  319. /**
  320. * Renders the full welcome page.
  321. *
  322. * @returns {ReactElement}
  323. */
  324. _renderFullUI() {
  325. return (
  326. <>
  327. { this._renderRoomNameInput() }
  328. <View style = { styles.welcomePage as ViewStyle }>
  329. <WelcomePageTabs
  330. disabled = { Boolean(this.state._fieldFocused) } // @ts-ignore
  331. onListContainerPress = { this._onFieldBlur }
  332. onSettingsScreenFocused = { this._onSettingsScreenFocused } />
  333. </View>
  334. </>
  335. );
  336. }
  337. /**
  338. * Renders a "reduced" version of the welcome page.
  339. *
  340. * @returns {ReactElement}
  341. */
  342. _renderReducedUI() {
  343. const { t } = this.props;
  344. return (
  345. <View style = { styles.reducedUIContainer as ViewStyle }>
  346. <Text style = { styles.reducedUIText }>
  347. { t('welcomepage.reducedUIText', { app: getName() }) }
  348. </Text>
  349. </View>
  350. );
  351. }
  352. }
  353. /**
  354. * Maps part of the Redux state to the props of this component.
  355. *
  356. * @param {Object} state - The Redux state.
  357. * @returns {Object}
  358. */
  359. function _mapStateToProps(state: IReduxState) {
  360. return {
  361. ..._abstractMapStateToProps(state),
  362. // _reducedUI: state['features/base/responsive-ui'].reducedUI
  363. getUnsafeRoomTextFn: (t: Function) => getUnsafeRoomText(state, t, 'welcome')
  364. };
  365. }
  366. export default translate(connect(_mapStateToProps)(WelcomePage));