Explorar el Código

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

* feat(calendar-sync): converted ConferenceNotification to a notification
factor2
Calinteodor hace 2 años
padre
commit
1f6483daae
No account linked to committer's email address

+ 3
- 2
lang/main.json Ver fichero

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

+ 0
- 296
react/features/calendar-sync/components/ConferenceNotification.native.js Ver fichero

@@ -1,296 +0,0 @@
1
-// @flow
2
-
3
-import React, { Component } from 'react';
4
-import { Text, TouchableOpacity, View } from 'react-native';
5
-
6
-import { appNavigate } from '../../app/actions';
7
-import { getURLWithoutParamsNormalized } from '../../base/connection';
8
-import { getLocalizedDateFormatter, translate } from '../../base/i18n';
9
-import { Icon, IconArrowRight } from '../../base/icons';
10
-import { connect } from '../../base/redux';
11
-import { ASPECT_RATIO_NARROW } from '../../base/responsive-ui';
12
-
13
-import styles from './styles';
14
-
15
-const ALERT_MILLISECONDS = 5 * 60 * 1000;
16
-
17
-/**
18
- * The type of the React {@code Component} props of
19
- * {@link ConferenceNotification}.
20
- */
21
-type Props = {
22
-
23
-    /**
24
-     * The current aspect ratio of the screen.
25
-     */
26
-    _aspectRatio: Symbol,
27
-
28
-    /**
29
-     * The URL of the current conference without params.
30
-     */
31
-    _currentConferenceURL: string,
32
-
33
-    /**
34
-     * The calendar event list.
35
-     */
36
-    _eventList: Array<Object>,
37
-
38
-    /**
39
-     * The Redux dispatch function.
40
-     */
41
-    dispatch: Function,
42
-
43
-    /**
44
-     * The translate function.
45
-     */
46
-    t: Function
47
-};
48
-
49
-/**
50
- * The type of the React {@code Component} state of
51
- * {@link ConferenceNotification}.
52
- */
53
-type State = {
54
-
55
-    /**
56
-     * The event object to display the notification for.
57
-     */
58
-    event?: Object
59
-};
60
-
61
-/**
62
- * Component to display a permanent badge-like notification on the conference
63
- * screen when another meeting is about to start.
64
- */
65
-class ConferenceNotification extends Component<Props, State> {
66
-    updateIntervalId: IntervalID;
67
-
68
-    /**
69
-     * Constructor of the ConferenceNotification component.
70
-     *
71
-     * @inheritdoc
72
-     */
73
-    constructor(props: Props) {
74
-        super(props);
75
-
76
-        this.state = {
77
-            event: undefined
78
-        };
79
-
80
-        // Bind event handlers so they are only bound once per instance.
81
-        this._getNotificationContentStyle
82
-            = this._getNotificationContentStyle.bind(this);
83
-        this._getNotificationPosition
84
-            = this._getNotificationPosition.bind(this);
85
-        this._maybeDisplayNotification
86
-            = this._maybeDisplayNotification.bind(this);
87
-        this._onGoToNext = this._onGoToNext.bind(this);
88
-    }
89
-
90
-    /**
91
-     * Implements React Component's componentDidMount.
92
-     *
93
-     * @inheritdoc
94
-     */
95
-    componentDidMount() {
96
-        this.updateIntervalId = setInterval(
97
-            this._maybeDisplayNotification,
98
-            10 * 1000
99
-        );
100
-    }
101
-
102
-    /**
103
-     * Implements React Component's componentWillUnmount.
104
-     *
105
-     * @inheritdoc
106
-     */
107
-    componentWillUnmount() {
108
-        clearInterval(this.updateIntervalId);
109
-    }
110
-
111
-    /**
112
-     * Implements the React Components's render.
113
-     *
114
-     * @inheritdoc
115
-     */
116
-    render() {
117
-        const { event } = this.state;
118
-        const { t } = this.props;
119
-
120
-        if (event) {
121
-            const now = Date.now();
122
-            const label
123
-                = event.startDate < now && event.endDate > now
124
-                    ? 'calendarSync.ongoingMeeting'
125
-                    : 'calendarSync.nextMeeting';
126
-
127
-            return (
128
-                <View
129
-                    style = { [
130
-                        styles.notificationContainer,
131
-                        this._getNotificationPosition()
132
-                    ] } >
133
-                    <View
134
-                        style = { this._getNotificationContentStyle() }>
135
-                        <TouchableOpacity
136
-                            onPress = { this._onGoToNext } >
137
-                            <View style = { styles.touchableView }>
138
-                                <View
139
-                                    style = {
140
-                                        styles.notificationTextContainer
141
-                                    }>
142
-                                    <Text style = { styles.notificationText }>
143
-                                        { t(label) }
144
-                                    </Text>
145
-                                    <Text style = { styles.notificationText }>
146
-                                        {
147
-                                            getLocalizedDateFormatter(
148
-                                                event.startDate
149
-                                            ).fromNow()
150
-                                        }
151
-                                    </Text>
152
-                                </View>
153
-                                <View
154
-                                    style = {
155
-                                        styles.notificationIconContainer
156
-                                    }>
157
-                                    <Icon
158
-                                        src = { IconArrowRight }
159
-                                        style = { styles.notificationIcon } />
160
-                                </View>
161
-                            </View>
162
-                        </TouchableOpacity>
163
-                    </View>
164
-                </View>
165
-            );
166
-        }
167
-
168
-        return null;
169
-    }
170
-
171
-    _getNotificationContentStyle: () => Array<Object>;
172
-
173
-    /**
174
-     * Decides the color of the notification and some additional
175
-     * styles based on notificationPosition.
176
-     *
177
-     * @private
178
-     * @returns {Array<Object>}
179
-     */
180
-    _getNotificationContentStyle() {
181
-        const { event } = this.state;
182
-        const { _aspectRatio } = this.props;
183
-        const now = Date.now();
184
-        const style = [
185
-            styles.notificationContent
186
-        ];
187
-
188
-        if (event && event.startDate < now && event.endDate > now) {
189
-            style.push(styles.notificationContentPast);
190
-        } else {
191
-            style.push(styles.notificationContentNext);
192
-        }
193
-
194
-        if (_aspectRatio === ASPECT_RATIO_NARROW) {
195
-            style.push(styles.notificationContentSide);
196
-        } else {
197
-            style.push(styles.notificationContentTop);
198
-        }
199
-
200
-        return style;
201
-    }
202
-
203
-    _getNotificationPosition: () => Object;
204
-
205
-    /**
206
-     * Decides the position of the notification.
207
-     *
208
-     * @private
209
-     * @returns {Object}
210
-     */
211
-    _getNotificationPosition() {
212
-        const { _aspectRatio } = this.props;
213
-
214
-        if (_aspectRatio === ASPECT_RATIO_NARROW) {
215
-            return styles.notificationContainerSide;
216
-        }
217
-
218
-        return styles.notificationContainerTop;
219
-    }
220
-
221
-    _maybeDisplayNotification: () => void;
222
-
223
-    /**
224
-     * Periodically checks if there is an event in the calendar for which we
225
-     * need to show a notification.
226
-     *
227
-     * @private
228
-     * @returns {void}
229
-     */
230
-    _maybeDisplayNotification() {
231
-        const { _currentConferenceURL, _eventList } = this.props;
232
-        let eventToShow;
233
-
234
-        if (_eventList && _eventList.length) {
235
-            const now = Date.now();
236
-
237
-            for (const event of _eventList) {
238
-                const eventUrl
239
-                    = event.url
240
-                        && getURLWithoutParamsNormalized(new URL(event.url));
241
-
242
-                if (eventUrl && eventUrl !== _currentConferenceURL) {
243
-                    if ((!eventToShow
244
-                                && event.startDate > now
245
-                                && event.startDate < now + ALERT_MILLISECONDS)
246
-                            || (event.startDate < now && event.endDate > now)) {
247
-                        eventToShow = event;
248
-                    }
249
-                }
250
-            }
251
-        }
252
-
253
-        this.setState({
254
-            event: eventToShow
255
-        });
256
-    }
257
-
258
-    _onGoToNext: () => void;
259
-
260
-    /**
261
-     * Opens the meeting URL that the notification shows.
262
-     *
263
-     * @private
264
-     * @returns {void}
265
-     */
266
-    _onGoToNext() {
267
-        const { event } = this.state;
268
-
269
-        if (event && event.url) {
270
-            this.props.dispatch(appNavigate(event.url));
271
-        }
272
-    }
273
-}
274
-
275
-/**
276
- * Maps redux state to component props.
277
- *
278
- * @param {Object} state - The redux state.
279
- * @returns {{
280
- *     _aspectRatio: Symbol,
281
- *     _currentConferenceURL: string,
282
- *     _eventList: Array
283
- * }}
284
- */
285
-function _mapStateToProps(state: Object) {
286
-    const { locationURL } = state['features/base/connection'];
287
-
288
-    return {
289
-        _aspectRatio: state['features/base/responsive-ui'].aspectRatio,
290
-        _currentConferenceURL:
291
-            locationURL ? getURLWithoutParamsNormalized(locationURL) : '',
292
-        _eventList: state['features/calendar-sync'].events
293
-    };
294
-}
295
-
296
-export default translate(connect(_mapStateToProps)(ConferenceNotification));

+ 0
- 0
react/features/calendar-sync/components/ConferenceNotification.web.js Ver fichero


+ 0
- 1
react/features/calendar-sync/components/index.js Ver fichero

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

+ 2
- 0
react/features/calendar-sync/reducer.tsx Ver fichero

@@ -31,7 +31,9 @@ export interface ICalendarSyncState {
31 31
     error?: Object;
32 32
     events: Array<{
33 33
         calendarId: string;
34
+        endDate: string;
34 35
         id: string;
36
+        startDate: string;
35 37
         url: string;
36 38
     }>;
37 39
     integrationReady: boolean;

+ 1
- 18
react/features/conference/components/native/Conference.js Ver fichero

@@ -12,7 +12,7 @@ import { Container, LoadingIndicator, TintedView } from '../../../base/react';
12 12
 import { connect } from '../../../base/redux';
13 13
 import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
14 14
 import { TestConnectionInfo } from '../../../base/testing';
15
-import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
15
+import { isCalendarEnabled } from '../../../calendar-sync/functions.native';
16 16
 import { DisplayNameLabel } from '../../../display-name';
17 17
 import { BrandingImageBackground } from '../../../dynamic-branding/components/native';
18 18
 import {
@@ -321,21 +321,6 @@ class Conference extends AbstractConference<Props, State> {
321 321
         return true;
322 322
     }
323 323
 
324
-    /**
325
-     * Renders the conference notification badge if the feature is enabled.
326
-     *
327
-     * @private
328
-     * @returns {React$Node}
329
-     */
330
-    _renderConferenceNotification() {
331
-        const { _calendarEnabled, _reducedUI } = this.props;
332
-
333
-        return (
334
-            _calendarEnabled && !_reducedUI
335
-                ? <ConferenceNotification />
336
-                : undefined);
337
-    }
338
-
339 324
     _createOnPress: (string) => void;
340 325
 
341 326
     /**
@@ -472,8 +457,6 @@ class Conference extends AbstractConference<Props, State> {
472 457
 
473 458
                 <TestConnectionInfo />
474 459
 
475
-                { this._renderConferenceNotification() }
476
-
477 460
                 {_shouldDisplayTileView && <Toolbox />}
478 461
             </>
479 462
         );

+ 146
- 5
react/features/conference/middleware.ts Ver fichero

@@ -1,31 +1,56 @@
1
+/* eslint-disable lines-around-comment */
2
+import i18n from 'i18next';
3
+import { batch } from 'react-redux';
4
+
5
+// @ts-ignore
1 6
 import { appNavigate } from '../app/actions';
2 7
 import { IStore } from '../app/types';
3
-import { CONFERENCE_JOINED, KICKED_OUT } from '../base/conference/actionTypes';
8
+import {
9
+    CONFERENCE_FAILED,
10
+    CONFERENCE_JOINED,
11
+    CONFERENCE_LEFT,
12
+    KICKED_OUT
13
+} from '../base/conference/actionTypes';
4 14
 import { conferenceLeft } from '../base/conference/actions';
5 15
 import { getCurrentConference } from '../base/conference/functions';
16
+import { getURLWithoutParamsNormalized } from '../base/connection/utils';
6 17
 import { hideDialog } from '../base/dialog/actions';
7 18
 import { isDialogOpen } from '../base/dialog/functions';
19
+import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
8 20
 import { pinParticipant } from '../base/participants/actions';
9 21
 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
10 22
 import StateListenerRegistry from '../base/redux/StateListenerRegistry';
11 23
 import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes';
12
-// eslint-disable-next-line lines-around-comment
24
+import { BUTTON_TYPES } from '../base/ui/constants.any';
25
+// @ts-ignore
26
+import { isCalendarEnabled } from '../calendar-sync';
13 27
 // @ts-ignore
14 28
 import { FeedbackDialog } from '../feedback';
15
-import { setFilmstripEnabled } from '../filmstrip/actions';
29
+import { setFilmstripEnabled } from '../filmstrip/actions.any';
30
+import { hideNotification, showNotification } from '../notifications/actions';
31
+import {
32
+    CALENDAR_NOTIFICATION_ID,
33
+    NOTIFICATION_ICON,
34
+    NOTIFICATION_TIMEOUT_TYPE
35
+} from '../notifications/constants';
16 36
 import { showSalesforceNotification } from '../salesforce/actions';
17
-import { setToolboxEnabled } from '../toolbox/actions';
37
+import { setToolboxEnabled } from '../toolbox/actions.any';
18 38
 
39
+// @ts-ignore
19 40
 import { notifyKickedOut } from './actions';
20 41
 
42
+let intervalId: any;
43
+
44
+
21 45
 MiddlewareRegistry.register(store => next => action => {
22 46
     const result = next(action);
23 47
 
24 48
     switch (action.type) {
25
-    case CONFERENCE_JOINED:
49
+    case CONFERENCE_JOINED: {
26 50
         _conferenceJoined(store);
27 51
 
28 52
         break;
53
+    }
29 54
 
30 55
     case SET_REDUCED_UI: {
31 56
         _setReducedUI(store);
@@ -46,6 +71,14 @@ MiddlewareRegistry.register(store => next => action => {
46 71
 
47 72
         break;
48 73
     }
74
+
75
+    case CONFERENCE_LEFT:
76
+    case CONFERENCE_FAILED: {
77
+        clearInterval(intervalId);
78
+        intervalId = null;
79
+
80
+        break;
81
+    }
49 82
     }
50 83
 
51 84
     return result;
@@ -113,5 +146,113 @@ function _conferenceJoined({ dispatch, getState }: IStore) {
113 146
         getState
114 147
     });
115 148
 
149
+    if (!intervalId) {
150
+        intervalId = setInterval(() =>
151
+            _maybeDisplayCalendarNotification({
152
+                dispatch,
153
+                getState
154
+            }), 10 * 1000);
155
+    }
156
+
116 157
     dispatch(showSalesforceNotification());
117 158
 }
159
+
160
+/**
161
+ * Periodically checks if there is an event in the calendar for which we
162
+ * need to show a notification.
163
+ *
164
+ * @param {Store} store - The redux store in which the specified {@code action}
165
+ * is being dispatched.
166
+ * @private
167
+ * @returns {void}
168
+ */
169
+function _maybeDisplayCalendarNotification({ dispatch, getState }: IStore) {
170
+    const state = getState();
171
+
172
+    const calendarEnabled = isCalendarEnabled(state);
173
+    const { events: eventList } = state['features/calendar-sync'];
174
+    const { locationURL } = state['features/base/connection'];
175
+    const { reducedUI } = state['features/base/responsive-ui'];
176
+
177
+    const currentConferenceURL
178
+        = locationURL ? getURLWithoutParamsNormalized(locationURL) : '';
179
+    const ALERT_MILLISECONDS = 5 * 60 * 1000;
180
+    const now = Date.now();
181
+
182
+    let eventToShow;
183
+
184
+    if (!calendarEnabled && reducedUI) {
185
+        return;
186
+    }
187
+
188
+    for (const event of eventList) {
189
+
190
+        const eventURL
191
+            = event?.url && getURLWithoutParamsNormalized(new URL(event.url));
192
+
193
+        if (eventURL && eventURL !== currentConferenceURL) {
194
+            // @ts-ignore
195
+            if ((!eventToShow && event.startDate > now && event.startDate < now + ALERT_MILLISECONDS)
196
+                // @ts-ignore
197
+                || (event.startDate < now && event.endDate > now)) {
198
+                eventToShow = event;
199
+            }
200
+        }
201
+    }
202
+
203
+    _calendarNotification(
204
+        {
205
+            dispatch,
206
+            getState
207
+        }, eventToShow
208
+    );
209
+}
210
+
211
+/**
212
+ * Calendar notification.
213
+ *
214
+ * @param {Store} store - The redux store in which the specified {@code action}
215
+ * is being dispatched.
216
+ * @param {eventToShow} eventToShow - Next or ongoing event.
217
+ * @private
218
+ * @returns {void}
219
+ */
220
+function _calendarNotification({ dispatch, getState }: IStore, eventToShow: any) {
221
+    const state = getState();
222
+
223
+    const { locationURL } = state['features/base/connection'];
224
+
225
+    const currentConferenceURL
226
+        = locationURL ? getURLWithoutParamsNormalized(locationURL) : '';
227
+    const now = Date.now();
228
+
229
+    if (!eventToShow) {
230
+        return;
231
+    }
232
+
233
+    const customActionNameKey = [ 'notify.joinMeeting' ];
234
+    const customActionType = [ BUTTON_TYPES.PRIMARY ];
235
+    const customActionHandler = [ () => batch(() => {
236
+        dispatch(hideNotification(CALENDAR_NOTIFICATION_ID));
237
+        if (eventToShow?.url && (eventToShow.url !== currentConferenceURL)) {
238
+            dispatch(appNavigate(eventToShow.url));
239
+        }
240
+    }) ];
241
+    const description
242
+        = getLocalizedDateFormatter(eventToShow.startDate).fromNow();
243
+    const icon = NOTIFICATION_ICON.WARNING;
244
+    const title = (eventToShow.startDate < now) && (eventToShow.endDate > now)
245
+        ? i18n.t('calendarSync.ongoingMeeting')
246
+        : i18n.t('calendarSync.nextMeeting');
247
+    const uid = CALENDAR_NOTIFICATION_ID;
248
+
249
+    dispatch(showNotification({
250
+        customActionHandler,
251
+        customActionNameKey,
252
+        customActionType,
253
+        description,
254
+        icon,
255
+        title,
256
+        uid
257
+    }, NOTIFICATION_TIMEOUT_TYPE.STICKY));
258
+}

+ 7
- 0
react/features/notifications/constants.ts Ver fichero

@@ -56,6 +56,13 @@ export const NOTIFICATION_ICON = {
56 56
     PARTICIPANTS: 'participants'
57 57
 };
58 58
 
59
+/**
60
+ * The identifier of the calendar notification.
61
+ *
62
+ * @type {string}
63
+ */
64
+export const CALENDAR_NOTIFICATION_ID = 'CALENDAR_NOTIFICATION_ID';
65
+
59 66
 /**
60 67
  * The identifier of the disable self view notification.
61 68
  *

Loading…
Cancelar
Guardar