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.

NotificationsContainer.js 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // @flow
  2. import React, { Component } from 'react';
  3. import { Platform } from 'react-native';
  4. import { SafeAreaView } from 'react-native-safe-area-context';
  5. import { connect } from '../../../base/redux';
  6. import { hideNotification } from '../../actions';
  7. import { areThereNotifications } from '../../functions';
  8. import Notification from './Notification';
  9. type Props = {
  10. /**
  11. * The notifications to be displayed, with the first index being the
  12. * notification at the top and the rest shown below it in order.
  13. */
  14. _notifications: Array<Object>,
  15. /**
  16. * Invoked to update the redux store in order to remove notifications.
  17. */
  18. dispatch: Function,
  19. toolboxVisible: boolean
  20. };
  21. /**
  22. * Implements a React {@link Component} which displays notifications and handles
  23. * automatic dismissmal after a notification is shown for a defined timeout
  24. * period.
  25. *
  26. * @augments {Component}
  27. */
  28. class NotificationsContainer extends Component<Props> {
  29. /**
  30. * A timeout id returned by setTimeout.
  31. */
  32. _notificationDismissTimeout: ?TimeoutID;
  33. /**
  34. * Initializes a new {@code NotificationsContainer} instance.
  35. *
  36. * @inheritdoc
  37. */
  38. constructor(props: Props) {
  39. super(props);
  40. /**
  41. * The timeout set for automatically dismissing a displayed
  42. * notification. This value is set on the instance and not state to
  43. * avoid additional re-renders.
  44. *
  45. * @type {number|null}
  46. */
  47. this._notificationDismissTimeout = null;
  48. // Bind event handlers so they are only bound once for every instance.
  49. this._onDismissed = this._onDismissed.bind(this);
  50. }
  51. /**
  52. * Sets a timeout (if applicable).
  53. *
  54. * @inheritdoc
  55. */
  56. componentDidMount() {
  57. // Set the initial dismiss timeout (if any)
  58. this._manageDismissTimeout();
  59. }
  60. /**
  61. * Sets a timeout if the currently displayed notification has changed.
  62. *
  63. * @inheritdoc
  64. */
  65. componentDidUpdate(prevProps: Props) {
  66. this._manageDismissTimeout(prevProps);
  67. }
  68. /**
  69. * Sets/clears the dismiss timeout for the top notification.
  70. *
  71. * @param {P} [prevProps] - The previous properties (if called from
  72. * {@code componentDidUpdate}).
  73. * @returns {void}
  74. * @private
  75. */
  76. _manageDismissTimeout(prevProps: ?Props) {
  77. const { _notifications } = this.props;
  78. if (_notifications.length) {
  79. const notification = _notifications[0];
  80. const previousNotification
  81. = prevProps && prevProps._notifications.length
  82. ? prevProps._notifications[0]
  83. : undefined;
  84. if (notification !== previousNotification) {
  85. this._clearNotificationDismissTimeout();
  86. if (notification && notification.timeout) {
  87. const {
  88. timeout,
  89. uid
  90. } = notification;
  91. this._notificationDismissTimeout = setTimeout(() => {
  92. // Perform a no-op if a timeout is not specified.
  93. this._onDismissed(uid);
  94. }, timeout);
  95. }
  96. }
  97. } else if (this._notificationDismissTimeout) {
  98. // Clear timeout when all notifications are cleared (e.g external
  99. // call to clear them)
  100. this._clearNotificationDismissTimeout();
  101. }
  102. }
  103. /**
  104. * Clear any dismissal timeout that is still active.
  105. *
  106. * @inheritdoc
  107. */
  108. componentWillUnmount() {
  109. this._clearNotificationDismissTimeout();
  110. }
  111. /**
  112. * Clears the running notification dismiss timeout, if any.
  113. *
  114. * @returns {void}
  115. */
  116. _clearNotificationDismissTimeout() {
  117. this._notificationDismissTimeout && clearTimeout(this._notificationDismissTimeout);
  118. this._notificationDismissTimeout = null;
  119. }
  120. _onDismissed: number => void;
  121. /**
  122. * Emits an action to remove the notification from the redux store so it
  123. * stops displaying.
  124. *
  125. * @param {number} uid - The id of the notification to be removed.
  126. * @private
  127. * @returns {void}
  128. */
  129. _onDismissed(uid) {
  130. const { _notifications } = this.props;
  131. // Clear the timeout only if it's the top notification that's being
  132. // dismissed (the timeout is set only for the top one).
  133. if (!_notifications.length || _notifications[0].uid === uid) {
  134. this._clearNotificationDismissTimeout();
  135. }
  136. this.props.dispatch(hideNotification(uid));
  137. }
  138. /**
  139. * Implements React's {@link Component#render()}.
  140. *
  141. * @inheritdoc
  142. */
  143. render() {
  144. const { _notifications, toolboxVisible } = this.props;
  145. const bottomEdge = Platform.OS === 'ios' && !toolboxVisible;
  146. return (
  147. <SafeAreaView
  148. edges = { [ bottomEdge && 'bottom', 'left', 'right' ].filter(Boolean) }>
  149. {
  150. _notifications.map((notification, index) => {
  151. const { props, uid } = notification;
  152. return (
  153. <Notification
  154. { ...props }
  155. key = { index }
  156. onDismissed = { this._onDismissed }
  157. uid = { uid } />
  158. );
  159. })
  160. }
  161. </SafeAreaView>
  162. );
  163. }
  164. _onDismissed: number => void;
  165. }
  166. /**
  167. * Maps (parts of) the Redux state to the associated NotificationsContainer's
  168. * props.
  169. *
  170. * @param {Object} state - The Redux state.
  171. * @private
  172. * @returns {Props}
  173. */
  174. export function mapStateToProps(state: Object) {
  175. const { notifications } = state['features/notifications'];
  176. const _visible = areThereNotifications(state);
  177. return {
  178. _notifications: _visible ? notifications : []
  179. };
  180. }
  181. export default connect(mapStateToProps)(NotificationsContainer);