2018-02-12 15:53:23 +00:00
|
|
|
// @flow
|
2018-04-16 02:04:57 +00:00
|
|
|
|
2018-02-12 15:53:23 +00:00
|
|
|
import React, { Component } from 'react';
|
2018-04-16 02:04:57 +00:00
|
|
|
import { Text, TouchableOpacity, View } from 'react-native';
|
2018-02-12 15:53:23 +00:00
|
|
|
|
2020-06-04 14:09:13 +00:00
|
|
|
import { appNavigate } from '../../app/actions';
|
2018-04-04 09:21:02 +00:00
|
|
|
import { getURLWithoutParamsNormalized } from '../../base/connection';
|
2018-04-16 02:04:57 +00:00
|
|
|
import { getLocalizedDateFormatter, translate } from '../../base/i18n';
|
2019-08-30 16:39:06 +00:00
|
|
|
import { Icon, IconNotificationJoin } from '../../base/icons';
|
2019-03-21 16:38:29 +00:00
|
|
|
import { connect } from '../../base/redux';
|
2018-02-12 15:53:23 +00:00
|
|
|
import { ASPECT_RATIO_NARROW } from '../../base/responsive-ui';
|
|
|
|
|
|
|
|
import styles from './styles';
|
|
|
|
|
|
|
|
const ALERT_MILLISECONDS = 5 * 60 * 1000;
|
|
|
|
|
2018-06-04 19:52:51 +00:00
|
|
|
/**
|
|
|
|
* The type of the React {@code Component} props of
|
|
|
|
* {@link ConferenceNotification}.
|
|
|
|
*/
|
2018-02-12 15:53:23 +00:00
|
|
|
type Props = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The current aspect ratio of the screen.
|
|
|
|
*/
|
|
|
|
_aspectRatio: Symbol,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The URL of the current conference without params.
|
|
|
|
*/
|
|
|
|
_currentConferenceURL: string,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The calendar event list.
|
|
|
|
*/
|
|
|
|
_eventList: Array<Object>,
|
|
|
|
|
2018-04-16 02:04:57 +00:00
|
|
|
/**
|
|
|
|
* The Redux dispatch function.
|
|
|
|
*/
|
|
|
|
dispatch: Function,
|
|
|
|
|
2018-02-12 15:53:23 +00:00
|
|
|
/**
|
|
|
|
* The translate function.
|
|
|
|
*/
|
|
|
|
t: Function
|
|
|
|
};
|
|
|
|
|
2018-06-04 19:52:51 +00:00
|
|
|
/**
|
|
|
|
* The type of the React {@code Component} state of
|
|
|
|
* {@link ConferenceNotification}.
|
|
|
|
*/
|
2018-02-12 15:53:23 +00:00
|
|
|
type State = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The event object to display the notification for.
|
|
|
|
*/
|
|
|
|
event?: Object
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2018-04-16 02:04:57 +00:00
|
|
|
* Component to display a permanent badge-like notification on the conference
|
|
|
|
* screen when another meeting is about to start.
|
2018-02-12 15:53:23 +00:00
|
|
|
*/
|
|
|
|
class ConferenceNotification extends Component<Props, State> {
|
2018-05-29 19:54:29 +00:00
|
|
|
updateIntervalId: IntervalID;
|
2018-02-12 15:53:23 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor of the ConferenceNotification component.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2018-05-22 14:23:03 +00:00
|
|
|
constructor(props: Props) {
|
2018-02-12 15:53:23 +00:00
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
event: undefined
|
|
|
|
};
|
|
|
|
|
2018-06-04 19:52:51 +00:00
|
|
|
// Bind event handlers so they are only bound once per instance.
|
2018-02-12 15:53:23 +00:00
|
|
|
this._getNotificationContentStyle
|
|
|
|
= this._getNotificationContentStyle.bind(this);
|
|
|
|
this._getNotificationPosition
|
|
|
|
= this._getNotificationPosition.bind(this);
|
|
|
|
this._maybeDisplayNotification
|
|
|
|
= this._maybeDisplayNotification.bind(this);
|
|
|
|
this._onGoToNext = this._onGoToNext.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements React Component's componentDidMount.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
componentDidMount() {
|
|
|
|
this.updateIntervalId = setInterval(
|
|
|
|
this._maybeDisplayNotification,
|
|
|
|
10 * 1000
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements React Component's componentWillUnmount.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
componentWillUnmount() {
|
2018-05-22 14:23:03 +00:00
|
|
|
clearInterval(this.updateIntervalId);
|
2018-02-12 15:53:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-04-16 02:04:57 +00:00
|
|
|
* Implements the React Components's render.
|
2018-02-12 15:53:23 +00:00
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
render() {
|
|
|
|
const { event } = this.state;
|
|
|
|
const { t } = this.props;
|
|
|
|
|
|
|
|
if (event) {
|
2018-05-23 15:10:50 +00:00
|
|
|
const now = Date.now();
|
|
|
|
const label
|
|
|
|
= event.startDate < now && event.endDate > now
|
|
|
|
? 'calendarSync.ongoingMeeting'
|
|
|
|
: 'calendarSync.nextMeeting';
|
|
|
|
|
2018-02-12 15:53:23 +00:00
|
|
|
return (
|
|
|
|
<View
|
|
|
|
style = { [
|
|
|
|
styles.notificationContainer,
|
|
|
|
this._getNotificationPosition()
|
|
|
|
] } >
|
|
|
|
<View
|
|
|
|
style = { this._getNotificationContentStyle() }>
|
|
|
|
<TouchableOpacity
|
|
|
|
onPress = { this._onGoToNext } >
|
|
|
|
<View style = { styles.touchableView }>
|
|
|
|
<View
|
|
|
|
style = {
|
|
|
|
styles.notificationTextContainer
|
|
|
|
}>
|
|
|
|
<Text style = { styles.notificationText }>
|
2018-05-23 15:10:50 +00:00
|
|
|
{ t(label) }
|
2018-02-12 15:53:23 +00:00
|
|
|
</Text>
|
|
|
|
<Text style = { styles.notificationText }>
|
|
|
|
{
|
|
|
|
getLocalizedDateFormatter(
|
|
|
|
event.startDate
|
|
|
|
).fromNow()
|
|
|
|
}
|
|
|
|
</Text>
|
|
|
|
</View>
|
|
|
|
<View
|
|
|
|
style = {
|
|
|
|
styles.notificationIconContainer
|
|
|
|
}>
|
|
|
|
<Icon
|
2019-08-30 16:39:06 +00:00
|
|
|
src = { IconNotificationJoin }
|
2018-02-12 15:53:23 +00:00
|
|
|
style = { styles.notificationIcon } />
|
|
|
|
</View>
|
|
|
|
</View>
|
|
|
|
</TouchableOpacity>
|
|
|
|
</View>
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-05-22 14:23:03 +00:00
|
|
|
_getNotificationContentStyle: () => Array<Object>;
|
2018-02-12 15:53:23 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Decides the color of the notification and some additional
|
|
|
|
* styles based on notificationPosition.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {Array<Object>}
|
|
|
|
*/
|
|
|
|
_getNotificationContentStyle() {
|
|
|
|
const { event } = this.state;
|
|
|
|
const { _aspectRatio } = this.props;
|
|
|
|
const now = Date.now();
|
|
|
|
const style = [
|
|
|
|
styles.notificationContent
|
|
|
|
];
|
|
|
|
|
|
|
|
if (event && event.startDate < now && event.endDate > now) {
|
|
|
|
style.push(styles.notificationContentPast);
|
|
|
|
} else {
|
|
|
|
style.push(styles.notificationContentNext);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_aspectRatio === ASPECT_RATIO_NARROW) {
|
|
|
|
style.push(styles.notificationContentSide);
|
|
|
|
} else {
|
|
|
|
style.push(styles.notificationContentTop);
|
|
|
|
}
|
|
|
|
|
|
|
|
return style;
|
|
|
|
}
|
|
|
|
|
|
|
|
_getNotificationPosition: () => Object;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decides the position of the notification.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
|
|
|
_getNotificationPosition() {
|
|
|
|
const { _aspectRatio } = this.props;
|
|
|
|
|
|
|
|
if (_aspectRatio === ASPECT_RATIO_NARROW) {
|
|
|
|
return styles.notificationContainerSide;
|
|
|
|
}
|
|
|
|
|
|
|
|
return styles.notificationContainerTop;
|
|
|
|
}
|
|
|
|
|
|
|
|
_maybeDisplayNotification: () => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Periodically checks if there is an event in the calendar for which we
|
|
|
|
* need to show a notification.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_maybeDisplayNotification() {
|
|
|
|
const { _currentConferenceURL, _eventList } = this.props;
|
|
|
|
let eventToShow;
|
|
|
|
|
|
|
|
if (_eventList && _eventList.length) {
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
|
|
for (const event of _eventList) {
|
2018-04-16 02:04:57 +00:00
|
|
|
const eventUrl
|
2018-09-04 07:29:48 +00:00
|
|
|
= event.url
|
|
|
|
&& getURLWithoutParamsNormalized(new URL(event.url));
|
2018-04-04 09:21:02 +00:00
|
|
|
|
2018-09-04 07:29:48 +00:00
|
|
|
if (eventUrl && eventUrl !== _currentConferenceURL) {
|
2018-02-12 15:53:23 +00:00
|
|
|
if ((!eventToShow
|
2018-04-16 02:04:57 +00:00
|
|
|
&& event.startDate > now
|
|
|
|
&& event.startDate < now + ALERT_MILLISECONDS)
|
|
|
|
|| (event.startDate < now && event.endDate > now)) {
|
2018-02-12 15:53:23 +00:00
|
|
|
eventToShow = event;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
event: eventToShow
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_onGoToNext: () => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Opens the meeting URL that the notification shows.
|
|
|
|
*
|
2018-04-16 02:04:57 +00:00
|
|
|
* @private
|
2018-02-12 15:53:23 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_onGoToNext() {
|
|
|
|
const { event } = this.state;
|
|
|
|
|
|
|
|
if (event && event.url) {
|
|
|
|
this.props.dispatch(appNavigate(event.url));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maps redux state to component props.
|
|
|
|
*
|
|
|
|
* @param {Object} state - The redux state.
|
|
|
|
* @returns {{
|
2018-04-16 02:04:57 +00:00
|
|
|
* _aspectRatio: Symbol,
|
|
|
|
* _currentConferenceURL: string,
|
|
|
|
* _eventList: Array
|
2018-02-12 15:53:23 +00:00
|
|
|
* }}
|
|
|
|
*/
|
2018-04-16 16:39:26 +00:00
|
|
|
function _mapStateToProps(state: Object) {
|
2018-02-12 15:53:23 +00:00
|
|
|
const { locationURL } = state['features/base/connection'];
|
|
|
|
|
|
|
|
return {
|
|
|
|
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
|
|
|
_currentConferenceURL:
|
2018-04-16 02:04:57 +00:00
|
|
|
locationURL ? getURLWithoutParamsNormalized(locationURL) : '',
|
2018-02-12 15:53:23 +00:00
|
|
|
_eventList: state['features/calendar-sync'].events
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-05-28 13:32:29 +00:00
|
|
|
export default translate(connect(_mapStateToProps)(ConferenceNotification));
|