Преглед изворни кода

Unify recent and meeting lists

master
zbettenbuk пре 7 година
родитељ
комит
b096622995

+ 5
- 0
lang/main.json Прегледај датотеку

@@ -533,5 +533,10 @@
533 533
         "next": "Upcoming",
534 534
         "nextMeeting": "next meeting",
535 535
         "now": "Now"
536
+    },
537
+    "recentList": {
538
+        "today": "Today",
539
+        "yesterday": "Yesterday",
540
+        "earlier": "Earlier"
536 541
     }
537 542
 }

+ 269
- 0
react/features/base/react/components/native/NavigateSectionList.js Прегледај датотеку

@@ -0,0 +1,269 @@
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
+
11
+import styles, { UNDERLAY_COLOR } from './styles';
12
+
13
+type Props = {
14
+
15
+    /**
16
+     * Indicates if the list is disabled or not.
17
+     */
18
+    disabled: boolean,
19
+
20
+    /**
21
+     * Function to be invoked when an item is pressed. The item's URL is passed.
22
+     */
23
+    onPress: Function,
24
+
25
+    /**
26
+     * Sections to be rendered in the following format:
27
+     *
28
+     * [
29
+     *   {
30
+     *     title: string,               <- section title
31
+     *     key: string,                 <- unique key for the section
32
+     *     data: [                      <- Array of items in the section
33
+     *       {
34
+     *         colorBase: string,       <- the color base of the avatar
35
+     *         title: string,           <- item title
36
+     *         url: string,             <- item url
37
+     *         lines: Array<string>     <- additional lines to be rendered
38
+     *       }
39
+     *     ]
40
+     *   }
41
+     * ]
42
+     */
43
+    sections: Array<Object>
44
+}
45
+
46
+/**
47
+ * Implements a general section list to display items that have a URL
48
+ * property and navigates to (probably) meetings, such as the recent list
49
+ * or the meeting list components.
50
+ */
51
+export default class NavigateSectionList extends Component<Props> {
52
+    /**
53
+     * Constructor of the NavigateSectionList component.
54
+     *
55
+     * @inheritdoc
56
+     */
57
+    constructor(props: Props) {
58
+        super(props);
59
+
60
+        this._getAvatarColor = this._getAvatarColor.bind(this);
61
+        this._getItemKey = this._getItemKey.bind(this);
62
+        this._onPress = this._onPress.bind(this);
63
+        this._renderItem = this._renderItem.bind(this);
64
+        this._renderItemLine = this._renderItemLine.bind(this);
65
+        this._renderItemLines = this._renderItemLines.bind(this);
66
+        this._renderSection = this._renderSection.bind(this);
67
+    }
68
+
69
+    /**
70
+     * Implements React's Component.render function.
71
+     *
72
+     * @inheritdoc
73
+     */
74
+    render() {
75
+        const { sections } = this.props;
76
+
77
+        return (
78
+            <SafeAreaView
79
+                style = { styles.container } >
80
+                <SectionList
81
+                    keyExtractor = { this._getItemKey }
82
+                    renderItem = { this._renderItem }
83
+                    renderSectionHeader = { this._renderSection }
84
+                    sections = { sections }
85
+                    style = { styles.list } />
86
+            </SafeAreaView>
87
+        );
88
+    }
89
+
90
+    /**
91
+     * Creates an empty section object.
92
+     *
93
+     * @private
94
+     * @param {string} title - The title of the section.
95
+     * @param {string} key - The key of the section. It must be unique.
96
+     * @returns {Object}
97
+     */
98
+    static createSection(title, key) {
99
+        return {
100
+            data: [],
101
+            key,
102
+            title
103
+        };
104
+    }
105
+
106
+    _getAvatarColor: string => Object
107
+
108
+    /**
109
+     * Returns a style (color) based on the string that determines the
110
+     * color of the avatar.
111
+     *
112
+     * @param {string} colorBase - The string that is the base of the color.
113
+     * @private
114
+     * @returns {Object}
115
+     */
116
+    _getAvatarColor(colorBase) {
117
+        if (!colorBase) {
118
+            return null;
119
+        }
120
+
121
+        let nameHash = 0;
122
+
123
+        for (let i = 0; i < colorBase.length; i++) {
124
+            nameHash += colorBase.codePointAt(i);
125
+        }
126
+
127
+        return styles[`avatarColor${(nameHash % 5) + 1}`];
128
+    }
129
+
130
+    _getItemKey: (Object, number) => string;
131
+
132
+    /**
133
+     * Generates a unique id to every item.
134
+     *
135
+     * @private
136
+     * @param {Object} item - The item.
137
+     * @param {number} index - The item index.
138
+     * @returns {string}
139
+     */
140
+    _getItemKey(item, index) {
141
+        return `${index}-${item.key}`;
142
+    }
143
+
144
+    _onPress: string => Function
145
+
146
+    /**
147
+     * Returns a function that is used in the onPress callback of the items.
148
+     *
149
+     * @private
150
+     * @param {string} url - The URL of the item to navigate to.
151
+     * @returns {Function}
152
+     */
153
+    _onPress(url) {
154
+        return () => {
155
+            const { disabled, onPress } = this.props;
156
+
157
+            !disabled && url && typeof onPress === 'function' && onPress(url);
158
+        };
159
+    }
160
+
161
+    _renderItem: Object => Object;
162
+
163
+    /**
164
+     * Renders a single item in the list.
165
+     *
166
+     * @private
167
+     * @param {Object} listItem - The item to render.
168
+     * @returns {Component}
169
+     */
170
+    _renderItem(listItem) {
171
+        const { item } = listItem;
172
+
173
+        return (
174
+            <TouchableHighlight
175
+                onPress = { this._onPress(item.url) }
176
+                underlayColor = { UNDERLAY_COLOR }>
177
+                <View style = { styles.listItem }>
178
+                    <View style = { styles.avatarContainer } >
179
+                        <View
180
+                            style = { [
181
+                                styles.avatar,
182
+                                this._getAvatarColor(item.colorBase)
183
+                            ] } >
184
+                            <Text style = { styles.avatarContent }>
185
+                                { item.title.substr(0, 1).toUpperCase() }
186
+                            </Text>
187
+                        </View>
188
+                    </View>
189
+                    <View style = { styles.listItemDetails }>
190
+                        <Text
191
+                            numberOfLines = { 1 }
192
+                            style = { [
193
+                                styles.listItemText,
194
+                                styles.listItemTitle
195
+                            ] }>
196
+                            { item.title }
197
+                        </Text>
198
+                        {
199
+                            this._renderItemLines(item.lines)
200
+                        }
201
+                    </View>
202
+                </View>
203
+            </TouchableHighlight>
204
+        );
205
+    }
206
+
207
+    _renderItemLine: (string, number) => React$Node;
208
+
209
+    /**
210
+     * Renders a single line from the additional lines.
211
+     *
212
+     * @private
213
+     * @param {string} line - The line text.
214
+     * @param {number} index - The index of the line.
215
+     * @returns {React$Node}
216
+     */
217
+    _renderItemLine(line, index) {
218
+        if (!line) {
219
+            return null;
220
+        }
221
+
222
+        return (
223
+            <Text
224
+                key = { index }
225
+                numberOfLines = { 1 }
226
+                style = { styles.listItemText }>
227
+                { line }
228
+            </Text>
229
+        );
230
+    }
231
+
232
+    _renderItemLines: (Array<string>) => Array<React$Node>;
233
+
234
+    /**
235
+     * Renders the additional item lines, if any.
236
+     *
237
+     * @private
238
+     * @param {Array<string>} lines - The lines to render.
239
+     * @returns {Array<React$Node>}
240
+     */
241
+    _renderItemLines(lines) {
242
+        if (lines && lines.length) {
243
+            return lines.map((line, index) =>
244
+                this._renderItemLine(line, index)
245
+            );
246
+        }
247
+
248
+        return null;
249
+    }
250
+
251
+    _renderSection: Object => Object
252
+
253
+    /**
254
+     * Renders a section title.
255
+     *
256
+     * @private
257
+     * @param {Object} section - The section being rendered.
258
+     * @returns {React$Node}
259
+     */
260
+    _renderSection(section) {
261
+        return (
262
+            <View style = { styles.listSection }>
263
+                <Text style = { styles.listSectionText }>
264
+                    { section.section.title }
265
+                </Text>
266
+            </View>
267
+        );
268
+    }
269
+}

+ 1
- 0
react/features/base/react/components/native/index.js Прегледај датотеку

@@ -1,5 +1,6 @@
1 1
 export { default as Container } from './Container';
2 2
 export { default as Header } from './Header';
3
+export { default as NavigateSectionList } from './NavigateSectionList';
3 4
 export { default as Link } from './Link';
4 5
 export { default as LoadingIndicator } from './LoadingIndicator';
5 6
 export { default as SideBar } from './SideBar';

+ 131
- 5
react/features/base/react/components/native/styles.js Прегледај датотеку

@@ -4,20 +4,20 @@ import {
4 4
     createStyleSheet
5 5
 } from '../../../styles';
6 6
 
7
+const AVATAR_OPACITY = 0.4;
8
+const AVATAR_SIZE = 65;
7 9
 const HEADER_COLOR = ColorPalette.blue;
8 10
 
9 11
 // Header height is from iOS guidelines. Also, this looks good.
10 12
 const HEADER_HEIGHT = 44;
13
+const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
11 14
 
12 15
 export const HEADER_PADDING = BoxModel.padding;
13 16
 export const STATUSBAR_COLOR = ColorPalette.blueHighlight;
14 17
 export const SIDEBAR_WIDTH = 250;
18
+export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
15 19
 
16
-/**
17
- * The styles of the generic React {@code Components} of the app.
18
- */
19
-export default createStyleSheet({
20
-
20
+const HEADER_STYLES = {
21 21
     /**
22 22
      * Platform specific header button (e.g. back, menu...etc).
23 23
      */
@@ -68,8 +68,124 @@ export default createStyleSheet({
68 68
         height: HEADER_HEIGHT,
69 69
         justifyContent: 'flex-start',
70 70
         padding: HEADER_PADDING
71
+    }
72
+};
73
+
74
+const SECTIONLIST_STYLES = {
75
+    /**
76
+     * The style of the actual avatar.
77
+     */
78
+    avatar: {
79
+        alignItems: 'center',
80
+        backgroundColor: `rgba(23, 160, 219, ${AVATAR_OPACITY})`,
81
+        borderRadius: AVATAR_SIZE,
82
+        height: AVATAR_SIZE,
83
+        justifyContent: 'center',
84
+        width: AVATAR_SIZE
85
+    },
86
+
87
+    /**
88
+     * List of styles of the avatar of a remote meeting (not the default
89
+     * server). The number of colors are limited because they should match
90
+     * nicely.
91
+     */
92
+    avatarColor1: {
93
+        backgroundColor: `rgba(232, 105, 156, ${AVATAR_OPACITY})`
94
+    },
95
+
96
+    avatarColor2: {
97
+        backgroundColor: `rgba(255, 198, 115, ${AVATAR_OPACITY})`
98
+    },
99
+
100
+    avatarColor3: {
101
+        backgroundColor: `rgba(128, 128, 255, ${AVATAR_OPACITY})`
102
+    },
103
+
104
+    avatarColor4: {
105
+        backgroundColor: `rgba(105, 232, 194, ${AVATAR_OPACITY})`
106
+    },
107
+
108
+    avatarColor5: {
109
+        backgroundColor: `rgba(234, 255, 128, ${AVATAR_OPACITY})`
110
+    },
111
+
112
+    /**
113
+     * The style of the avatar container that makes the avatar rounded.
114
+     */
115
+    avatarContainer: {
116
+        alignItems: 'center',
117
+        flexDirection: 'row',
118
+        justifyContent: 'space-around',
119
+        padding: 5
120
+    },
121
+
122
+    /**
123
+     * Simple {@code Text} content of the avatar (the actual initials).
124
+     */
125
+    avatarContent: {
126
+        backgroundColor: 'rgba(0, 0, 0, 0)',
127
+        color: OVERLAY_FONT_COLOR,
128
+        fontSize: 32,
129
+        fontWeight: '100',
130
+        textAlign: 'center'
131
+    },
132
+
133
+    /**
134
+     * The top level container style of the list.
135
+     */
136
+    container: {
137
+        flex: 1
71 138
     },
72 139
 
140
+    list: {
141
+        flex: 1,
142
+        flexDirection: 'column'
143
+    },
144
+
145
+    listItem: {
146
+        alignItems: 'center',
147
+        flex: 1,
148
+        flexDirection: 'row',
149
+        paddingVertical: 5
150
+    },
151
+
152
+    listItemDetails: {
153
+        flex: 1,
154
+        flexDirection: 'column',
155
+        overflow: 'hidden',
156
+        paddingHorizontal: 5
157
+    },
158
+
159
+    listItemText: {
160
+        color: OVERLAY_FONT_COLOR,
161
+        fontSize: 14
162
+    },
163
+
164
+    listItemTitle: {
165
+        fontWeight: 'bold',
166
+        fontSize: 16
167
+    },
168
+
169
+    listSection: {
170
+        alignItems: 'center',
171
+        backgroundColor: 'rgba(255, 255, 255, 0.2)',
172
+        flex: 1,
173
+        flexDirection: 'row',
174
+        padding: 5
175
+    },
176
+
177
+    listSectionText: {
178
+        color: OVERLAY_FONT_COLOR,
179
+        fontSize: 14,
180
+        fontWeight: 'normal'
181
+    },
182
+
183
+    touchableView: {
184
+        flexDirection: 'row'
185
+    }
186
+};
187
+
188
+const SIDEBAR_STYLES = {
73 189
     /**
74 190
      * The topmost container of the side bar.
75 191
      */
@@ -105,4 +221,14 @@ export default createStyleSheet({
105 221
     sideMenuShadowTouchable: {
106 222
         flex: 1
107 223
     }
224
+};
225
+
226
+/**
227
+ * The styles of the React {@code Components} of the generic components
228
+ * in the app.
229
+ */
230
+export default createStyleSheet({
231
+    ...HEADER_STYLES,
232
+    ...SECTIONLIST_STYLES,
233
+    ...SIDEBAR_STYLES
108 234
 });

+ 13
- 0
react/features/base/util/dateUtil.js Прегледај датотеку

@@ -43,6 +43,19 @@ export function getLocalizedDateFormatter(dateOrTimeStamp: Date | number) {
43 43
     return moment(dateOrTimeStamp).locale(_getSupportedLocale());
44 44
 }
45 45
 
46
+/**
47
+ * Returns a localized duration formatter initialized with a
48
+ * specific duration ({@code number}).
49
+ *
50
+ * @private
51
+ * @param {number} duration - The duration (ms)
52
+ * to format.
53
+ * @returns {Object}
54
+ */
55
+export function getLocalizedDurationFormatter(duration: number) {
56
+    return moment.duration(duration).locale(_getSupportedLocale());
57
+}
58
+
46 59
 /**
47 60
  * A lenient locale matcher to match language and dialect if possible.
48 61
  *

+ 63
- 154
react/features/calendar-sync/components/MeetingList.native.js Прегледај датотеку

@@ -1,20 +1,12 @@
1 1
 // @flow
2 2
 import React, { Component } from 'react';
3
-import {
4
-    SafeAreaView,
5
-    SectionList,
6
-    Text,
7
-    TouchableHighlight,
8
-    View
9
-} from 'react-native';
10 3
 import { connect } from 'react-redux';
11 4
 
12 5
 import { appNavigate } from '../../app';
13 6
 import { translate } from '../../base/i18n';
7
+import { NavigateSectionList } from '../../base/react';
14 8
 import { getLocalizedDateFormatter } from '../../base/util';
15 9
 
16
-import styles, { UNDERLAY_COLOR } from './styles';
17
-
18 10
 type Props = {
19 11
 
20 12
     /**
@@ -43,6 +35,13 @@ type Props = {
43 35
  */
44 36
 class MeetingList extends Component<Props> {
45 37
 
38
+    /**
39
+     * Default values for the component's props.
40
+     */
41
+    static defaultProps = {
42
+        _eventList: []
43
+    };
44
+
46 45
     /**
47 46
      * Constructor of the MeetingList component.
48 47
      *
@@ -51,12 +50,8 @@ class MeetingList extends Component<Props> {
51 50
     constructor(props) {
52 51
         super(props);
53 52
 
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);
53
+        this._onPress = this._onPress.bind(this);
54
+        this._toDisplayableItem = this._toDisplayableItem.bind(this);
60 55
         this._toDisplayableList = this._toDisplayableList.bind(this);
61 56
         this._toDateString = this._toDateString.bind(this);
62 57
     }
@@ -70,145 +65,47 @@ class MeetingList extends Component<Props> {
70 65
         const { disabled } = this.props;
71 66
 
72 67
         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>
68
+            <NavigateSectionList
69
+                disabled = { disabled }
70
+                onPress = { this._onPress }
71
+                sections = { this._toDisplayableList() } />
85 72
         );
86 73
     }
87 74
 
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;
75
+    _onPress: string => Function
106 76
 
107 77
     /**
108
-     * Generates a unique id to every item.
78
+     * Handles the list's navigate action.
109 79
      *
110 80
      * @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.
81
+     * @param {string} url - The url string to navigate to.
125 82
      * @returns {void}
126 83
      */
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;
84
+    _onPress(url) {
85
+        const { dispatch } = this.props;
157 86
 
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
-        );
87
+        dispatch(appNavigate(url));
193 88
     }
194 89
 
195
-    _renderSection: Object => Object;
90
+    _toDisplayableItem: Object => Object
196 91
 
197 92
     /**
198
-     * Renders a section title.
93
+     * Creates a displayable object from an event.
199 94
      *
200 95
      * @private
201
-     * @param {Object} section - The section being rendered.
202
-     * @returns {Component}
96
+     * @param {Object} event - The calendar event.
97
+     * @returns {Object}
203 98
      */
204
-    _renderSection(section) {
205
-        return (
206
-            <View style = { styles.listSection }>
207
-                <Text style = { styles.listSectionText }>
208
-                    { section.section.title }
209
-                </Text>
210
-            </View>
211
-        );
99
+    _toDisplayableItem(event) {
100
+        return {
101
+            key: `${event.id}-${event.startDate}`,
102
+            lines: [
103
+                event.url,
104
+                this._toDateString(event)
105
+            ],
106
+            title: event.title,
107
+            url: event.url
108
+        };
212 109
     }
213 110
 
214 111
     _toDisplayableList: () => Array<Object>
@@ -221,23 +118,32 @@ class MeetingList extends Component<Props> {
221 118
      * @returns {Array<Object>}
222 119
      */
223 120
     _toDisplayableList() {
224
-        const { _eventList } = this.props;
121
+        const { _eventList, t } = this.props;
225 122
         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');
123
+        const nowSection = NavigateSectionList.createSection(
124
+            t('calendarSync.now'),
125
+            'now'
126
+        );
127
+        const nextSection = NavigateSectionList.createSection(
128
+            t('calendarSync.next'),
129
+            'next'
130
+        );
131
+        const laterSection = NavigateSectionList.createSection(
132
+            t('calendarSync.later'),
133
+            'later'
134
+        );
229 135
 
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
-                    }
136
+        for (const event of _eventList) {
137
+            const displayableEvent = this._toDisplayableItem(event);
138
+
139
+            if (event.startDate < now && event.endDate > now) {
140
+                nowSection.data.push(displayableEvent);
141
+            } else if (event.startDate > now) {
142
+                if (nextSection.data.length
143
+                && nextSection.data[0].startDate !== event.startDate) {
144
+                    laterSection.data.push(displayableEvent);
145
+                } else {
146
+                    nextSection.data.push(displayableEvent);
241 147
                 }
242 148
             }
243 149
         }
@@ -257,7 +163,7 @@ class MeetingList extends Component<Props> {
257 163
         return sectionList;
258 164
     }
259 165
 
260
-    _toDateString: Object => string;
166
+    _toDateString: Object => string
261 167
 
262 168
     /**
263 169
      * Generates a date (interval) string for a given event.
@@ -268,7 +174,10 @@ class MeetingList extends Component<Props> {
268 174
      */
269 175
     _toDateString(event) {
270 176
         /* eslint-disable max-len */
271
-        return `${getLocalizedDateFormatter(event.startDate).format('lll')} - ${getLocalizedDateFormatter(event.endDate).format('LT')}`;
177
+        const startDateTime = getLocalizedDateFormatter(event.startDate).format('lll');
178
+        const endTime = getLocalizedDateFormatter(event.endDate).format('LT');
179
+
180
+        return `${startDateTime} - ${endTime}`;
272 181
         /* eslint-enable max-len */
273 182
     }
274 183
 }

+ 14
- 99
react/features/calendar-sync/components/styles.js Прегледај датотеку

@@ -1,58 +1,18 @@
1 1
 import { createStyleSheet } from '../../base/styles';
2 2
 
3
-const AVATAR_OPACITY = 0.4;
4
-const AVATAR_SIZE = 65;
5 3
 const NOTIFICATION_SIZE = 55;
6
-const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
7
-
8
-export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
9 4
 
10 5
 /**
11
- * The styles of the React {@code Component}s of the feature recent-list i.e.
12
- * {@code RecentList}.
6
+ * The styles of the React {@code Component}s of the feature meeting-list i.e.
7
+ * {@code MeetingList}.
13 8
  */
14 9
 export default createStyleSheet({
15 10
 
16 11
     /**
17
-     * The style of the actual avatar.
18
-     * Recent-list copy!
19
-     */
20
-    avatar: {
21
-        alignItems: 'center',
22
-        backgroundColor: `rgba(23, 160, 219, ${AVATAR_OPACITY})`,
23
-        borderRadius: AVATAR_SIZE,
24
-        height: AVATAR_SIZE,
25
-        justifyContent: 'center',
26
-        width: AVATAR_SIZE
27
-    },
28
-
29
-    /**
30
-     * The style of the avatar container that makes the avatar rounded.
31
-     * Recent-list copy!
32
-     */
33
-    avatarContainer: {
34
-        alignItems: 'center',
35
-        flexDirection: 'row',
36
-        justifyContent: 'space-around',
37
-        padding: 5
38
-    },
39
-
40
-    /**
41
-     * Simple {@code Text} content of the avatar (the actual initials).
42
-     * Recent-list copy!
43
-     */
44
-    avatarContent: {
45
-        backgroundColor: 'rgba(0, 0, 0, 0)',
46
-        color: OVERLAY_FONT_COLOR,
47
-        fontSize: 32,
48
-        fontWeight: '100',
49
-        textAlign: 'center'
50
-    },
51
-
52
-    /**
53
-     * Style for the actual notification content.
12
+     * The top level container of the notification.
54 13
      */
55 14
     notificationContainer: {
15
+        alignSelf: 'flex-start',
56 16
         flexDirection: 'row',
57 17
         justifyContent: 'center',
58 18
         overflow: 'hidden',
@@ -130,6 +90,9 @@ export default createStyleSheet({
130 90
         fontSize: 25
131 91
     },
132 92
 
93
+    /**
94
+     * The container that contains the icon.
95
+     */
133 96
     notificationIconContainer: {
134 97
         alignItems: 'center',
135 98
         flexDirection: 'row',
@@ -137,11 +100,17 @@ export default createStyleSheet({
137 100
         justifyContent: 'center'
138 101
     },
139 102
 
103
+    /**
104
+     * A single line of text of the notification.
105
+     */
140 106
     notificationText: {
141 107
         color: 'white',
142 108
         fontSize: 13
143 109
     },
144 110
 
111
+    /**
112
+     * The container for all the lines if the norification.
113
+     */
145 114
     notificationTextContainer: {
146 115
         flexDirection: 'column',
147 116
         height: NOTIFICATION_SIZE,
@@ -149,62 +118,8 @@ export default createStyleSheet({
149 118
     },
150 119
 
151 120
     /**
152
-     * The top level container style of the list.
121
+     * The touchable component.
153 122
      */
154
-    container: {
155
-        flex: 1
156
-    },
157
-
158
-    /**
159
-     * Shows the container disabled.
160
-     */
161
-    containerDisabled: {
162
-        opacity: 0.2
163
-    },
164
-
165
-    list: {
166
-        flex: 1,
167
-        flexDirection: 'column'
168
-    },
169
-
170
-    listItem: {
171
-        alignItems: 'center',
172
-        flex: 1,
173
-        flexDirection: 'row',
174
-        paddingVertical: 5
175
-    },
176
-
177
-    listItemDetails: {
178
-        flex: 1,
179
-        flexDirection: 'column',
180
-        overflow: 'hidden',
181
-        paddingHorizontal: 5
182
-    },
183
-
184
-    listItemText: {
185
-        color: OVERLAY_FONT_COLOR,
186
-        fontSize: 16
187
-    },
188
-
189
-    listItemTitle: {
190
-        fontWeight: 'bold',
191
-        fontSize: 18
192
-    },
193
-
194
-    listSection: {
195
-        alignItems: 'center',
196
-        backgroundColor: 'rgba(255, 255, 255, 0.2)',
197
-        flex: 1,
198
-        flexDirection: 'row',
199
-        padding: 5
200
-    },
201
-
202
-    listSectionText: {
203
-        color: OVERLAY_FONT_COLOR,
204
-        fontSize: 14,
205
-        fontWeight: 'normal'
206
-    },
207
-
208 123
     touchableView: {
209 124
         flexDirection: 'row'
210 125
     }

+ 0
- 75
react/features/recent-list/components/AbstractRecentList.js Прегледај датотеку

@@ -1,75 +0,0 @@
1
-// @flow
2
-
3
-import { Component } from 'react';
4
-
5
-import { appNavigate } from '../../app';
6
-
7
-/**
8
- * The type of the React {@code Component} props of {@link AbstractRecentList}
9
- */
10
-type Props = {
11
-    _defaultURL: string,
12
-
13
-    _recentList: Array<Object>,
14
-
15
-    /**
16
-     * Indicates if the list is disabled or not.
17
-     */
18
-    disabled: boolean,
19
-
20
-    /**
21
-     * The redux store's {@code dispatch} function.
22
-     */
23
-    dispatch: Dispatch<*>
24
-};
25
-
26
-/**
27
- * Implements a React {@link Component} which represents the list of conferences
28
- * recently joined, similar to how a list of last dialed numbers list would do
29
- * on a mobile device.
30
- *
31
- * @extends Component
32
- */
33
-export default class AbstractRecentList extends Component<Props> {
34
-
35
-    /**
36
-     * Joins the selected room.
37
-     *
38
-     * @param {string} room - The selected room.
39
-     * @protected
40
-     * @returns {void}
41
-     */
42
-    _onJoin(room) {
43
-        const { dispatch, disabled } = this.props;
44
-
45
-        !disabled && room && dispatch(appNavigate(room));
46
-    }
47
-
48
-    /**
49
-     * Creates a bound onPress action for the list item.
50
-     *
51
-     * @param {string} room - The selected room.
52
-     * @protected
53
-     * @returns {Function}
54
-     */
55
-    _onSelect(room) {
56
-        return this._onJoin.bind(this, room);
57
-    }
58
-}
59
-
60
-/**
61
- * Maps (parts of) the redux state into {@code AbstractRecentList}'s React
62
- * {@code Component} props.
63
- *
64
- * @param {Object} state - The redux state.
65
- * @returns {{
66
- *     _defaultURL: string,
67
- *     _recentList: Array
68
- * }}
69
- */
70
-export function _mapStateToProps(state: Object) {
71
-    return {
72
-        _defaultURL: state['features/app'].app._getDefaultURL(),
73
-        _recentList: state['features/recent-list']
74
-    };
75
-}

+ 175
- 143
react/features/recent-list/components/RecentList.native.js Прегледај датотеку

@@ -1,208 +1,240 @@
1
-import React from 'react';
2
-import {
3
-    ListView,
4
-    SafeAreaView,
5
-    Text,
6
-    TouchableHighlight,
7
-    View
8
-} from 'react-native';
1
+// @flow
2
+import React, { Component } from 'react';
9 3
 import { connect } from 'react-redux';
10 4
 
11
-import { Icon } from '../../base/font-icons';
12
-
13
-import AbstractRecentList, { _mapStateToProps } from './AbstractRecentList';
14
-import { getRecentRooms } from '../functions';
15
-import styles, { UNDERLAY_COLOR } from './styles';
5
+import { appNavigate } from '../../app';
6
+import { translate } from '../../base/i18n';
7
+import { NavigateSectionList } from '../../base/react';
8
+import {
9
+    getLocalizedDateFormatter,
10
+    getLocalizedDurationFormatter,
11
+    parseURIString
12
+} from '../../base/util';
16 13
 
17 14
 /**
18
- * The native container rendering the list of the recently joined rooms.
19
- *
20
- * @extends AbstractRecentList
15
+ * The type of the React {@code Component} props of {@link RecentList}
21 16
  */
22
-class RecentList extends AbstractRecentList {
17
+type Props = {
18
+
19
+    /**
20
+     * Renders the list disabled.
21
+     */
22
+    disabled: boolean,
23
+
24
+    /**
25
+     * The redux store's {@code dispatch} function.
26
+     */
27
+    dispatch: Dispatch<*>,
28
+
29
+    /**
30
+     * The translate function.
31
+     */
32
+    t: Function,
33
+
23 34
     /**
24
-     * The datasource wrapper to be used for the display.
35
+     * The default server URL.
25 36
      */
26
-    dataSource = new ListView.DataSource({
27
-        rowHasChanged: (r1, r2) =>
28
-            r1.conference !== r2.conference
29
-                && r1.dateTimeStamp !== r2.dateTimeStamp
30
-    });
37
+    _defaultServerURL: string,
31 38
 
39
+    /**
40
+     * The recent list from the Redux store.
41
+     */
42
+    _recentList: Array<Object>
43
+};
44
+
45
+/**
46
+ * The native container rendering the list of the recently joined rooms.
47
+ *
48
+ */
49
+class RecentList extends Component<Props> {
32 50
     /**
33 51
      * Initializes a new {@code RecentList} instance.
34 52
      *
35 53
      * @inheritdoc
36 54
      */
37
-    constructor(props) {
55
+    constructor(props: Props) {
38 56
         super(props);
39 57
 
40
-        // Bind event handlers so they are only bound once per instance.
41
-        this._getAvatarStyle = this._getAvatarStyle.bind(this);
42
-        this._onSelect = this._onSelect.bind(this);
43
-        this._renderConfDuration = this._renderConfDuration.bind(this);
44
-        this._renderRow = this._renderRow.bind(this);
45
-        this._renderServerInfo = this._renderServerInfo.bind(this);
58
+        this._onPress = this._onPress.bind(this);
59
+        this._toDateString = this._toDateString.bind(this);
60
+        this._toDurationString = this._toDurationString.bind(this);
61
+        this._toDisplayableItem = this._toDisplayableItem.bind(this);
62
+        this._toDisplayableList = this._toDisplayableList.bind(this);
46 63
     }
47 64
 
48 65
     /**
49
-     * Implements React's {@link Component#render()}. Renders a list of recently
50
-     * joined rooms.
66
+     * Implements the React Components's render method.
51 67
      *
52 68
      * @inheritdoc
53
-     * @returns {ReactElement}
54 69
      */
55 70
     render() {
56
-        const { disabled, _recentList } = this.props;
57
-
58
-        if (!_recentList) {
59
-            return null;
60
-        }
61
-
62
-        const listViewDataSource
63
-            = this.dataSource.cloneWithRows(getRecentRooms(_recentList));
71
+        const { disabled } = this.props;
64 72
 
65 73
         return (
66
-            <SafeAreaView
67
-                style = { [
68
-                    styles.container,
69
-                    disabled ? styles.containerDisabled : null
70
-                ] }>
71
-                <ListView
72
-                    dataSource = { listViewDataSource }
73
-                    enableEmptySections = { true }
74
-                    renderRow = { this._renderRow } />
75
-            </SafeAreaView>
74
+            <NavigateSectionList
75
+                disabled = { disabled }
76
+                onPress = { this._onPress }
77
+                sections = { this._toDisplayableList() } />
76 78
         );
77 79
     }
78 80
 
81
+    _onPress: string => Function
82
+
79 83
     /**
80
-     * Assembles the style array of the avatar based on if the conference was
81
-     * hosted on the default Jitsi Meet deployment or on a non-default one
82
-     * (based on current app setting).
84
+     * Handles the list's navigate action.
83 85
      *
84
-     * @param {Object} recentListEntry - The recent list entry being rendered.
85 86
      * @private
86
-     * @returns {Array<Object>}
87
+     * @param {string} url - The url string to navigate to.
88
+     * @returns {void}
87 89
      */
88
-    _getAvatarStyle({ baseURL, serverName }) {
89
-        const avatarStyles = [ styles.avatar ];
90
+    _onPress(url) {
91
+        const { dispatch } = this.props;
90 92
 
91
-        if (baseURL !== this.props._defaultURL) {
92
-            avatarStyles.push(this._getColorForServerName(serverName));
93
-        }
94
-
95
-        return avatarStyles;
93
+        dispatch(appNavigate(url));
96 94
     }
97 95
 
96
+    _toDisplayableItem: Object => Object
97
+
98 98
     /**
99
-     * Returns a style (color) based on the server name, so then the same server
100
-     * will always be rendered with the same avatar color.
99
+     * Creates a displayable list item of a recent list entry.
101 100
      *
102
-     * @param {string} serverName - The recent list entry being rendered.
103 101
      * @private
102
+     * @param {Object} item - The recent list entry.
104 103
      * @returns {Object}
105 104
      */
106
-    _getColorForServerName(serverName) {
107
-        let nameHash = 0;
108
-
109
-        for (let i = 0; i < serverName.length; i++) {
110
-            nameHash += serverName.codePointAt(i);
111
-        }
112
-
113
-        return styles[`avatarRemoteServer${(nameHash % 5) + 1}`];
105
+    _toDisplayableItem(item) {
106
+        const { _defaultServerURL } = this.props;
107
+        const location = parseURIString(item.conference);
108
+        const baseURL = `${location.protocol}//${location.host}`;
109
+        const serverName = baseURL === _defaultServerURL ? null : location.host;
110
+
111
+        return {
112
+            colorBase: serverName,
113
+            key: `key-${item.conference}-${item.date}`,
114
+            lines: [
115
+                this._toDateString(item.date),
116
+                this._toDurationString(item.duration),
117
+                serverName
118
+            ],
119
+            title: location.room,
120
+            url: item.conference
121
+        };
114 122
     }
115 123
 
124
+    _toDisplayableList: () => Array<Object>
125
+
116 126
     /**
117
-     * Renders the conference duration if available.
127
+     * Transforms the history list to a displayable list
128
+     * with sections.
118 129
      *
119
-     * @param {Object} recentListEntry - The recent list entry being rendered.
120 130
      * @private
121
-     * @returns {ReactElement}
131
+     * @returns {Array<Object>}
122 132
      */
123
-    _renderConfDuration({ durationString }) {
124
-        if (durationString) {
125
-            return (
126
-                <View style = { styles.infoWithIcon } >
127
-                    <Icon
128
-                        name = 'timer'
129
-                        style = { styles.inlineIcon } />
130
-                    <Text style = { styles.confLength }>
131
-                        { durationString }
132
-                    </Text>
133
-                </View>
134
-            );
133
+    _toDisplayableList() {
134
+        const { _recentList, t } = this.props;
135
+        const todaySection = NavigateSectionList.createSection(
136
+            t('recentList.today'),
137
+            'today'
138
+        );
139
+        const yesterdaySection = NavigateSectionList.createSection(
140
+            t('recentList.yesterday'),
141
+            'yesterday'
142
+        );
143
+        const earlierSection = NavigateSectionList.createSection(
144
+            t('recentList.earlier'),
145
+            'earlier'
146
+        );
147
+        const today = new Date().toDateString();
148
+        const yesterdayDate = new Date();
149
+
150
+        yesterdayDate.setDate(yesterdayDate.getDate() - 1);
151
+
152
+        const yesterday = yesterdayDate.toDateString();
153
+
154
+        for (const item of _recentList) {
155
+            const itemDay = new Date(item.date).toDateString();
156
+            const displayableItem = this._toDisplayableItem(item);
157
+
158
+            if (itemDay === today) {
159
+                todaySection.data.push(displayableItem);
160
+            } else if (itemDay === yesterday) {
161
+                yesterdaySection.data.push(displayableItem);
162
+            } else {
163
+                earlierSection.data.push(displayableItem);
164
+            }
135 165
         }
136 166
 
137
-        return null;
167
+        const displayableList = [];
168
+
169
+        if (todaySection.data.length) {
170
+            todaySection.data.reverse();
171
+            displayableList.push(todaySection);
172
+        }
173
+        if (yesterdaySection.data.length) {
174
+            yesterdaySection.data.reverse();
175
+            displayableList.push(yesterdaySection);
176
+        }
177
+        if (earlierSection.data.length) {
178
+            earlierSection.data.reverse();
179
+            displayableList.push(earlierSection);
180
+        }
181
+
182
+        return displayableList;
138 183
     }
139 184
 
185
+    _toDateString: number => string
186
+
140 187
     /**
141
-     * Renders the list of recently joined rooms.
188
+     * Generates a date string for the item.
142 189
      *
143
-     * @param {Object} data - The row data to be rendered.
144 190
      * @private
145
-     * @returns {ReactElement}
191
+     * @param {number} itemDate - The item's timestamp.
192
+     * @returns {string}
146 193
      */
147
-    _renderRow(data) {
148
-        return (
149
-            <TouchableHighlight
150
-                onPress = { this._onSelect(data.conference) }
151
-                underlayColor = { UNDERLAY_COLOR } >
152
-                <View style = { styles.row } >
153
-                    <View style = { styles.avatarContainer } >
154
-                        <View style = { this._getAvatarStyle(data) } >
155
-                            <Text style = { styles.avatarContent }>
156
-                                { data.initials }
157
-                            </Text>
158
-                        </View>
159
-                    </View>
160
-                    <View style = { styles.detailsContainer } >
161
-                        <Text
162
-                            numberOfLines = { 1 }
163
-                            style = { styles.roomName }>
164
-                            { data.room }
165
-                        </Text>
166
-                        <View style = { styles.infoWithIcon } >
167
-                            <Icon
168
-                                name = 'event_note'
169
-                                style = { styles.inlineIcon } />
170
-                            <Text style = { styles.date }>
171
-                                { data.dateString }
172
-                            </Text>
173
-                        </View>
174
-                        { this._renderConfDuration(data) }
175
-                        { this._renderServerInfo(data) }
176
-                    </View>
177
-                </View>
178
-            </TouchableHighlight>
179
-        );
194
+    _toDateString(itemDate) {
195
+        const date = new Date(itemDate);
196
+        const m = getLocalizedDateFormatter(itemDate);
197
+
198
+        if (date.toDateString() === new Date().toDateString()) {
199
+            // The date is today, we use fromNow format.
200
+            return m.fromNow();
201
+        }
202
+
203
+        return m.format('lll');
180 204
     }
181 205
 
206
+    _toDurationString: number => string
207
+
182 208
     /**
183
-     * Renders the server info component based on whether the entry was on a
184
-     * different server.
209
+     * Generates a duration string for the item.
185 210
      *
186
-     * @param {Object} recentListEntry - The recent list entry being rendered.
187 211
      * @private
188
-     * @returns {ReactElement}
212
+     * @param {number} duration - The item's duration.
213
+     * @returns {string}
189 214
      */
190
-    _renderServerInfo({ baseURL, serverName }) {
191
-        if (baseURL !== this.props._defaultURL) {
192
-            return (
193
-                <View style = { styles.infoWithIcon } >
194
-                    <Icon
195
-                        name = 'public'
196
-                        style = { styles.inlineIcon } />
197
-                    <Text style = { styles.serverName }>
198
-                        { serverName }
199
-                    </Text>
200
-                </View>
201
-            );
215
+    _toDurationString(duration) {
216
+        if (duration) {
217
+            return getLocalizedDurationFormatter(duration).humanize();
202 218
         }
203 219
 
204 220
         return null;
205 221
     }
206 222
 }
207 223
 
208
-export default connect(_mapStateToProps)(RecentList);
224
+/**
225
+ * Maps redux state to component props.
226
+ *
227
+ * @param {Object} state - The redux state.
228
+ * @returns {{
229
+ *      _defaultServerURL: string,
230
+ *      _recentList: Array
231
+ * }}
232
+ */
233
+export function _mapStateToProps(state: Object) {
234
+    return {
235
+        _defaultServerURL: state['features/app'].app._getDefaultURL(),
236
+        _recentList: state['features/recent-list']
237
+    };
238
+}
239
+
240
+export default translate(connect(_mapStateToProps)(RecentList));

+ 0
- 166
react/features/recent-list/functions.js Прегледај датотеку

@@ -1,166 +0,0 @@
1
-// @flow
2
-
3
-import moment from 'moment';
4
-
5
-// MomentJS uses static language bundle loading, so in order to support dynamic
6
-// language selection in the app we need to load all bundles that we support in
7
-// the app.
8
-// FIXME: If we decide to support MomentJS in other features as well we may need
9
-// to move this import and the lenient matcher to the i18n feature.
10
-require('moment/locale/bg');
11
-require('moment/locale/de');
12
-require('moment/locale/eo');
13
-require('moment/locale/es');
14
-require('moment/locale/fr');
15
-require('moment/locale/hy-am');
16
-require('moment/locale/it');
17
-require('moment/locale/nb');
18
-
19
-// OC is not available. Please submit OC translation to the MomentJS project.
20
-require('moment/locale/pl');
21
-require('moment/locale/pt');
22
-require('moment/locale/pt-br');
23
-require('moment/locale/ru');
24
-require('moment/locale/sk');
25
-require('moment/locale/sl');
26
-require('moment/locale/sv');
27
-require('moment/locale/tr');
28
-require('moment/locale/zh-cn');
29
-
30
-import { i18next } from '../base/i18n';
31
-import { parseURIString } from '../base/util';
32
-
33
-/**
34
- * Retrieves the recent room list and generates all the data needed to be
35
- * displayed.
36
- *
37
- * @param {Array<Object>} list - The stored recent list retrieved from redux.
38
- * @returns {Array}
39
- */
40
-export function getRecentRooms(list: Array<Object>): Array<Object> {
41
-    const recentRoomDS = [];
42
-
43
-    if (list.length) {
44
-        // We init the locale on every list render, so then it changes
45
-        // immediately if a language change happens in the app.
46
-        const locale = _getSupportedLocale();
47
-
48
-        for (const e of list) {
49
-            const uri = parseURIString(e.conference);
50
-
51
-            if (uri && uri.room && uri.hostname) {
52
-                const duration
53
-                    = e.duration || /* legacy */ e.conferenceDuration || 0;
54
-
55
-                recentRoomDS.push({
56
-                    baseURL: `${uri.protocol}//${uri.host}`,
57
-                    conference: e.conference,
58
-                    dateString: _getDateString(e.date, locale),
59
-                    dateTimeStamp: e.date,
60
-                    duration,
61
-                    durationString: _getDurationString(duration, locale),
62
-                    initials: _getInitials(uri.room),
63
-                    room: uri.room,
64
-                    serverName: uri.hostname
65
-                });
66
-            }
67
-        }
68
-    }
69
-
70
-    return recentRoomDS.reverse();
71
-}
72
-
73
-/**
74
- * Returns a well formatted date string to be displayed in the list.
75
- *
76
- * @param {number} dateTimeStamp - The UTC timestamp to be converted to String.
77
- * @param {string} locale - The locale to init the formatter with. Note: This
78
- * locale must be supported by the formatter so ensure this prerequisite before
79
- * invoking the function.
80
- * @private
81
- * @returns {string}
82
- */
83
-function _getDateString(dateTimeStamp: number, locale: string) {
84
-    const date = new Date(dateTimeStamp);
85
-    const m = _getLocalizedFormatter(date, locale);
86
-
87
-    if (date.toDateString() === new Date().toDateString()) {
88
-        // The date is today, we use fromNow format.
89
-        return m.fromNow();
90
-    }
91
-
92
-    return m.format('lll');
93
-}
94
-
95
-/**
96
- * Returns a well formatted duration string to be displayed as the conference
97
- * length.
98
- *
99
- * @param {number} duration - The duration in MS.
100
- * @param {string} locale - The locale to init the formatter with. Note: This
101
- * locale must be supported by the formatter so ensure this prerequisite before
102
- * invoking the function.
103
- * @private
104
- * @returns {string}
105
- */
106
-function _getDurationString(duration: number, locale: string) {
107
-    return _getLocalizedFormatter(duration, locale).humanize();
108
-}
109
-
110
-/**
111
- * Returns the initials supposed to be used based on the room name.
112
- *
113
- * @param {string} room - The room name.
114
- * @private
115
- * @returns {string}
116
- */
117
-function _getInitials(room: string) {
118
-    return room && room.charAt(0) ? room.charAt(0).toUpperCase() : '?';
119
-}
120
-
121
-/**
122
- * Returns a localized date formatter initialized with a specific {@code Date}
123
- * or duration ({@code number}).
124
- *
125
- * @private
126
- * @param {Date|number} dateOrDuration - The date or duration to format.
127
- * @param {string} locale - The locale to init the formatter with. Note: The
128
- * specified locale must be supported by the formatter so ensure the
129
- * prerequisite is met before invoking the function.
130
- * @returns {Object}
131
- */
132
-function _getLocalizedFormatter(dateOrDuration: Date | number, locale: string) {
133
-    const m
134
-        = typeof dateOrDuration === 'number'
135
-            ? moment.duration(dateOrDuration)
136
-            : moment(dateOrDuration);
137
-
138
-    return m.locale(locale);
139
-}
140
-
141
-/**
142
- * A lenient locale matcher to match language and dialect if possible.
143
- *
144
- * @private
145
- * @returns {string}
146
- */
147
-function _getSupportedLocale() {
148
-    const i18nLocale = i18next.language;
149
-    let supportedLocale;
150
-
151
-    if (i18nLocale) {
152
-        const localeRegexp = new RegExp('^([a-z]{2,2})(-)*([a-z]{2,2})*$');
153
-        const localeResult = localeRegexp.exec(i18nLocale.toLowerCase());
154
-
155
-        if (localeResult) {
156
-            const currentLocaleRegexp
157
-                = new RegExp(
158
-                    `^${localeResult[1]}(-)*${`(${localeResult[3]})*` || ''}`);
159
-
160
-            supportedLocale
161
-                = moment.locales().find(lang => currentLocaleRegexp.exec(lang));
162
-        }
163
-    }
164
-
165
-    return supportedLocale || 'en';
166
-}

+ 8
- 3
react/features/welcome/components/PagedList.android.js Прегледај датотеку

@@ -20,8 +20,9 @@ export default class PagedList extends AbstractPagedList {
20 20
      *
21 21
      * @inheritdoc
22 22
      */
23
-    constructor() {
24
-        super();
23
+    constructor(props) {
24
+        super(props);
25
+
25 26
         this._getIndicatorStyle = this._getIndicatorStyle.bind(this);
26 27
         this._onPageSelected = this._onPageSelected.bind(this);
27 28
     }
@@ -35,7 +36,11 @@ export default class PagedList extends AbstractPagedList {
35 36
         const { disabled } = this.props;
36 37
 
37 38
         return (
38
-            <View style = { styles.pagedListContainer }>
39
+            <View
40
+                style = { [
41
+                    styles.pagedListContainer,
42
+                    disabled ? styles.pagedListContainerDisabled : null
43
+                ] }>
39 44
                 <ViewPagerAndroid
40 45
                     initialPage = { DEFAULT_PAGE }
41 46
                     keyboardDismissMode = 'on-drag'

+ 5
- 1
react/features/welcome/components/PagedList.ios.js Прегледај датотеку

@@ -39,7 +39,11 @@ class PagedList extends AbstractPagedList {
39 39
         const { disabled, t } = this.props;
40 40
 
41 41
         return (
42
-            <View style = { styles.pagedListContainer }>
42
+            <View
43
+                style = { [
44
+                    styles.pagedListContainer,
45
+                    disabled ? styles.pagedListContainerDisabled : null
46
+                ] }>
43 47
                 <TabBarIOS
44 48
                     itemPositioning = 'fill'
45 49
                     style = { styles.pagedList }>

+ 7
- 0
react/features/welcome/components/styles.js Прегледај датотеку

@@ -179,6 +179,13 @@ export default createStyleSheet({
179 179
         flexDirection: 'column'
180 180
     },
181 181
 
182
+    /**
183
+     * Disabled style for the container.
184
+     */
185
+    pagedListContainerDisabled: {
186
+        opacity: 0.2
187
+    },
188
+
182 189
     /**
183 190
      * Container for room name input box and 'join' button.
184 191
      */

Loading…
Откажи
Сачувај