Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

WelcomePage.native.tsx 12KB

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