feat(calendar-sync): refactored ConferenceNotification (#12945)

* feat(calendar-sync): converted ConferenceNotification to a notification
pull/13022/head jitsi-meet_8383
Calinteodor 2 years ago committed by GitHub
parent c8ecd47ff5
commit 1f6483daae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      lang/main.json
  2. 296
      react/features/calendar-sync/components/ConferenceNotification.native.js
  3. 0
      react/features/calendar-sync/components/ConferenceNotification.web.js
  4. 1
      react/features/calendar-sync/components/index.js
  5. 2
      react/features/calendar-sync/reducer.tsx
  6. 19
      react/features/conference/components/native/Conference.js
  7. 151
      react/features/conference/middleware.ts
  8. 7
      react/features/notifications/constants.ts

@ -68,9 +68,9 @@
},
"join": "Join",
"joinTooltip": "Join the meeting",
"nextMeeting": "next meeting",
"nextMeeting": "Next meeting",
"noEvents": "There are no upcoming events scheduled.",
"ongoingMeeting": "ongoing meeting",
"ongoingMeeting": "Ongoing meeting",
"permissionButton": "Open settings",
"permissionMessage": "The Calendar permission is required to see your meetings in the app.",
"refresh": "Refresh calendar",
@ -683,6 +683,7 @@
"invitedOneMember": "{{name}} has been invited",
"invitedThreePlusMembers": "{{name}} and {{count}} others have been invited",
"invitedTwoMembers": "{{first}} and {{second}} have been invited",
"joinMeeting": "Join",
"kickParticipant": "{{kicked}} was kicked by {{kicker}}",
"leftOneMember": "{{name}} left the meeting",
"leftThreePlusMembers": "{{name}} and many others left the meeting",

@ -1,296 +0,0 @@
// @flow
import React, { Component } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { appNavigate } from '../../app/actions';
import { getURLWithoutParamsNormalized } from '../../base/connection';
import { getLocalizedDateFormatter, translate } from '../../base/i18n';
import { Icon, IconArrowRight } from '../../base/icons';
import { connect } from '../../base/redux';
import { ASPECT_RATIO_NARROW } from '../../base/responsive-ui';
import styles from './styles';
const ALERT_MILLISECONDS = 5 * 60 * 1000;
/**
* The type of the React {@code Component} props of
* {@link ConferenceNotification}.
*/
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>,
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The translate function.
*/
t: Function
};
/**
* The type of the React {@code Component} state of
* {@link ConferenceNotification}.
*/
type State = {
/**
* The event object to display the notification for.
*/
event?: Object
};
/**
* Component to display a permanent badge-like notification on the conference
* screen when another meeting is about to start.
*/
class ConferenceNotification extends Component<Props, State> {
updateIntervalId: IntervalID;
/**
* Constructor of the ConferenceNotification component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this.state = {
event: undefined
};
// Bind event handlers so they are only bound once per instance.
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() {
clearInterval(this.updateIntervalId);
}
/**
* Implements the React Components's render.
*
* @inheritdoc
*/
render() {
const { event } = this.state;
const { t } = this.props;
if (event) {
const now = Date.now();
const label
= event.startDate < now && event.endDate > now
? 'calendarSync.ongoingMeeting'
: 'calendarSync.nextMeeting';
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 }>
{ t(label) }
</Text>
<Text style = { styles.notificationText }>
{
getLocalizedDateFormatter(
event.startDate
).fromNow()
}
</Text>
</View>
<View
style = {
styles.notificationIconContainer
}>
<Icon
src = { IconArrowRight }
style = { styles.notificationIcon } />
</View>
</View>
</TouchableOpacity>
</View>
</View>
);
}
return null;
}
_getNotificationContentStyle: () => Array<Object>;
/**
* 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) {
const eventUrl
= event.url
&& getURLWithoutParamsNormalized(new URL(event.url));
if (eventUrl && eventUrl !== _currentConferenceURL) {
if ((!eventToShow
&& event.startDate > now
&& event.startDate < now + ALERT_MILLISECONDS)
|| (event.startDate < now && event.endDate > now)) {
eventToShow = event;
}
}
}
}
this.setState({
event: eventToShow
});
}
_onGoToNext: () => void;
/**
* Opens the meeting URL that the notification shows.
*
* @private
* @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 {{
* _aspectRatio: Symbol,
* _currentConferenceURL: string,
* _eventList: Array
* }}
*/
function _mapStateToProps(state: Object) {
const { locationURL } = state['features/base/connection'];
return {
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
_currentConferenceURL:
locationURL ? getURLWithoutParamsNormalized(locationURL) : '',
_eventList: state['features/calendar-sync'].events
};
}
export default translate(connect(_mapStateToProps)(ConferenceNotification));

@ -1,4 +1,3 @@
export { default as ConferenceNotification } from './ConferenceNotification';
export { default as CalendarList } from './CalendarList';
export { default as MicrosoftSignInButton } from './MicrosoftSignInButton';
export {

@ -31,7 +31,9 @@ export interface ICalendarSyncState {
error?: Object;
events: Array<{
calendarId: string;
endDate: string;
id: string;
startDate: string;
url: string;
}>;
integrationReady: boolean;

@ -12,7 +12,7 @@ import { Container, LoadingIndicator, TintedView } from '../../../base/react';
import { connect } from '../../../base/redux';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { TestConnectionInfo } from '../../../base/testing';
import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
import { isCalendarEnabled } from '../../../calendar-sync/functions.native';
import { DisplayNameLabel } from '../../../display-name';
import { BrandingImageBackground } from '../../../dynamic-branding/components/native';
import {
@ -321,21 +321,6 @@ class Conference extends AbstractConference<Props, State> {
return true;
}
/**
* Renders the conference notification badge if the feature is enabled.
*
* @private
* @returns {React$Node}
*/
_renderConferenceNotification() {
const { _calendarEnabled, _reducedUI } = this.props;
return (
_calendarEnabled && !_reducedUI
? <ConferenceNotification />
: undefined);
}
_createOnPress: (string) => void;
/**
@ -472,8 +457,6 @@ class Conference extends AbstractConference<Props, State> {
<TestConnectionInfo />
{ this._renderConferenceNotification() }
{_shouldDisplayTileView && <Toolbox />}
</>
);

@ -1,31 +1,56 @@
/* eslint-disable lines-around-comment */
import i18n from 'i18next';
import { batch } from 'react-redux';
// @ts-ignore
import { appNavigate } from '../app/actions';
import { IStore } from '../app/types';
import { CONFERENCE_JOINED, KICKED_OUT } from '../base/conference/actionTypes';
import {
CONFERENCE_FAILED,
CONFERENCE_JOINED,
CONFERENCE_LEFT,
KICKED_OUT
} from '../base/conference/actionTypes';
import { conferenceLeft } from '../base/conference/actions';
import { getCurrentConference } from '../base/conference/functions';
import { getURLWithoutParamsNormalized } from '../base/connection/utils';
import { hideDialog } from '../base/dialog/actions';
import { isDialogOpen } from '../base/dialog/functions';
import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
import { pinParticipant } from '../base/participants/actions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes';
// eslint-disable-next-line lines-around-comment
import { BUTTON_TYPES } from '../base/ui/constants.any';
// @ts-ignore
import { isCalendarEnabled } from '../calendar-sync';
// @ts-ignore
import { FeedbackDialog } from '../feedback';
import { setFilmstripEnabled } from '../filmstrip/actions';
import { setFilmstripEnabled } from '../filmstrip/actions.any';
import { hideNotification, showNotification } from '../notifications/actions';
import {
CALENDAR_NOTIFICATION_ID,
NOTIFICATION_ICON,
NOTIFICATION_TIMEOUT_TYPE
} from '../notifications/constants';
import { showSalesforceNotification } from '../salesforce/actions';
import { setToolboxEnabled } from '../toolbox/actions';
import { setToolboxEnabled } from '../toolbox/actions.any';
// @ts-ignore
import { notifyKickedOut } from './actions';
let intervalId: any;
MiddlewareRegistry.register(store => next => action => {
const result = next(action);
switch (action.type) {
case CONFERENCE_JOINED:
case CONFERENCE_JOINED: {
_conferenceJoined(store);
break;
}
case SET_REDUCED_UI: {
_setReducedUI(store);
@ -46,6 +71,14 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case CONFERENCE_LEFT:
case CONFERENCE_FAILED: {
clearInterval(intervalId);
intervalId = null;
break;
}
}
return result;
@ -113,5 +146,113 @@ function _conferenceJoined({ dispatch, getState }: IStore) {
getState
});
if (!intervalId) {
intervalId = setInterval(() =>
_maybeDisplayCalendarNotification({
dispatch,
getState
}), 10 * 1000);
}
dispatch(showSalesforceNotification());
}
/**
* Periodically checks if there is an event in the calendar for which we
* need to show a notification.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @private
* @returns {void}
*/
function _maybeDisplayCalendarNotification({ dispatch, getState }: IStore) {
const state = getState();
const calendarEnabled = isCalendarEnabled(state);
const { events: eventList } = state['features/calendar-sync'];
const { locationURL } = state['features/base/connection'];
const { reducedUI } = state['features/base/responsive-ui'];
const currentConferenceURL
= locationURL ? getURLWithoutParamsNormalized(locationURL) : '';
const ALERT_MILLISECONDS = 5 * 60 * 1000;
const now = Date.now();
let eventToShow;
if (!calendarEnabled && reducedUI) {
return;
}
for (const event of eventList) {
const eventURL
= event?.url && getURLWithoutParamsNormalized(new URL(event.url));
if (eventURL && eventURL !== currentConferenceURL) {
// @ts-ignore
if ((!eventToShow && event.startDate > now && event.startDate < now + ALERT_MILLISECONDS)
// @ts-ignore
|| (event.startDate < now && event.endDate > now)) {
eventToShow = event;
}
}
}
_calendarNotification(
{
dispatch,
getState
}, eventToShow
);
}
/**
* Calendar notification.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {eventToShow} eventToShow - Next or ongoing event.
* @private
* @returns {void}
*/
function _calendarNotification({ dispatch, getState }: IStore, eventToShow: any) {
const state = getState();
const { locationURL } = state['features/base/connection'];
const currentConferenceURL
= locationURL ? getURLWithoutParamsNormalized(locationURL) : '';
const now = Date.now();
if (!eventToShow) {
return;
}
const customActionNameKey = [ 'notify.joinMeeting' ];
const customActionType = [ BUTTON_TYPES.PRIMARY ];
const customActionHandler = [ () => batch(() => {
dispatch(hideNotification(CALENDAR_NOTIFICATION_ID));
if (eventToShow?.url && (eventToShow.url !== currentConferenceURL)) {
dispatch(appNavigate(eventToShow.url));
}
}) ];
const description
= getLocalizedDateFormatter(eventToShow.startDate).fromNow();
const icon = NOTIFICATION_ICON.WARNING;
const title = (eventToShow.startDate < now) && (eventToShow.endDate > now)
? i18n.t('calendarSync.ongoingMeeting')
: i18n.t('calendarSync.nextMeeting');
const uid = CALENDAR_NOTIFICATION_ID;
dispatch(showNotification({
customActionHandler,
customActionNameKey,
customActionType,
description,
icon,
title,
uid
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}

@ -56,6 +56,13 @@ export const NOTIFICATION_ICON = {
PARTICIPANTS: 'participants'
};
/**
* The identifier of the calendar notification.
*
* @type {string}
*/
export const CALENDAR_NOTIFICATION_ID = 'CALENDAR_NOTIFICATION_ID';
/**
* The identifier of the disable self view notification.
*

Loading…
Cancel
Save