Bladeren bron

Add calendar-sync feature

master
zbettenbuk 7 jaren geleden
bovenliggende
commit
bba480f329
30 gewijzigde bestanden met toevoegingen van 1070 en 30 verwijderingen
  1. 8
    0
      android/app/src/main/java/org/jitsi/meet/MainActivity.java
  2. 1
    0
      android/sdk/build.gradle
  3. 1
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java
  4. 2
    0
      android/settings.gradle
  5. BIN
      images/calendar@2x.png
  6. BIN
      images/calendar@3x.png
  7. 2
    0
      ios/Podfile
  8. 7
    1
      ios/Podfile.lock
  9. 2
    0
      ios/app/src/Info.plist
  10. 6
    0
      lang/main.json
  11. 5
    0
      package-lock.json
  12. 1
    0
      package.json
  13. 49
    0
      react/features/base/styles/components/styles/PlatformElements.native.js
  14. 1
    0
      react/features/base/styles/components/styles/index.js
  15. 71
    0
      react/features/base/util/dateUtil.js
  16. 1
    0
      react/features/base/util/index.js
  17. 6
    0
      react/features/calendar-sync/actionTypes.js
  18. 18
    0
      react/features/calendar-sync/actions.js
  19. 290
    0
      react/features/calendar-sync/components/MeetingList.native.js
  20. 1
    0
      react/features/calendar-sync/components/index.js
  21. 109
    0
      react/features/calendar-sync/components/styles.js
  22. 4
    0
      react/features/calendar-sync/index.js
  23. 164
    0
      react/features/calendar-sync/middleware.js
  24. 28
    0
      react/features/calendar-sync/reducer.js
  25. 9
    3
      react/features/recent-list/components/RecentList.native.js
  26. 48
    0
      react/features/welcome/components/AbstractPagedList.js
  27. 97
    0
      react/features/welcome/components/PagedList.android.js
  28. 82
    0
      react/features/welcome/components/PagedList.ios.js
  29. 26
    22
      react/features/welcome/components/WelcomePage.native.js
  30. 31
    4
      react/features/welcome/components/styles.js

+ 8
- 0
android/app/src/main/java/org/jitsi/meet/MainActivity.java Bestand weergeven

@@ -23,6 +23,8 @@ import org.jitsi.meet.sdk.JitsiMeetActivity;
23 23
 import org.jitsi.meet.sdk.JitsiMeetView;
24 24
 import org.jitsi.meet.sdk.JitsiMeetViewListener;
25 25
 
26
+import com.calendarevents.CalendarEventsPackage;
27
+
26 28
 import java.util.Map;
27 29
 
28 30
 /**
@@ -103,4 +105,10 @@ public class MainActivity extends JitsiMeetActivity {
103 105
 
104 106
         super.onCreate(savedInstanceState);
105 107
     }
108
+
109
+    @Override
110
+  public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
111
+      CalendarEventsPackage.onRequestPermissionsResult(requestCode, permissions, grantResults);
112
+      super.onRequestPermissionsResult(requestCode, permissions, grantResults);
113
+  }
106 114
 }

+ 1
- 0
android/sdk/build.gradle Bestand weergeven

@@ -32,6 +32,7 @@ dependencies {
32 32
     compile project(':react-native-sound')
33 33
     compile project(':react-native-vector-icons')
34 34
     compile project(':react-native-webrtc')
35
+    compile project(':react-native-calendar-events')
35 36
 }
36 37
 
37 38
 // Build process helpers

+ 1
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java Bestand weergeven

@@ -114,6 +114,7 @@ public class JitsiMeetView extends FrameLayout {
114 114
                 .setApplication(application)
115 115
                 .setBundleAssetName("index.android.bundle")
116 116
                 .setJSMainModulePath("index.android")
117
+                .addPackage(new com.calendarevents.CalendarEventsPackage())
117 118
                 .addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
118 119
                 .addPackage(new com.facebook.react.shell.MainReactPackage())
119 120
                 .addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())

+ 2
- 0
android/settings.gradle Bestand weergeven

@@ -17,3 +17,5 @@ include ':react-native-vector-icons'
17 17
 project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
18 18
 include ':react-native-webrtc'
19 19
 project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
20
+include ':react-native-calendar-events'
21
+project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')

BIN
images/calendar@2x.png Bestand weergeven


BIN
images/calendar@3x.png Bestand weergeven


+ 2
- 0
ios/Podfile Bestand weergeven

@@ -30,6 +30,8 @@ target 'JitsiMeet' do
30 30
   pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
31 31
   pod 'RNSound', :path => '../node_modules/react-native-sound'
32 32
   pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
33
+  pod 'react-native-calendar-events',
34
+    :path => '../node_modules/react-native-calendar-events'
33 35
 end
34 36
 
35 37
 post_install do |installer|

+ 7
- 1
ios/Podfile.lock Bestand weergeven

@@ -3,6 +3,8 @@ PODS:
3 3
     - React/Core (= 0.51.0)
4 4
   - react-native-background-timer (2.0.0):
5 5
     - React
6
+  - react-native-calendar-events (1.4.3):
7
+    - React
6 8
   - react-native-fetch-blob (0.10.6):
7 9
     - React/Core
8 10
   - react-native-keep-awake (2.0.6):
@@ -52,6 +54,7 @@ PODS:
52 54
 
53 55
 DEPENDENCIES:
54 56
   - react-native-background-timer (from `../node_modules/react-native-background-timer`)
57
+  - react-native-calendar-events (from `../node_modules/react-native-calendar-events`)
55 58
   - react-native-fetch-blob (from `../node_modules/react-native-fetch-blob`)
56 59
   - react-native-keep-awake (from `../node_modules/react-native-keep-awake`)
57 60
   - react-native-locale-detector (from `../node_modules/react-native-locale-detector`)
@@ -75,6 +78,8 @@ EXTERNAL SOURCES:
75 78
     :path: ../node_modules/react-native
76 79
   react-native-background-timer:
77 80
     :path: ../node_modules/react-native-background-timer
81
+  react-native-calendar-events:
82
+    :path: ../node_modules/react-native-calendar-events
78 83
   react-native-fetch-blob:
79 84
     :path: ../node_modules/react-native-fetch-blob
80 85
   react-native-keep-awake:
@@ -93,6 +98,7 @@ EXTERNAL SOURCES:
93 98
 SPEC CHECKSUMS:
94 99
   React: 541ba768b9855e10cdc76f55427a5cd0653ca806
95 100
   react-native-background-timer: 63dcbf37dbcf294b5c6c071afcdc661fa06a7594
101
+  react-native-calendar-events: fe6fbc8ed337a7423c98f2c9012b25f20444de09
96 102
   react-native-fetch-blob: 63394b1d7b0781547b3e4463b3195790177b1222
97 103
   react-native-keep-awake: 0de4bd66de0c23178107dce0c2fcc3354b2a8e94
98 104
   react-native-locale-detector: d1b2c6fe5abb56e3a1efb6c2d6f308c05c4251f1
@@ -101,6 +107,6 @@ SPEC CHECKSUMS:
101 107
   RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
102 108
   yoga: 17521bbb0dd54a47c0b3ac43253e78cdac7488e0
103 109
 
104
-PODFILE CHECKSUM: 1e6ce4da1b385720c726f3f131a6aaf08bf9c0ba
110
+PODFILE CHECKSUM: 4a5a310403b99b9c2d619e0b18da89bf0fe5858c
105 111
 
106 112
 COCOAPODS: 1.4.0

+ 2
- 0
ios/app/src/Info.plist Bestand weergeven

@@ -55,6 +55,8 @@
55 55
 			</dict>
56 56
 		</dict>
57 57
 	</dict>
58
+    <key>NSCalendarsUsageDescription</key>
59
+    <string>Displays the user's meetings in the app.</string>
58 60
 	<key>NSCameraUsageDescription</key>
59 61
 	<string>Participate in conferences with video.</string>
60 62
 	<key>NSLocationWhenInUseUsageDescription</key>

+ 6
- 0
lang/main.json Bestand weergeven

@@ -51,6 +51,7 @@
51 51
             "audio": "Voice",
52 52
             "video": "Video"
53 53
         },
54
+        "calendar": "Calendar",
54 55
         "go": "GO",
55 56
         "join": "JOIN",
56 57
         "privacy": "Privacy",
@@ -526,5 +527,10 @@
526 527
         "serverURL": "Server URL",
527 528
         "startWithAudioMuted": "Start with audio muted",
528 529
         "startWithVideoMuted": "Start with video muted"
530
+    },
531
+    "calendarSync": {
532
+        "later": "Later",
533
+        "next": "Upcoming",
534
+        "now": "Now"
529 535
     }
530 536
 }

+ 5
- 0
package-lock.json Bestand weergeven

@@ -9845,6 +9845,11 @@
9845 9845
       "resolved": "https://registry.npmjs.org/react-native-background-timer/-/react-native-background-timer-2.0.0.tgz",
9846 9846
       "integrity": "sha512-vLNJIedXQZN4p3ChFsAgVHacnJqQMnLl+wBsnZuliRkmsjEHo8kQOA9fnLih/OoiDi1O3eHQvXC5L8f+RYiKgw=="
9847 9847
     },
9848
+    "react-native-calendar-events": {
9849
+      "version": "1.4.3",
9850
+      "resolved": "https://registry.npmjs.org/react-native-calendar-events/-/react-native-calendar-events-1.4.3.tgz",
9851
+      "integrity": "sha1-KYBOi0TWlG5pq1ogkC2USe0xXEc="
9852
+    },
9848 9853
     "react-native-callstats": {
9849 9854
       "version": "3.27.0",
9850 9855
       "resolved": "https://registry.npmjs.org/react-native-callstats/-/react-native-callstats-3.27.0.tgz",

+ 1
- 0
package.json Bestand weergeven

@@ -55,6 +55,7 @@
55 55
     "react-i18next": "4.8.0",
56 56
     "react-native": "0.51.0",
57 57
     "react-native-background-timer": "2.0.0",
58
+    "react-native-calendar-events": "1.4.3",
58 59
     "react-native-callstats": "3.27.0",
59 60
     "react-native-fetch-blob": "0.10.8",
60 61
     "react-native-img-cache": "1.5.2",

+ 49
- 0
react/features/base/styles/components/styles/PlatformElements.native.js Bestand weergeven

@@ -0,0 +1,49 @@
1
+import { ColorPalette } from './ColorPalette';
2
+import { BoxModel } from './BoxModel';
3
+
4
+import {
5
+    createStyleSheet
6
+} from '../../functions';
7
+
8
+export const PlatformElements = createStyleSheet({
9
+
10
+    /**
11
+     * Platform specific header button (e.g. back, menu...etc).
12
+     */
13
+    headerButton: {
14
+        alignSelf: 'center',
15
+        color: ColorPalette.white,
16
+        fontSize: 26,
17
+        paddingRight: 22
18
+    },
19
+
20
+    /**
21
+     * Generic style for a label placed in the header.
22
+     */
23
+    headerText: {
24
+        color: ColorPalette.white,
25
+        fontSize: 20
26
+    },
27
+
28
+    /**
29
+     * An empty padded view to place components.
30
+     */
31
+    paddedView: {
32
+        padding: BoxModel.padding
33
+    },
34
+
35
+    /**
36
+     * The topmost level element of a page.
37
+     */
38
+    page: {
39
+        alignItems: 'stretch',
40
+        bottom: 0,
41
+        flex: 1,
42
+        flexDirection: 'column',
43
+        left: 0,
44
+        overflow: 'hidden',
45
+        position: 'absolute',
46
+        right: 0,
47
+        top: 0
48
+    }
49
+});

+ 1
- 0
react/features/base/styles/components/styles/index.js Bestand weergeven

@@ -1,2 +1,3 @@
1 1
 export * from './BoxModel';
2 2
 export * from './ColorPalette';
3
+export * from './PlatformElements';

+ 71
- 0
react/features/base/util/dateUtil.js Bestand weergeven

@@ -0,0 +1,71 @@
1
+// @flow
2
+
3
+import moment from 'moment';
4
+
5
+import { i18next } from '../i18n';
6
+
7
+// MomentJS uses static language bundle loading, so in order to support dynamic
8
+// language selection in the app we need to load all bundles that we support in
9
+// the app.
10
+// FIXME: If we decide to support MomentJS in other features as well we may need
11
+// to move this import and the lenient matcher to the i18n feature.
12
+require('moment/locale/bg');
13
+require('moment/locale/de');
14
+require('moment/locale/eo');
15
+require('moment/locale/es');
16
+require('moment/locale/fr');
17
+require('moment/locale/hy-am');
18
+require('moment/locale/it');
19
+require('moment/locale/nb');
20
+
21
+// OC is not available. Please submit OC translation to the MomentJS project.
22
+
23
+require('moment/locale/pl');
24
+require('moment/locale/pt');
25
+require('moment/locale/pt-br');
26
+require('moment/locale/ru');
27
+require('moment/locale/sk');
28
+require('moment/locale/sl');
29
+require('moment/locale/sv');
30
+require('moment/locale/tr');
31
+require('moment/locale/zh-cn');
32
+
33
+/**
34
+ * Returns a localized date formatter initialized with a specific {@code Date}
35
+ * or time stamp ({@code number}).
36
+ *
37
+ * @private
38
+ * @param {Date | number} dateOrTimeStamp - The date or unix timestamp (ms)
39
+ * to format.
40
+ * @returns {Object}
41
+ */
42
+export function getLocalizedDateFormatter(dateOrTimeStamp: Date | number) {
43
+    return moment(dateOrTimeStamp).locale(_getSupportedLocale());
44
+}
45
+
46
+/**
47
+ * A lenient locale matcher to match language and dialect if possible.
48
+ *
49
+ * @private
50
+ * @returns {string}
51
+ */
52
+function _getSupportedLocale() {
53
+    const i18nLocale = i18next.language;
54
+    let supportedLocale;
55
+
56
+    if (i18nLocale) {
57
+        const localeRegexp = new RegExp('^([a-z]{2,2})(-)*([a-z]{2,2})*$');
58
+        const localeResult = localeRegexp.exec(i18nLocale.toLowerCase());
59
+
60
+        if (localeResult) {
61
+            const currentLocaleRegexp
62
+                = new RegExp(
63
+                    `^${localeResult[1]}(-)*${`(${localeResult[3]})*` || ''}`);
64
+
65
+            supportedLocale
66
+                = moment.locales().find(lang => currentLocaleRegexp.exec(lang));
67
+        }
68
+    }
69
+
70
+    return supportedLocale || 'en';
71
+}

+ 1
- 0
react/features/base/util/index.js Bestand weergeven

@@ -1,3 +1,4 @@
1
+export * from './dateUtil';
1 2
 export * from './helpers';
2 3
 export * from './loadScript';
3 4
 export * from './randomUtil';

+ 6
- 0
react/features/calendar-sync/actionTypes.js Bestand weergeven

@@ -0,0 +1,6 @@
1
+// @flow
2
+
3
+/**
4
+ * Action to update the current calendar entry list in the store.
5
+ */
6
+export const NEW_CALENDAR_ENTRY_LIST = Symbol('NEW_CALENDAR_ENTRY_LIST');

+ 18
- 0
react/features/calendar-sync/actions.js Bestand weergeven

@@ -0,0 +1,18 @@
1
+// @flow
2
+import { NEW_CALENDAR_ENTRY_LIST } from './actionTypes';
3
+
4
+/**
5
+ * Sends an action to update the current calendar list in redux.
6
+ *
7
+ * @param {Array<Object>} events - The new list.
8
+ * @returns {{
9
+ *  type: NEW_CALENDAR_ENTRY_LIST,
10
+ *  events: Array<Object>
11
+ * }}
12
+ */
13
+export function updateCalendarEntryList(events: Array<Object>) {
14
+    return {
15
+        type: NEW_CALENDAR_ENTRY_LIST,
16
+        events
17
+    };
18
+}

+ 290
- 0
react/features/calendar-sync/components/MeetingList.native.js Bestand weergeven

@@ -0,0 +1,290 @@
1
+// @flow
2
+import React, { Component } from 'react';
3
+import {
4
+    SafeAreaView,
5
+    SectionList,
6
+    Text,
7
+    TouchableHighlight,
8
+    View
9
+} from 'react-native';
10
+import { connect } from 'react-redux';
11
+
12
+import { appNavigate } from '../../app';
13
+import { translate } from '../../base/i18n';
14
+import { getLocalizedDateFormatter } from '../../base/util';
15
+
16
+import styles, { UNDERLAY_COLOR } from './styles';
17
+
18
+type Props = {
19
+
20
+    /**
21
+     * Indicates if the list is disabled or not.
22
+     */
23
+    disabled: boolean,
24
+
25
+    /**
26
+     * The Redux dispatch function.
27
+     */
28
+    dispatch: Function,
29
+
30
+    /**
31
+     * The calendar event list.
32
+     */
33
+    _eventList: Array<Object>,
34
+
35
+    /**
36
+     * The translate function.
37
+     */
38
+    t: Function
39
+};
40
+
41
+/**
42
+ * Component to display a list of events from the (mobile) user's calendar.
43
+ */
44
+class MeetingList extends Component<Props> {
45
+
46
+    /**
47
+     * Constructor of the MeetingList component.
48
+     *
49
+     * @inheritdoc
50
+     */
51
+    constructor(props) {
52
+        super(props);
53
+
54
+        this._createSection = this._createSection.bind(this);
55
+        this._getItemKey = this._getItemKey.bind(this);
56
+        this._onJoin = this._onJoin.bind(this);
57
+        this._onSelect = this._onSelect.bind(this);
58
+        this._renderItem = this._renderItem.bind(this);
59
+        this._renderSection = this._renderSection.bind(this);
60
+        this._toDisplayableList = this._toDisplayableList.bind(this);
61
+        this._toDateString = this._toDateString.bind(this);
62
+    }
63
+
64
+    /**
65
+     * Implements the React Components's render method.
66
+     *
67
+     * @inheritdoc
68
+     */
69
+    render() {
70
+        const { disabled } = this.props;
71
+
72
+        return (
73
+            <SafeAreaView
74
+                style = { [
75
+                    styles.container,
76
+                    disabled ? styles.containerDisabled : null
77
+                ] } >
78
+                <SectionList
79
+                    keyExtractor = { this._getItemKey }
80
+                    renderItem = { this._renderItem }
81
+                    renderSectionHeader = { this._renderSection }
82
+                    sections = { this._toDisplayableList() }
83
+                    style = { styles.list } />
84
+            </SafeAreaView>
85
+        );
86
+    }
87
+
88
+    _createSection: string => Object;
89
+
90
+    /**
91
+     * Creates a section object of a list of events.
92
+     *
93
+     * @private
94
+     * @param {string} i18Title - The i18 title of the section.
95
+     * @returns {Object}
96
+     */
97
+    _createSection(i18Title) {
98
+        return {
99
+            data: [],
100
+            key: `key-${i18Title}`,
101
+            title: this.props.t(i18Title)
102
+        };
103
+    }
104
+
105
+    _getItemKey: (Object, number) => string;
106
+
107
+    /**
108
+     * Generates a unique id to every item.
109
+     *
110
+     * @private
111
+     * @param {Object} item - The item.
112
+     * @param {number} index - The item index.
113
+     * @returns {string}
114
+     */
115
+    _getItemKey(item, index) {
116
+        return `${index}-${item.id}-${item.startDate}`;
117
+    }
118
+
119
+    _onJoin: string => void;
120
+
121
+    /**
122
+     * Joins the selected URL.
123
+     *
124
+     * @param {string} url - The URL to join to.
125
+     * @returns {void}
126
+     */
127
+    _onJoin(url) {
128
+        const { disabled, dispatch } = this.props;
129
+
130
+        !disabled && url && dispatch(appNavigate(url));
131
+    }
132
+
133
+    _onSelect: string => Function;
134
+
135
+    /**
136
+     * Creates a function that when invoked, joins the given URL.
137
+     *
138
+     * @private
139
+     * @param {string} url - The URL to join to.
140
+     * @returns {Function}
141
+     */
142
+    _onSelect(url) {
143
+        return this._onJoin.bind(this, url);
144
+    }
145
+
146
+    _renderItem: Object => Object;
147
+
148
+    /**
149
+     * Renders a single item in the list.
150
+     *
151
+     * @private
152
+     * @param {Object} listItem - The item to render.
153
+     * @returns {Component}
154
+     */
155
+    _renderItem(listItem) {
156
+        const { item } = listItem;
157
+
158
+        return (
159
+            <TouchableHighlight
160
+                onPress = { this._onSelect(item.url) }
161
+                underlayColor = { UNDERLAY_COLOR }>
162
+                <View style = { styles.listItem }>
163
+                    <View style = { styles.avatarContainer } >
164
+                        <View style = { styles.avatar } >
165
+                            <Text style = { styles.avatarContent }>
166
+                                { item.title.substr(0, 1).toUpperCase() }
167
+                            </Text>
168
+                        </View>
169
+                    </View>
170
+                    <View style = { styles.listItemDetails }>
171
+                        <Text
172
+                            numberOfLines = { 1 }
173
+                            style = { [
174
+                                styles.listItemText,
175
+                                styles.listItemTitle
176
+                            ] }>
177
+                            { item.title }
178
+                        </Text>
179
+                        <Text
180
+                            numberOfLines = { 1 }
181
+                            style = { styles.listItemText }>
182
+                            { item.url }
183
+                        </Text>
184
+                        <Text
185
+                            numberOfLines = { 1 }
186
+                            style = { styles.listItemText }>
187
+                            { this._toDateString(item) }
188
+                        </Text>
189
+                    </View>
190
+                </View>
191
+            </TouchableHighlight>
192
+        );
193
+    }
194
+
195
+    _renderSection: Object => Object;
196
+
197
+    /**
198
+     * Renders a section title.
199
+     *
200
+     * @private
201
+     * @param {Object} section - The section being rendered.
202
+     * @returns {Component}
203
+     */
204
+    _renderSection(section) {
205
+        return (
206
+            <View style = { styles.listSection }>
207
+                <Text style = { styles.listSectionText }>
208
+                    { section.section.title }
209
+                </Text>
210
+            </View>
211
+        );
212
+    }
213
+
214
+    _toDisplayableList: () => Array<Object>
215
+
216
+    /**
217
+     * Transforms the event list to a displayable list
218
+     * with sections.
219
+     *
220
+     * @private
221
+     * @returns {Array<Object>}
222
+     */
223
+    _toDisplayableList() {
224
+        const { _eventList } = this.props;
225
+        const now = Date.now();
226
+        const nowSection = this._createSection('calendarSync.now');
227
+        const nextSection = this._createSection('calendarSync.next');
228
+        const laterSection = this._createSection('calendarSync.later');
229
+
230
+        if (_eventList && _eventList.length) {
231
+            for (const event of _eventList) {
232
+                if (event.startDate < now && event.endDate > now) {
233
+                    nowSection.data.push(event);
234
+                } else if (event.startDate > now) {
235
+                    if (nextSection.data.length
236
+                    && nextSection.data[0].startDate !== event.startDate) {
237
+                        laterSection.data.push(event);
238
+                    } else {
239
+                        nextSection.data.push(event);
240
+                    }
241
+                }
242
+            }
243
+        }
244
+
245
+        const sectionList = [];
246
+
247
+        for (const section of [
248
+            nowSection,
249
+            nextSection,
250
+            laterSection
251
+        ]) {
252
+            if (section.data.length) {
253
+                sectionList.push(section);
254
+            }
255
+        }
256
+
257
+        return sectionList;
258
+    }
259
+
260
+    _toDateString: Object => string;
261
+
262
+    /**
263
+     * Generates a date (interval) string for a given event.
264
+     *
265
+     * @private
266
+     * @param {Object} event - The event.
267
+     * @returns {string}
268
+     */
269
+    _toDateString(event) {
270
+        /* eslint-disable max-len */
271
+        return `${getLocalizedDateFormatter(event.startDate).format('lll')} - ${getLocalizedDateFormatter(event.endDate).format('LT')}`;
272
+        /* eslint-enable max-len */
273
+    }
274
+}
275
+
276
+/**
277
+ * Maps redux state to component props.
278
+ *
279
+ * @param {Object} state - The redux state.
280
+ * @returns {{
281
+ *      _eventList: Array
282
+ * }}
283
+ */
284
+export function _mapStateToProps(state: Object) {
285
+    return {
286
+        _eventList: state['features/calendar-sync'].events
287
+    };
288
+}
289
+
290
+export default translate(connect(_mapStateToProps)(MeetingList));

+ 1
- 0
react/features/calendar-sync/components/index.js Bestand weergeven

@@ -0,0 +1 @@
1
+export { default as MeetingList } from './MeetingList';

+ 109
- 0
react/features/calendar-sync/components/styles.js Bestand weergeven

@@ -0,0 +1,109 @@
1
+import { createStyleSheet } from '../../base/styles';
2
+
3
+const AVATAR_OPACITY = 0.4;
4
+
5
+const AVATAR_SIZE = 65;
6
+
7
+const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
8
+
9
+export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
10
+
11
+/**
12
+ * The styles of the React {@code Component}s of the feature recent-list i.e.
13
+ * {@code RecentList}.
14
+ */
15
+export default createStyleSheet({
16
+
17
+    /**
18
+     * The style of the actual avatar.
19
+     * Recent-list copy!
20
+     */
21
+    avatar: {
22
+        alignItems: 'center',
23
+        backgroundColor: `rgba(23, 160, 219, ${AVATAR_OPACITY})`,
24
+        borderRadius: AVATAR_SIZE,
25
+        height: AVATAR_SIZE,
26
+        justifyContent: 'center',
27
+        width: AVATAR_SIZE
28
+    },
29
+
30
+    /**
31
+     * The style of the avatar container that makes the avatar rounded.
32
+     * Recent-list copy!
33
+     */
34
+    avatarContainer: {
35
+        alignItems: 'center',
36
+        flexDirection: 'row',
37
+        justifyContent: 'space-around',
38
+        padding: 5
39
+    },
40
+
41
+    /**
42
+     * Simple {@code Text} content of the avatar (the actual initials).
43
+     * Recent-list copy!
44
+     */
45
+    avatarContent: {
46
+        backgroundColor: 'rgba(0, 0, 0, 0)',
47
+        color: OVERLAY_FONT_COLOR,
48
+        fontSize: 32,
49
+        fontWeight: '100',
50
+        textAlign: 'center'
51
+    },
52
+
53
+    /**
54
+     * The top level container style of the list.
55
+     */
56
+    container: {
57
+        flex: 1
58
+    },
59
+
60
+    /**
61
+     * Shows the container disabled.
62
+     */
63
+    containerDisabled: {
64
+        opacity: 0.2
65
+    },
66
+
67
+    list: {
68
+        flex: 1,
69
+        flexDirection: 'column'
70
+    },
71
+
72
+    listItem: {
73
+        alignItems: 'center',
74
+        flex: 1,
75
+        flexDirection: 'row',
76
+        paddingVertical: 5
77
+    },
78
+
79
+    listItemDetails: {
80
+        flex: 1,
81
+        flexDirection: 'column',
82
+        overflow: 'hidden',
83
+        paddingHorizontal: 5
84
+    },
85
+
86
+    listItemText: {
87
+        color: OVERLAY_FONT_COLOR,
88
+        fontSize: 16
89
+    },
90
+
91
+    listItemTitle: {
92
+        fontWeight: 'bold',
93
+        fontSize: 18
94
+    },
95
+
96
+    listSection: {
97
+        alignItems: 'center',
98
+        backgroundColor: 'rgba(255, 255, 255, 0.2)',
99
+        flex: 1,
100
+        flexDirection: 'row',
101
+        padding: 5
102
+    },
103
+
104
+    listSectionText: {
105
+        color: OVERLAY_FONT_COLOR,
106
+        fontSize: 14,
107
+        fontWeight: 'normal'
108
+    }
109
+});

+ 4
- 0
react/features/calendar-sync/index.js Bestand weergeven

@@ -0,0 +1,4 @@
1
+export * from './components';
2
+
3
+import './middleware';
4
+import './reducer';

+ 164
- 0
react/features/calendar-sync/middleware.js Bestand weergeven

@@ -0,0 +1,164 @@
1
+// @flow
2
+import Logger from 'jitsi-meet-logger';
3
+import RNCalendarEvents from 'react-native-calendar-events';
4
+
5
+import { MiddlewareRegistry } from '../base/redux';
6
+
7
+import { APP_WILL_MOUNT } from '../app';
8
+
9
+import { updateCalendarEntryList } from './actions';
10
+
11
+const FETCH_END_DAYS = 10;
12
+const FETCH_START_DAYS = -1;
13
+const MAX_LIST_LENGTH = 10;
14
+const logger = Logger.getLogger(__filename);
15
+
16
+// this is to be dynamic later.
17
+const domainList = [
18
+    'meet.jit.si',
19
+    'beta.meet.jit.si'
20
+];
21
+
22
+MiddlewareRegistry.register(store => next => action => {
23
+    const result = next(action);
24
+
25
+    switch (action.type) {
26
+    case APP_WILL_MOUNT:
27
+        _fetchCalendarEntries(store);
28
+    }
29
+
30
+    return result;
31
+});
32
+
33
+/**
34
+ * Ensures calendar access if possible and resolves the promise if it's granted.
35
+ *
36
+ * @private
37
+ * @returns {Promise}
38
+ */
39
+function _ensureCalendarAccess() {
40
+    return new Promise((resolve, reject) => {
41
+        RNCalendarEvents.authorizationStatus()
42
+            .then(status => {
43
+                if (status === 'authorized') {
44
+                    resolve();
45
+                } else if (status === 'undetermined') {
46
+                    RNCalendarEvents.authorizeEventStore()
47
+                        .then(result => {
48
+                            if (result === 'authorized') {
49
+                                resolve();
50
+                            } else {
51
+                                reject(result);
52
+                            }
53
+                        })
54
+                        .catch(error => {
55
+                            reject(error);
56
+                        });
57
+                } else {
58
+                    reject(status);
59
+                }
60
+            })
61
+            .catch(error => {
62
+                reject(error);
63
+            });
64
+    });
65
+}
66
+
67
+/**
68
+ * Reads the user's calendar and updates the stored entries if need be.
69
+ *
70
+ * @private
71
+ * @param {Object} store - The redux store.
72
+ * @returns {void}
73
+ */
74
+function _fetchCalendarEntries(store) {
75
+    _ensureCalendarAccess()
76
+    .then(() => {
77
+        const startDate = new Date();
78
+        const endDate = new Date();
79
+
80
+        startDate.setDate(startDate.getDate() + FETCH_START_DAYS);
81
+        endDate.setDate(endDate.getDate() + FETCH_END_DAYS);
82
+
83
+        RNCalendarEvents.fetchAllEvents(
84
+            startDate.getTime(),
85
+            endDate.getTime(),
86
+            []
87
+        )
88
+        .then(events => {
89
+            const eventList = [];
90
+
91
+            if (events && events.length) {
92
+                for (const event of events) {
93
+                    const jitsiURL = _getURLFromEvent(event);
94
+                    const now = Date.now();
95
+
96
+                    if (jitsiURL) {
97
+                        const eventStartDate = Date.parse(event.startDate);
98
+                        const eventEndDate = Date.parse(event.endDate);
99
+
100
+                        if (isNaN(eventStartDate) || isNaN(eventEndDate)) {
101
+                            logger.warn(
102
+                                'Skipping calendar event due to invalid dates',
103
+                                event.title,
104
+                                event.startDate,
105
+                                event.endDate
106
+                            );
107
+                        } else if (eventEndDate > now) {
108
+                            eventList.push({
109
+                                endDate: eventEndDate,
110
+                                id: event.id,
111
+                                startDate: eventStartDate,
112
+                                title: event.title,
113
+                                url: jitsiURL
114
+                            });
115
+                        }
116
+                    }
117
+                }
118
+            }
119
+
120
+            store.dispatch(updateCalendarEntryList(eventList.sort((a, b) =>
121
+                a.startDate - b.startDate
122
+            ).slice(0, MAX_LIST_LENGTH)));
123
+        })
124
+        .catch(error => {
125
+            logger.error('Error fetching calendar.', error);
126
+        });
127
+    })
128
+    .catch(reason => {
129
+        logger.error('Error accessing calendar.', reason);
130
+    });
131
+}
132
+
133
+/**
134
+ * Retreives a jitsi URL from an event if present.
135
+ *
136
+ * @private
137
+ * @param {Object} event - The event to parse.
138
+ * @returns {string}
139
+ *
140
+ */
141
+function _getURLFromEvent(event) {
142
+    const urlRegExp
143
+        = new RegExp(`http(s)?://(${domainList.join('|')})/[^\\s<>$]+`, 'gi');
144
+    const fieldsToSearch = [
145
+        event.title,
146
+        event.url,
147
+        event.location,
148
+        event.notes,
149
+        event.description
150
+    ];
151
+    let matchArray;
152
+
153
+    for (const field of fieldsToSearch) {
154
+        if (typeof field === 'string') {
155
+            if (
156
+                (matchArray = urlRegExp.exec(field)) !== null
157
+            ) {
158
+                return matchArray[0];
159
+            }
160
+        }
161
+    }
162
+
163
+    return null;
164
+}

+ 28
- 0
react/features/calendar-sync/reducer.js Bestand weergeven

@@ -0,0 +1,28 @@
1
+// @flow
2
+
3
+import { ReducerRegistry } from '../base/redux';
4
+
5
+import { NEW_CALENDAR_ENTRY_LIST } from './actionTypes';
6
+
7
+/**
8
+ * ZB: this is an object, as further data is to come here, like:
9
+ * - known domain list
10
+ */
11
+const DEFAULT_STATE = {
12
+    events: []
13
+};
14
+const STORE_NAME = 'features/calendar-sync';
15
+
16
+ReducerRegistry.register(
17
+    STORE_NAME,
18
+    (state = DEFAULT_STATE, action) => {
19
+        switch (action.type) {
20
+        case NEW_CALENDAR_ENTRY_LIST:
21
+            return {
22
+                events: action.events
23
+            };
24
+
25
+        default:
26
+            return state;
27
+        }
28
+    });

+ 9
- 3
react/features/recent-list/components/RecentList.native.js Bestand weergeven

@@ -1,5 +1,11 @@
1 1
 import React from 'react';
2
-import { ListView, Text, TouchableHighlight, View } from 'react-native';
2
+import {
3
+    ListView,
4
+    SafeAreaView,
5
+    Text,
6
+    TouchableHighlight,
7
+    View
8
+} from 'react-native';
3 9
 import { connect } from 'react-redux';
4 10
 
5 11
 import { Icon } from '../../base/font-icons';
@@ -57,7 +63,7 @@ class RecentList extends AbstractRecentList {
57 63
             = this.dataSource.cloneWithRows(getRecentRooms(_recentList));
58 64
 
59 65
         return (
60
-            <View
66
+            <SafeAreaView
61 67
                 style = { [
62 68
                     styles.container,
63 69
                     enabled ? null : styles.containerDisabled
@@ -66,7 +72,7 @@ class RecentList extends AbstractRecentList {
66 72
                     dataSource = { listViewDataSource }
67 73
                     enableEmptySections = { true }
68 74
                     renderRow = { this._renderRow } />
69
-            </View>
75
+            </SafeAreaView>
70 76
         );
71 77
     }
72 78
 

+ 48
- 0
react/features/welcome/components/AbstractPagedList.js Bestand weergeven

@@ -0,0 +1,48 @@
1
+// @flow
2
+
3
+import { Component } from 'react';
4
+
5
+/**
6
+ * The page to be displayed on render.
7
+ */
8
+export const DEFAULT_PAGE = 0;
9
+
10
+type Props = {
11
+
12
+    /**
13
+     * Indicates if the list is disabled or not.
14
+     */
15
+    disabled: boolean,
16
+
17
+    /**
18
+     * The i18n translate function
19
+     */
20
+    t: Function
21
+}
22
+
23
+type State = {
24
+
25
+    /**
26
+     * The currently selected page.
27
+     */
28
+    pageIndex: number
29
+}
30
+
31
+/**
32
+ * Abstract class for the platform specific paged lists.
33
+ */
34
+export default class AbstractPagedList extends Component<Props, State> {
35
+    /**
36
+     * Constructor of the component.
37
+     *
38
+     * @inheritdoc
39
+     */
40
+    constructor(props: Props) {
41
+        super(props);
42
+
43
+        this.state = {
44
+            pageIndex: DEFAULT_PAGE
45
+        };
46
+    }
47
+
48
+}

+ 97
- 0
react/features/welcome/components/PagedList.android.js Bestand weergeven

@@ -0,0 +1,97 @@
1
+// @flow
2
+import React from 'react';
3
+import { View, ViewPagerAndroid } from 'react-native';
4
+
5
+import { MeetingList } from '../../calendar-sync';
6
+import { RecentList } from '../../recent-list';
7
+
8
+import AbstractPagedList, { DEFAULT_PAGE } from './AbstractPagedList';
9
+import styles from './styles';
10
+
11
+/**
12
+ * A platform specific component to render a paged or tabbed list/view.
13
+ *
14
+ * @extends PagedList
15
+ */
16
+export default class PagedList extends AbstractPagedList {
17
+
18
+    /**
19
+     * Constructor of the PagedList Component.
20
+     *
21
+     * @inheritdoc
22
+     */
23
+    constructor() {
24
+        super();
25
+        this._getIndicatorStyle = this._getIndicatorStyle.bind(this);
26
+        this._onPageSelected = this._onPageSelected.bind(this);
27
+    }
28
+
29
+    /**
30
+     * Renders the paged list.
31
+     *
32
+     * @inheritdoc
33
+     */
34
+    render() {
35
+        const { disabled } = this.props;
36
+
37
+        return (
38
+            <View style = { styles.pagedListContainer }>
39
+                <ViewPagerAndroid
40
+                    initialPage = { DEFAULT_PAGE }
41
+                    keyboardDismissMode = 'on-drag'
42
+                    onPageSelected = { this._onPageSelected }
43
+                    peekEnabled = { true }
44
+                    style = { styles.pagedList }>
45
+                    <View key = { 0 }>
46
+                        <RecentList disabled = { disabled } />
47
+                    </View>
48
+                    <View key = { 1 }>
49
+                        <MeetingList disabled = { disabled } />
50
+                    </View>
51
+                </ViewPagerAndroid>
52
+                <View style = { styles.pageIndicatorContainer }>
53
+                    <View style = { this._getIndicatorStyle(0) } />
54
+                    <View style = { this._getIndicatorStyle(1) } />
55
+                </View>
56
+            </View>
57
+        );
58
+    }
59
+
60
+    _getIndicatorStyle: number => Array<Object>;
61
+
62
+    /**
63
+     * Constructs the style array of an idicator.
64
+     *
65
+     * @private
66
+     * @param {number} indicatorIndex - The index of the indicator.
67
+     * @returns {Array<Object>}
68
+     */
69
+    _getIndicatorStyle(indicatorIndex) {
70
+        const style = [
71
+            styles.pageIndicator
72
+        ];
73
+
74
+        if (this.state.pageIndex === indicatorIndex) {
75
+            style.push(styles.pageIndicatorActive);
76
+        }
77
+
78
+        return style;
79
+    }
80
+
81
+    _onPageSelected: Object => void;
82
+
83
+    /**
84
+     * Updates the index of the currently selected page.
85
+     *
86
+     * @private
87
+     * @param {Object} event - The native event of the callback.
88
+     * @returns {void}
89
+     */
90
+    _onPageSelected({ nativeEvent: { position } }) {
91
+        if (this.state.pageIndex !== position) {
92
+            this.setState({
93
+                pageIndex: position
94
+            });
95
+        }
96
+    }
97
+}

+ 82
- 0
react/features/welcome/components/PagedList.ios.js Bestand weergeven

@@ -0,0 +1,82 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { View, TabBarIOS } from 'react-native';
5
+
6
+import { translate } from '../../base/i18n';
7
+import { MeetingList } from '../../calendar-sync';
8
+import { RecentList } from '../../recent-list';
9
+
10
+import AbstractPagedList from './AbstractPagedList';
11
+import styles from './styles';
12
+
13
+const CALENDAR_ICON = require('../../../../images/calendar.png');
14
+
15
+/**
16
+ * A platform specific component to render a paged or tabbed list/view.
17
+ *
18
+ * @extends PagedList
19
+ */
20
+class PagedList extends AbstractPagedList {
21
+
22
+    /**
23
+     * Constructor of the PagedList Component.
24
+     *
25
+     * @inheritdoc
26
+     */
27
+    constructor(props) {
28
+        super(props);
29
+        this._onTabSelected = this._onTabSelected.bind(this);
30
+    }
31
+
32
+    /**
33
+     * Renders the paged list.
34
+     *
35
+     * @inheritdoc
36
+     */
37
+    render() {
38
+        const { pageIndex } = this.state;
39
+        const { disabled, t } = this.props;
40
+
41
+        return (
42
+            <View style = { styles.pagedListContainer }>
43
+                <TabBarIOS
44
+                    itemPositioning = 'fill'
45
+                    style = { styles.pagedList }>
46
+                    <TabBarIOS.Item
47
+                        onPress = { this._onTabSelected(0) }
48
+                        selected = { pageIndex === 0 }
49
+                        systemIcon = 'history' >
50
+                        <RecentList disabled = { disabled } />
51
+                    </TabBarIOS.Item>
52
+                    <TabBarIOS.Item
53
+                        icon = { CALENDAR_ICON }
54
+                        onPress = { this._onTabSelected(1) }
55
+                        selected = { pageIndex === 1 }
56
+                        title = { t('welcomepage.calendar') } >
57
+                        <MeetingList disabled = { disabled } />
58
+                    </TabBarIOS.Item>
59
+                </TabBarIOS>
60
+            </View>
61
+        );
62
+    }
63
+
64
+    _onTabSelected: number => Function;
65
+
66
+    /**
67
+     * Constructs a callback to update the selected tab.
68
+     *
69
+     * @private
70
+     * @param {number} tabIndex - The selected tab.
71
+     * @returns {Function}
72
+     */
73
+    _onTabSelected(tabIndex) {
74
+        return () => {
75
+            this.setState({
76
+                pageIndex: tabIndex
77
+            });
78
+        };
79
+    }
80
+}
81
+
82
+export default translate(PagedList);

+ 26
- 22
react/features/welcome/components/WelcomePage.native.js Bestand weergeven

@@ -16,17 +16,17 @@ import { Icon } from '../../base/font-icons';
16 16
 import { MEDIA_TYPE } from '../../base/media';
17 17
 import { updateProfile } from '../../base/profile';
18 18
 import { LoadingIndicator, Header, Text } from '../../base/react';
19
-import { ColorPalette } from '../../base/styles';
19
+import { ColorPalette, PlatformElements } from '../../base/styles';
20 20
 import {
21 21
     createDesiredLocalTracks,
22 22
     destroyLocalTracks
23 23
 } from '../../base/tracks';
24
-import { RecentList } from '../../recent-list';
25 24
 import { SettingsView } from '../../settings';
26 25
 
27 26
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
28 27
 import { setSideBarVisible } from '../actions';
29 28
 import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
29
+import PagedList from './PagedList';
30 30
 import styles, {
31 31
     PLACEHOLDER_TEXT_COLOR,
32 32
     SWITCH_THUMB_COLOR,
@@ -114,27 +114,31 @@ class WelcomePage extends AbstractWelcomePage {
114 114
                         </View>
115 115
                     </Header>
116 116
                     <SafeAreaView style = { styles.roomContainer } >
117
-                        <TextInput
118
-                            accessibilityLabel = { 'Input room name.' }
119
-                            autoCapitalize = 'none'
120
-                            autoComplete = { false }
121
-                            autoCorrect = { false }
122
-                            autoFocus = { false }
123
-                            onBlur = { this._onFieldFocusChange(false) }
124
-                            onChangeText = { this._onRoomChange }
125
-                            onFocus = { this._onFieldFocusChange(true) }
126
-                            onSubmitEditing = { this._onJoin }
127
-                            placeholder = { t('welcomepage.roomname') }
128
-                            placeholderTextColor = { PLACEHOLDER_TEXT_COLOR }
129
-                            returnKeyType = { 'go' }
130
-                            style = { styles.textInput }
131
-                            underlineColorAndroid = 'transparent'
132
-                            value = { this.state.room } />
133
-                        {
134
-                            this._renderHintBox()
135
-                        }
136
-                        <RecentList enabled = { !this.state._fieldFocused } />
117
+                        <View style = { PlatformElements.paddedView } >
118
+                            <TextInput
119
+                                accessibilityLabel = { 'Input room name.' }
120
+                                autoCapitalize = 'none'
121
+                                autoComplete = { false }
122
+                                autoCorrect = { false }
123
+                                autoFocus = { false }
124
+                                onBlur = { this._onFieldFocusChange(false) }
125
+                                onChangeText = { this._onRoomChange }
126
+                                onFocus = { this._onFieldFocusChange(true) }
127
+                                onSubmitEditing = { this._onJoin }
128
+                                placeholder = { t('welcomepage.roomname') }
129
+                                placeholderTextColor = {
130
+                                    PLACEHOLDER_TEXT_COLOR
131
+                                }
132
+                                returnKeyType = { 'go' }
133
+                                style = { styles.textInput }
134
+                                underlineColorAndroid = 'transparent'
135
+                                value = { this.state.room } />
136
+                            {
137
+                                this._renderHintBox()
138
+                            }
139
+                        </View>
137 140
                     </SafeAreaView>
141
+                    <PagedList disabled = { this.state._fieldFocused } />
138 142
                     <SettingsView />
139 143
                 </View>
140 144
                 <WelcomePageSideBar />

+ 31
- 4
react/features/welcome/components/styles.js Bestand weergeven

@@ -149,15 +149,42 @@ export default createStyleSheet({
149 149
         flexDirection: 'column'
150 150
     },
151 151
 
152
+    pageIndicator: {
153
+        backgroundColor: 'rgba(255, 255, 255, 0.2)',
154
+        height: 3,
155
+        marginHorizontal: 7,
156
+        width: 20
157
+    },
158
+
159
+    pageIndicatorActive: {
160
+        backgroundColor: 'rgba(255, 255, 255, 0.8)'
161
+    },
162
+
163
+    pageIndicatorContainer: {
164
+        alignItems: 'center',
165
+        flexDirection: 'row',
166
+        justifyContent: 'center',
167
+        padding: 12
168
+    },
169
+
170
+    /**
171
+     * Top level style of the paged list.
172
+     */
173
+    pagedList: {
174
+        flex: 1
175
+    },
176
+
177
+    pagedListContainer: {
178
+        flex: 1,
179
+        flexDirection: 'column'
180
+    },
181
+
152 182
     /**
153 183
      * Container for room name input box and 'join' button.
154 184
      */
155 185
     roomContainer: {
156 186
         alignSelf: 'stretch',
157
-        flex: 1,
158
-        flexDirection: 'column',
159
-        margin: BoxModel.margin,
160
-        marginTop: BoxModel.margin * 2
187
+        flexDirection: 'column'
161 188
     },
162 189
 
163 190
     /**

Laden…
Annuleren
Opslaan