Browse Source

[RN] Add swipe to delete feature

master
Bettenbuk Zoltan 6 years ago
parent
commit
d8c1f107da
27 changed files with 537 additions and 566 deletions
  1. BIN
      images/calendar@2x.png
  2. BIN
      images/calendar@3x.png
  3. BIN
      images/history@2x.png
  4. BIN
      images/history@3x.png
  5. 1
    0
      lang/main.json
  6. 33
    2
      package-lock.json
  7. 1
    0
      package.json
  8. 5
    0
      react/features/base/react/Types.js
  9. 22
    0
      react/features/base/react/components/AbstractPage.js
  10. 9
    2
      react/features/base/react/components/NavigateSectionList.js
  11. 1
    0
      react/features/base/react/components/index.js
  12. 0
    184
      react/features/base/react/components/native/AbstractPagedList.js
  13. 54
    23
      react/features/base/react/components/native/NavigateSectionListItem.js
  14. 0
    199
      react/features/base/react/components/native/PagedList.android.js
  15. 0
    100
      react/features/base/react/components/native/PagedList.ios.js
  16. 286
    0
      react/features/base/react/components/native/PagedList.js
  17. 10
    2
      react/features/base/react/components/native/styles.js
  18. 1
    0
      react/features/base/styles/components/styles/ColorPalette.js
  19. 22
    5
      react/features/calendar-sync/components/CalendarList.native.js
  20. 6
    5
      react/features/calendar-sync/components/CalendarList.web.js
  21. 4
    24
      react/features/calendar-sync/components/CalendarListContent.js
  22. 10
    0
      react/features/recent-list/actionTypes.js
  23. 19
    1
      react/features/recent-list/actions.js
  24. 30
    4
      react/features/recent-list/components/RecentList.js
  25. 4
    0
      react/features/recent-list/functions.any.js
  26. 17
    1
      react/features/recent-list/reducer.js
  27. 2
    14
      react/features/welcome/components/WelcomePageLists.js

BIN
images/calendar@2x.png View File


BIN
images/calendar@3x.png View File


BIN
images/history@2x.png View File


BIN
images/history@3x.png View File


+ 1
- 0
lang/main.json View File

@@ -63,6 +63,7 @@
63 63
         "join": "JOIN",
64 64
         "privacy": "Privacy",
65 65
         "recentList": "Recent",
66
+        "recentListDelete": "Delete",
66 67
         "recentListEmpty": "Your recent list is currently empty. Chat with your team and you will find all your recent meetings here.",
67 68
         "roomname": "Enter room name",
68 69
         "roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",

+ 33
- 2
package-lock.json View File

@@ -11426,8 +11426,7 @@
11426 11426
     "performance-now": {
11427 11427
       "version": "2.1.0",
11428 11428
       "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
11429
-      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
11430
-      "dev": true
11429
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
11431 11430
     },
11432 11431
     "pify": {
11433 11432
       "version": "2.3.0",
@@ -12281,6 +12280,14 @@
12281 12280
       "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=",
12282 12281
       "dev": true
12283 12282
     },
12283
+    "raf": {
12284
+      "version": "3.4.0",
12285
+      "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz",
12286
+      "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==",
12287
+      "requires": {
12288
+        "performance-now": "^2.1.0"
12289
+      }
12290
+    },
12284 12291
     "raf-schd": {
12285 12292
       "version": "2.1.2",
12286 12293
       "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-2.1.2.tgz",
@@ -12657,6 +12664,16 @@
12657 12664
       "resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.10.9.tgz",
12658 12665
       "integrity": "sha1-awCw9K/QF83gn7udFx3xtdW4Uag="
12659 12666
     },
12667
+    "react-native-swipeout": {
12668
+      "version": "2.3.6",
12669
+      "resolved": "https://registry.npmjs.org/react-native-swipeout/-/react-native-swipeout-2.3.6.tgz",
12670
+      "integrity": "sha512-t9suUCspzck4vp2pWggWe0frS/QOtX6yYCawHnEes75A7dZCEE74bxX2A1bQzGH9cUMjq6xsdfC94RbiDKIkJg==",
12671
+      "requires": {
12672
+        "create-react-class": "^15.6.0",
12673
+        "prop-types": "^15.5.10",
12674
+        "react-tween-state": "^0.1.5"
12675
+      }
12676
+    },
12660 12677
     "react-native-vector-icons": {
12661 12678
       "version": "4.4.2",
12662 12679
       "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-4.4.2.tgz",
@@ -12805,6 +12822,15 @@
12805 12822
         }
12806 12823
       }
12807 12824
     },
12825
+    "react-tween-state": {
12826
+      "version": "0.1.5",
12827
+      "resolved": "https://registry.npmjs.org/react-tween-state/-/react-tween-state-0.1.5.tgz",
12828
+      "integrity": "sha1-6YsGZVHvuTy5LdG+FJlcLj3q4zk=",
12829
+      "requires": {
12830
+        "raf": "^3.1.0",
12831
+        "tween-functions": "^1.0.1"
12832
+      }
12833
+    },
12808 12834
     "read-pkg": {
12809 12835
       "version": "2.0.0",
12810 12836
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
@@ -15500,6 +15526,11 @@
15500 15526
         "safe-buffer": "^5.0.1"
15501 15527
       }
15502 15528
     },
15529
+    "tween-functions": {
15530
+      "version": "1.2.0",
15531
+      "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
15532
+      "integrity": "sha1-GuOlDnxguz3vd06scHrLynO7w/8="
15533
+    },
15503 15534
     "tweetnacl": {
15504 15535
       "version": "0.14.5",
15505 15536
       "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",

+ 1
- 0
package.json View File

@@ -73,6 +73,7 @@
73 73
     "react-native-locale-detector": "github:jitsi/react-native-locale-detector#845281e9fd4af756f6d0f64afe5cce08c63e5ee9",
74 74
     "react-native-prompt": "1.0.0",
75 75
     "react-native-sound": "0.10.9",
76
+    "react-native-swipeout": "2.3.6",
76 77
     "react-native-vector-icons": "4.4.2",
77 78
     "react-native-webrtc": "github:jitsi/react-native-webrtc#be3de15bb988cfabbb62cd4b3b06f4c920ee5ba0",
78 79
     "react-redux": "5.0.7",

+ 5
- 0
react/features/base/react/Types.js View File

@@ -17,6 +17,11 @@ export type Item = {
17 17
      */
18 18
     elementAfter?: ?ComponentType<any>,
19 19
 
20
+    /**
21
+     * Unique ID of the item.
22
+     */
23
+    id: Object | string,
24
+
20 25
     /**
21 26
      * Item title
22 27
      */

+ 22
- 0
react/features/base/react/components/AbstractPage.js View File

@@ -0,0 +1,22 @@
1
+// @flow
2
+
3
+import { Component } from 'react';
4
+
5
+/**
6
+ * Abstract component that defines a refreshable page to be rendered by
7
+ * {@code PagedList}.
8
+ */
9
+export default class AbstractPage<P> extends Component<P> {
10
+    /**
11
+     * Method to be overriden by the implementing classes to refresh the data
12
+     * content of the component.
13
+     *
14
+     * Note: It is a static method as the {@code Component} may not be
15
+     * initialized yet when the UI invokes refresh (e.g. tab change).
16
+     *
17
+     * @returns {void}
18
+     */
19
+    static refresh() {
20
+        // No implementation in abstract class.
21
+    }
22
+}

+ 9
- 2
react/features/base/react/components/NavigateSectionList.js View File

@@ -43,7 +43,13 @@ type Props = {
43 43
     /**
44 44
      * An array of sections
45 45
      */
46
-    sections: Array<Section>
46
+    sections: Array<Section>,
47
+
48
+    /**
49
+     * Optional array of on-slide actions this list should support. For details
50
+     * see https://github.com/dancormier/react-native-swipeout.
51
+     */
52
+    slideActions?: Array<Object>
47 53
 };
48 54
 
49 55
 /**
@@ -205,7 +211,8 @@ class NavigateSectionList extends Component<Props> {
205 211
                 key = { key }
206 212
                 onPress = { url ? this._onPress(url) : undefined }
207 213
                 secondaryAction = {
208
-                    url ? undefined : this._onSecondaryAction(id) } />
214
+                    url ? undefined : this._onSecondaryAction(id) }
215
+                slideActions = { this.props.slideActions } />
209 216
         );
210 217
     }
211 218
 

+ 1
- 0
react/features/base/react/components/index.js View File

@@ -1,2 +1,3 @@
1 1
 export * from './_';
2
+export { default as AbstractPage } from './AbstractPage';
2 3
 export { default as NavigateSectionList } from './NavigateSectionList';

+ 0
- 184
react/features/base/react/components/native/AbstractPagedList.js View File

@@ -1,184 +0,0 @@
1
-// @flow
2
-
3
-import React, { Component } from 'react';
4
-import { View } from 'react-native';
5
-
6
-import styles from './styles';
7
-
8
-/**
9
- * The type of the React {@code Component} props of {@link AbstractPagedList}.
10
- */
11
-type Props = {
12
-
13
-    /**
14
-     * The zero-based index of the page that should be rendered (selected) by
15
-     * default.
16
-     */
17
-    defaultPage: number,
18
-
19
-    /**
20
-     * Indicates if the list is disabled or not.
21
-     */
22
-    disabled: boolean,
23
-
24
-    /**
25
-     * The Redux dispatch function.
26
-     */
27
-    dispatch: Function,
28
-
29
-    /**
30
-     * Callback to execute on page change.
31
-     */
32
-    onSelectPage: ?Function,
33
-
34
-    /**
35
-     * The pages of the PagedList component to be rendered.
36
-     *
37
-     * Note: An element's {@code component} may be {@code undefined} and then it
38
-     * won't need to be rendered.
39
-     */
40
-    pages: Array<{
41
-        component: ?Object,
42
-        icon: string | number,
43
-        title: string
44
-    }>
45
-};
46
-
47
-/**
48
- * The type of the React {@code Component} state of {@link AbstractPagedList}.
49
- */
50
-type State = {
51
-
52
-    /**
53
-     * The currently selected page.
54
-     */
55
-    pageIndex: number
56
-};
57
-
58
-/**
59
- * Abstract class containing the platform independent logic of the paged lists.
60
- */
61
-export default class AbstractPagedList extends Component<Props, State> {
62
-    /**
63
-     * Initializes a new {@code AbstractPagedList} instance.
64
-     *
65
-     * @inheritdoc
66
-     */
67
-    constructor(props: Props) {
68
-        super(props);
69
-
70
-        this.state = {
71
-            pageIndex: this._validatePageIndex(props.defaultPage)
72
-        };
73
-
74
-        // Bind event handlers so they are only bound once per instance.
75
-        this._maybeRefreshSelectedPage
76
-            = this._maybeRefreshSelectedPage.bind(this);
77
-    }
78
-
79
-    /**
80
-     * Implements React's {@link Component#componentDidMount}.
81
-     *
82
-     * @inheritdoc
83
-     */
84
-    componentDidMount() {
85
-        this._maybeRefreshSelectedPage(false);
86
-    }
87
-
88
-    /**
89
-     * Renders the component.
90
-     *
91
-     * @inheritdoc
92
-     */
93
-    render() {
94
-        const { disabled } = this.props;
95
-        const pages = this.props.pages.filter(({ component }) => component);
96
-
97
-        return (
98
-            <View
99
-                style = { [
100
-                    styles.pagedListContainer,
101
-                    disabled ? styles.pagedListContainerDisabled : null
102
-                ] }>
103
-                {
104
-                    pages.length > 1
105
-                        ? this._renderPagedList(disabled)
106
-                        : pages.length === 1
107
-                            ? React.createElement(
108
-
109
-                                // $FlowExpectedError
110
-                                /* type */ pages[0].component,
111
-                                /* props */ {
112
-                                    disabled,
113
-                                    style: styles.pagedList
114
-                                })
115
-                            : null
116
-                }
117
-            </View>
118
-        );
119
-    }
120
-
121
-    _maybeRefreshSelectedPage: ?boolean => void;
122
-
123
-    /**
124
-     * Components that this PagedList displays may have a refresh function to
125
-     * refresh its content when displayed (or based on custom logic). This
126
-     * function invokes this logic if it's present.
127
-     *
128
-     * @private
129
-     * @param {boolean} isInteractive - If true this refresh was caused by
130
-     * direct user interaction, false otherwise.
131
-     * @returns {void}
132
-     */
133
-    _maybeRefreshSelectedPage(isInteractive: boolean = true) {
134
-        const selectedPage = this.props.pages[this.state.pageIndex];
135
-        let component;
136
-
137
-        if (selectedPage && (component = selectedPage.component)) {
138
-            const { refresh } = component;
139
-
140
-            typeof refresh === 'function'
141
-                && refresh.call(component, this.props.dispatch, isInteractive);
142
-        }
143
-    }
144
-
145
-    _renderPagedList: boolean => React$Node;
146
-
147
-    _selectPage: number => void;
148
-
149
-    /**
150
-     * Sets the selected page.
151
-     *
152
-     * @param {number} pageIndex - The index of the selected page.
153
-     * @protected
154
-     * @returns {void}
155
-     */
156
-    _selectPage(pageIndex: number) {
157
-        // eslint-disable-next-line no-param-reassign
158
-        pageIndex = this._validatePageIndex(pageIndex);
159
-
160
-        const { onSelectPage } = this.props;
161
-
162
-        typeof onSelectPage === 'function' && onSelectPage(pageIndex);
163
-
164
-        this.setState({ pageIndex }, this._maybeRefreshSelectedPage);
165
-    }
166
-
167
-    _validatePageIndex: number => number;
168
-
169
-    /**
170
-     * Validates the requested page index and returns a safe value.
171
-     *
172
-     * @private
173
-     * @param {number} pageIndex - The requested page index.
174
-     * @returns {number}
175
-     */
176
-    _validatePageIndex(pageIndex) {
177
-        // pageIndex may point to a non-existing page if some of the pages are
178
-        // disabled (their component property is undefined).
179
-        const maxPageIndex
180
-            = this.props.pages.filter(({ component }) => component).length - 1;
181
-
182
-        return Math.max(0, Math.min(maxPageIndex, pageIndex));
183
-    }
184
-}

+ 54
- 23
react/features/base/react/components/native/NavigateSectionListItem.js View File

@@ -1,6 +1,9 @@
1 1
 // @flow
2 2
 
3 3
 import React, { Component } from 'react';
4
+import Swipeout from 'react-native-swipeout';
5
+
6
+import { ColorPalette } from '../../../styles';
4 7
 
5 8
 import Container from './Container';
6 9
 import Text from './Text';
@@ -22,7 +25,13 @@ type Props = {
22 25
     /**
23 26
      * Function to be invoked when secondary action was performed on an Item.
24 27
      */
25
-    secondaryAction: ?Function
28
+    secondaryAction: ?Function,
29
+
30
+    /**
31
+     * Optional array of on-slide actions this list should support. For details
32
+     * see https://github.com/dancormier/react-native-swipeout.
33
+     */
34
+    slideActions?: Array<Object>
26 35
 }
27 36
 
28 37
 /**
@@ -129,37 +138,59 @@ export default class NavigateSectionListItem extends Component<Props> {
129 138
      * @returns {ReactElement}
130 139
      */
131 140
     render() {
132
-        const { colorBase, lines, title } = this.props.item;
141
+        const { slideActions } = this.props;
142
+        const { id, colorBase, lines, title } = this.props.item;
133 143
         const avatarStyles = {
134 144
             ...styles.avatar,
135 145
             ...this._getAvatarColor(colorBase)
136 146
         };
147
+        let right;
148
+
149
+        // NOTE: The {@code Swipeout} component has an onPress prop encapsulated
150
+        // in the {@code right} array, but we need to bind it to the ID of the
151
+        // item too.
152
+
153
+        if (slideActions) {
154
+            right = [];
155
+            for (const slideAction of slideActions) {
156
+                right.push({
157
+                    backgroundColor: slideAction.backgroundColor,
158
+                    onPress: slideAction.onPress.bind(undefined, id),
159
+                    text: slideAction.text
160
+                });
161
+            }
162
+        }
137 163
 
138 164
         return (
139
-            <Container
140
-                onClick = { this.props.onPress }
141
-                style = { styles.listItem }
142
-                underlayColor = { UNDERLAY_COLOR }>
143
-                <Container style = { styles.avatarContainer }>
144
-                    <Container style = { avatarStyles }>
145
-                        <Text style = { styles.avatarContent }>
146
-                            {title.substr(0, 1).toUpperCase()}
165
+            <Swipeout
166
+                backgroundColor = { ColorPalette.transparent }
167
+                right = { right }>
168
+                <Container
169
+                    onClick = { this.props.onPress }
170
+                    style = { styles.listItem }
171
+                    underlayColor = { UNDERLAY_COLOR }>
172
+                    <Container style = { styles.avatarContainer }>
173
+                        <Container style = { avatarStyles }>
174
+                            <Text style = { styles.avatarContent }>
175
+                                {title.substr(0, 1).toUpperCase()}
176
+                            </Text>
177
+                        </Container>
178
+                    </Container>
179
+                    <Container style = { styles.listItemDetails }>
180
+                        <Text
181
+                            numberOfLines = { 1 }
182
+                            style = { [
183
+                                styles.listItemText,
184
+                                styles.listItemTitle
185
+                            ] }>
186
+                            {title}
147 187
                         </Text>
188
+                        {this._renderItemLines(lines)}
148 189
                     </Container>
190
+                    { this.props.secondaryAction
191
+                        && this._renderSecondaryAction() }
149 192
                 </Container>
150
-                <Container style = { styles.listItemDetails }>
151
-                    <Text
152
-                        numberOfLines = { 1 }
153
-                        style = {{
154
-                            ...styles.listItemText,
155
-                            ...styles.listItemTitle
156
-                        }}>
157
-                        {title}
158
-                    </Text>
159
-                    {this._renderItemLines(lines)}
160
-                </Container>
161
-                { this.props.secondaryAction && this._renderSecondaryAction() }
162
-            </Container>
193
+            </Swipeout>
163 194
         );
164 195
     }
165 196
 }

+ 0
- 199
react/features/base/react/components/native/PagedList.android.js View File

@@ -1,199 +0,0 @@
1
-// @flow
2
-
3
-import React from 'react';
4
-import { Text, TouchableOpacity, View, ViewPagerAndroid } from 'react-native';
5
-import { connect } from 'react-redux';
6
-
7
-import { Icon } from '../../../font-icons';
8
-
9
-import AbstractPagedList from './AbstractPagedList';
10
-import styles from './styles';
11
-
12
-/**
13
- * An Android specific component to render a paged list.
14
- *
15
- * @extends PagedList
16
- */
17
-class PagedList extends AbstractPagedList {
18
-    /**
19
-     * A reference to the viewpager.
20
-     */
21
-    _viewPager: ViewPagerAndroid;
22
-
23
-    /**
24
-     * Initializes a new {@code PagedList} instance.
25
-     *
26
-     * @inheritdoc
27
-     */
28
-    constructor(props) {
29
-        super(props);
30
-
31
-        // Bind event handlers so they are only bound once per instance.
32
-        this._onIconPress = this._onIconPress.bind(this);
33
-        this._getIndicatorStyle = this._getIndicatorStyle.bind(this);
34
-        this._onPageSelected = this._onPageSelected.bind(this);
35
-        this._setViewPager = this._setViewPager.bind(this);
36
-    }
37
-
38
-    _onIconPress: number => Function;
39
-
40
-    /**
41
-     * Constructs a function to be used as a callback for the icons in the tab
42
-     * bar.
43
-     *
44
-     * @param {number} pageIndex - The index of the page to activate via the
45
-     * callback.
46
-     * @private
47
-     * @returns {Function}
48
-     */
49
-    _onIconPress(pageIndex) {
50
-        return () => {
51
-            this._viewPager.setPage(pageIndex);
52
-            this._selectPage(pageIndex);
53
-        };
54
-    }
55
-
56
-    _getIndicatorStyle: number => Object;
57
-
58
-    /**
59
-     * Constructs the style of an indicator.
60
-     *
61
-     * @param {number} indicatorIndex - The index of the indicator.
62
-     * @private
63
-     * @returns {Object}
64
-     */
65
-    _getIndicatorStyle(indicatorIndex) {
66
-        if (this.state.pageIndex === indicatorIndex) {
67
-            return styles.pageIndicatorActive;
68
-        }
69
-
70
-        return null;
71
-    }
72
-
73
-    _onPageSelected: Object => void;
74
-
75
-    /**
76
-     * Updates the index of the currently selected page, based on the native
77
-     * event received from the {@link ViewPagerAndroid} component.
78
-     *
79
-     * @param {Object} event - The native event of the callback.
80
-     * @private
81
-     * @returns {void}
82
-     */
83
-    _onPageSelected({ nativeEvent: { position } }) {
84
-        if (this.state.pageIndex !== position) {
85
-            this._selectPage(position);
86
-        }
87
-    }
88
-
89
-    /**
90
-     * Renders a single page of the page list.
91
-     *
92
-     * @private
93
-     * @param {Object} page - The page to render.
94
-     * @param {number} index - The index of the rendered page.
95
-     * @param {boolean} disabled - Renders the page disabled.
96
-     * @returns {React$Node}
97
-     */
98
-    _renderPage(page, index, disabled) {
99
-        return page.component
100
-            ? <View key = { index }>
101
-                {
102
-                    React.createElement(
103
-                        page.component,
104
-                        {
105
-                            disabled
106
-                        })
107
-                }
108
-            </View>
109
-            : null;
110
-    }
111
-
112
-    /**
113
-     * Renders a page indicator (icon) for the page.
114
-     *
115
-     * @private
116
-     * @param {Object} page - The page the indicator is rendered for.
117
-     * @param {number} index - The index of the page the indicator is rendered
118
-     * for.
119
-     * @param {boolean} disabled - Renders the indicator disabled.
120
-     * @returns {React$Node}
121
-     */
122
-    _renderPageIndicator(page, index, disabled) {
123
-        return page.component
124
-            ? <TouchableOpacity
125
-                disabled = { disabled }
126
-                key = { index }
127
-                onPress = { this._onIconPress(index) }
128
-                style = { styles.pageIndicator } >
129
-                <View style = { styles.pageIndicator }>
130
-                    <Icon
131
-                        name = { page.icon }
132
-                        style = { [
133
-                            styles.pageIndicatorIcon,
134
-                            this._getIndicatorStyle(index)
135
-                        ] } />
136
-                    <Text
137
-                        style = { [
138
-                            styles.pageIndicatorText,
139
-                            this._getIndicatorStyle(index)
140
-                        ] }>
141
-                        { page.title }
142
-                    </Text>
143
-                </View>
144
-            </TouchableOpacity>
145
-            : null;
146
-    }
147
-
148
-    /**
149
-     * Renders the paged list if multiple pages are to be rendered. This is the
150
-     * platform dependent part of the component.
151
-     *
152
-     * @param {boolean} disabled - True if the rendered lists should be
153
-     * disabled.
154
-     * @returns {ReactElement}
155
-     */
156
-    _renderPagedList(disabled) {
157
-        const { defaultPage, pages } = this.props;
158
-
159
-        return (
160
-            <View style = { styles.pagedListContainer }>
161
-                <ViewPagerAndroid
162
-                    initialPage = { defaultPage }
163
-                    onPageSelected = { this._onPageSelected }
164
-                    peekEnabled = { true }
165
-                    ref = { this._setViewPager }
166
-                    style = { styles.pagedList }>
167
-                    {
168
-                        pages.map((page, index) => this._renderPage(
169
-                            page, index, disabled
170
-                        ))
171
-                    }
172
-                </ViewPagerAndroid>
173
-                <View style = { styles.pageIndicatorContainer }>
174
-                    {
175
-                        pages.map((page, index) => this._renderPageIndicator(
176
-                            page, index, disabled
177
-                        ))
178
-                    }
179
-                </View>
180
-            </View>
181
-        );
182
-    }
183
-
184
-    _setViewPager: Object => void;
185
-
186
-    /**
187
-     * Sets the {@link ViewPagerAndroid} instance.
188
-     *
189
-     * @param {ViewPagerAndroid} viewPager - The {@code ViewPagerAndroid}
190
-     * instance.
191
-     * @private
192
-     * @returns {void}
193
-     */
194
-    _setViewPager(viewPager) {
195
-        this._viewPager = viewPager;
196
-    }
197
-}
198
-
199
-export default connect()(PagedList);

+ 0
- 100
react/features/base/react/components/native/PagedList.ios.js View File

@@ -1,100 +0,0 @@
1
-// @flow
2
-
3
-import React from 'react';
4
-import { TabBarIOS } from 'react-native';
5
-import { connect } from 'react-redux';
6
-
7
-import AbstractPagedList from './AbstractPagedList';
8
-import styles from './styles';
9
-
10
-/**
11
- * An iOS specific component to render a paged list.
12
- *
13
- * @extends PagedList
14
- */
15
-class PagedList extends AbstractPagedList {
16
-
17
-    /**
18
-     * Initializes a new {@code PagedList} instance.
19
-     *
20
-     * @inheritdoc
21
-     */
22
-    constructor(props) {
23
-        super(props);
24
-
25
-        // Bind event handlers so they are only bound once per instance.
26
-        this._onTabSelected = this._onTabSelected.bind(this);
27
-    }
28
-
29
-    _onTabSelected: number => Function;
30
-
31
-    /**
32
-     * Constructs a callback to update the selected tab when the bottom bar icon
33
-     * is pressed.
34
-     *
35
-     * @param {number} tabIndex - The selected tab.
36
-     * @private
37
-     * @returns {Function}
38
-     */
39
-    _onTabSelected(tabIndex) {
40
-        return () => super._selectPage(tabIndex);
41
-    }
42
-
43
-    _renderPage: (Object, number, boolean) => React$Node
44
-
45
-    /**
46
-     * Renders a single page of the page list.
47
-     *
48
-     * @private
49
-     * @param {Object} page - The page to render.
50
-     * @param {number} index - The index of the rendered page.
51
-     * @param {boolean} disabled - Renders the page disabled.
52
-     * @returns {React$Node}
53
-     */
54
-    _renderPage(page, index, disabled) {
55
-        const { pageIndex } = this.state;
56
-
57
-        return page.component
58
-            ? <TabBarIOS.Item
59
-                icon = { page.icon }
60
-                key = { index }
61
-                onPress = { this._onTabSelected(index) }
62
-                selected = { pageIndex === index }
63
-                title = { page.title }>
64
-                {
65
-                    React.createElement(
66
-                        page.component,
67
-                        {
68
-                            disabled
69
-                        })
70
-                }
71
-            </TabBarIOS.Item>
72
-            : null;
73
-    }
74
-
75
-    /**
76
-     * Renders the paged list if multiple pages are to be rendered. This is the
77
-     * platform dependent part of the component.
78
-     *
79
-     * @param {boolean} disabled - True if the rendered lists should be
80
-     * disabled.
81
-     * @returns {ReactElement}
82
-     */
83
-    _renderPagedList(disabled) {
84
-        const { pages } = this.props;
85
-
86
-        return (
87
-            <TabBarIOS
88
-                itemPositioning = 'fill'
89
-                style = { styles.pagedList }>
90
-                {
91
-                    pages.map((page, index) => this._renderPage(
92
-                        page, index, disabled
93
-                    ))
94
-                }
95
-            </TabBarIOS>
96
-        );
97
-    }
98
-}
99
-
100
-export default connect()(PagedList);

+ 286
- 0
react/features/base/react/components/native/PagedList.js View File

@@ -0,0 +1,286 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { Text, TouchableOpacity, View } from 'react-native';
5
+import { connect } from 'react-redux';
6
+
7
+import { Icon } from '../../../font-icons';
8
+
9
+import styles from './styles';
10
+
11
+/**
12
+ * The type of the React {@code Component} props of {@link PagedList}.
13
+ */
14
+type Props = {
15
+
16
+    /**
17
+     * The zero-based index of the page that should be rendered (selected) by
18
+     * default.
19
+     */
20
+    defaultPage: number,
21
+
22
+    /**
23
+     * Indicates if the list is disabled or not.
24
+     */
25
+    disabled: boolean,
26
+
27
+    /**
28
+     * The Redux dispatch function.
29
+     */
30
+    dispatch: Function,
31
+
32
+    /**
33
+     * Callback to execute on page change.
34
+     */
35
+    onSelectPage: ?Function,
36
+
37
+    /**
38
+     * The pages of the PagedList component to be rendered.
39
+     *
40
+     * NOTE 1: An element's {@code component} may be {@code undefined} and then
41
+     * it won't need to be rendered.
42
+     *
43
+     * NOTE 2: There must be at least one page available and enabled.
44
+     */
45
+    pages: Array<{
46
+        component: ?Object,
47
+        icon: string | number,
48
+        title: string
49
+    }>
50
+};
51
+
52
+/**
53
+ * The type of the React {@code Component} state of {@link PagedList}.
54
+ */
55
+type State = {
56
+
57
+    /**
58
+     * The currently selected page.
59
+     */
60
+    pageIndex: number
61
+};
62
+
63
+/**
64
+ * A component that renders a paged list.
65
+ *
66
+ * @extends PagedList
67
+ */
68
+class PagedList extends Component<Props, State> {
69
+
70
+    /**
71
+     * Initializes a new {@code PagedList} instance.
72
+     *
73
+     * @inheritdoc
74
+     */
75
+    constructor(props: Props) {
76
+        super(props);
77
+
78
+        this.state = {
79
+            pageIndex: this._validatePageIndex(props.defaultPage)
80
+        };
81
+
82
+        // Bind event handlers so they are only bound once per instance.
83
+        this._maybeRefreshSelectedPage
84
+            = this._maybeRefreshSelectedPage.bind(this);
85
+    }
86
+
87
+    /**
88
+     * Renders the component.
89
+     *
90
+     * @inheritdoc
91
+     */
92
+    render() {
93
+        const { disabled } = this.props;
94
+        const pages = this.props.pages.filter(({ component }) => component);
95
+
96
+        return (
97
+            <View
98
+                style = { [
99
+                    styles.pagedListContainer,
100
+                    disabled ? styles.pagedListContainerDisabled : null
101
+                ] }>
102
+                {
103
+                    pages.length > 1
104
+                        ? this._renderPagedList(disabled)
105
+                        : React.createElement(
106
+
107
+                            // $FlowExpectedError
108
+                            /* type */ pages[0].component,
109
+                            /* props */ {
110
+                                disabled,
111
+                                style: styles.pagedList
112
+                            })
113
+                }
114
+            </View>
115
+        );
116
+    }
117
+
118
+    /**
119
+     * Constructs the style of an indicator.
120
+     *
121
+     * @param {number} indicatorIndex - The index of the indicator.
122
+     * @private
123
+     * @returns {Object}
124
+     */
125
+    _getIndicatorStyle(indicatorIndex) {
126
+        if (this.state.pageIndex === indicatorIndex) {
127
+            return styles.pageIndicatorActive;
128
+        }
129
+
130
+        return null;
131
+    }
132
+
133
+    _maybeRefreshSelectedPage: ?boolean => void;
134
+
135
+    /**
136
+     * Components that this PagedList displays may have a refresh function to
137
+     * refresh its content when displayed (or based on custom logic). This
138
+     * function invokes this logic if it's present.
139
+     *
140
+     * @private
141
+     * @param {boolean} isInteractive - If true this refresh was caused by
142
+     * direct user interaction, false otherwise.
143
+     * @returns {void}
144
+     */
145
+    _maybeRefreshSelectedPage(isInteractive: boolean = true) {
146
+        const selectedPage = this.props.pages[this.state.pageIndex];
147
+        let component;
148
+
149
+        if (selectedPage && (component = selectedPage.component)) {
150
+            const { refresh } = component;
151
+
152
+            refresh.call(component, this.props.dispatch, isInteractive);
153
+        }
154
+    }
155
+
156
+    /**
157
+     * Sets the selected page.
158
+     *
159
+     * @param {number} pageIndex - The index of the selected page.
160
+     * @protected
161
+     * @returns {void}
162
+     */
163
+    _onSelectPage(pageIndex: number) {
164
+        return () => {
165
+            // eslint-disable-next-line no-param-reassign
166
+            pageIndex = this._validatePageIndex(pageIndex);
167
+
168
+            const { onSelectPage } = this.props;
169
+
170
+            onSelectPage && onSelectPage(pageIndex);
171
+
172
+            this.setState({ pageIndex }, this._maybeRefreshSelectedPage);
173
+        };
174
+    }
175
+
176
+    /**
177
+     * Renders a single page of the page list.
178
+     *
179
+     * @private
180
+     * @param {Object} page - The page to render.
181
+     * @param {boolean} disabled - Renders the page disabled.
182
+     * @returns {React$Node}
183
+     */
184
+    _renderPage(page, disabled) {
185
+        if (!page.component) {
186
+            return null;
187
+        }
188
+
189
+        return (
190
+            <View style = { styles.pageContainer }>
191
+                {
192
+                    React.createElement(
193
+                        page.component,
194
+                        {
195
+                            disabled
196
+                        })
197
+                }
198
+            </View>
199
+        );
200
+    }
201
+
202
+    /**
203
+     * Renders the paged list if multiple pages are to be rendered.
204
+     *
205
+     * @param {boolean} disabled - True if the rendered lists should be
206
+     * disabled.
207
+     * @returns {ReactElement}
208
+     */
209
+    _renderPagedList(disabled) {
210
+        const { pages } = this.props;
211
+        const { pageIndex } = this.state;
212
+
213
+        return (
214
+            <View style = { styles.pagedListContainer }>
215
+                {
216
+                    this._renderPage(pages[pageIndex], disabled)
217
+                }
218
+                <View style = { styles.pageIndicatorContainer }>
219
+                    {
220
+                        pages.map((page, index) => this._renderPageIndicator(
221
+                            page, index, disabled
222
+                        ))
223
+                    }
224
+                </View>
225
+            </View>
226
+        );
227
+    }
228
+
229
+    /**
230
+     * Renders a page indicator (icon) for the page.
231
+     *
232
+     * @private
233
+     * @param {Object} page - The page the indicator is rendered for.
234
+     * @param {number} index - The index of the page the indicator is rendered
235
+     * for.
236
+     * @param {boolean} disabled - Renders the indicator disabled.
237
+     * @returns {React$Node}
238
+     */
239
+    _renderPageIndicator(page, index, disabled) {
240
+        if (!page.component) {
241
+            return null;
242
+        }
243
+
244
+        return (
245
+            <TouchableOpacity
246
+                disabled = { disabled }
247
+                key = { index }
248
+                onPress = { this._onSelectPage(index) }
249
+                style = { styles.pageIndicator } >
250
+                <View style = { styles.pageIndicator }>
251
+                    <Icon
252
+                        name = { page.icon }
253
+                        style = { [
254
+                            styles.pageIndicatorIcon,
255
+                            this._getIndicatorStyle(index)
256
+                        ] } />
257
+                    <Text
258
+                        style = { [
259
+                            styles.pageIndicatorText,
260
+                            this._getIndicatorStyle(index)
261
+                        ] }>
262
+                        { page.title }
263
+                    </Text>
264
+                </View>
265
+            </TouchableOpacity>
266
+        );
267
+    }
268
+
269
+    /**
270
+     * Validates the requested page index and returns a safe value.
271
+     *
272
+     * @private
273
+     * @param {number} pageIndex - The requested page index.
274
+     * @returns {number}
275
+     */
276
+    _validatePageIndex(pageIndex) {
277
+        // pageIndex may point to a non-existing page if some of the pages are
278
+        // disabled (their component property is undefined).
279
+        const maxPageIndex
280
+            = this.props.pages.filter(({ component }) => component).length - 1;
281
+
282
+        return Math.max(0, Math.min(maxPageIndex, pageIndex));
283
+    }
284
+}
285
+
286
+export default connect()(PagedList);

+ 10
- 2
react/features/base/react/components/native/styles.js View File

@@ -73,6 +73,13 @@ const HEADER_STYLES = {
73 73
  */
74 74
 const PAGED_LIST_STYLES = {
75 75
 
76
+    /**
77
+     * Outermost container of a page in {@code PagedList}.
78
+     */
79
+    pageContainer: {
80
+        flex: 1
81
+    },
82
+
76 83
     /**
77 84
      * Style of the page indicator (Android).
78 85
      */
@@ -214,7 +221,7 @@ const SECTION_LIST_STYLES = {
214 221
         alignItems: 'center',
215 222
         flex: 1,
216 223
         flexDirection: 'row',
217
-        paddingVertical: 5
224
+        padding: 5
218 225
     },
219 226
 
220 227
     listItemDetails: {
@@ -239,7 +246,8 @@ const SECTION_LIST_STYLES = {
239 246
         backgroundColor: 'rgba(255, 255, 255, 0.2)',
240 247
         flex: 1,
241 248
         flexDirection: 'row',
242
-        padding: 5
249
+        paddingVertical: 5,
250
+        paddingHorizontal: 10
243 251
     },
244 252
 
245 253
     listSectionText: {

+ 1
- 0
react/features/base/styles/components/styles/ColorPalette.js View File

@@ -26,6 +26,7 @@ export const ColorPalette = {
26 26
     lightGrey: '#AAAAAA',
27 27
     lighterGrey: '#EEEEEE',
28 28
     red: '#D00000',
29
+    transparent: 'rgba(0, 0, 0, 0)',
29 30
     white: 'white',
30 31
 
31 32
     /**

+ 22
- 5
react/features/calendar-sync/components/CalendarList.native.js View File

@@ -1,16 +1,18 @@
1 1
 // @flow
2 2
 
3
-import React, { Component } from 'react';
3
+import React from 'react';
4 4
 import { Text, TouchableOpacity, View } from 'react-native';
5 5
 import { connect } from 'react-redux';
6 6
 
7 7
 import { openSettings } from '../../mobile/permissions';
8 8
 import { translate } from '../../base/i18n';
9
+import { AbstractPage } from '../../base/react';
9 10
 
11
+import { refreshCalendar } from '../actions';
10 12
 import { isCalendarEnabled } from '../functions';
11 13
 import styles from './styles';
12 14
 
13
-import BaseCalendarList from './BaseCalendarList';
15
+import CalendarListContent from './CalendarListContent';
14 16
 
15 17
 /**
16 18
  * The tyoe of the React {@code Component} props of {@link CalendarList}.
@@ -36,7 +38,7 @@ type Props = {
36 38
 /**
37 39
  * Component to display a list of events from the (mobile) user's calendar.
38 40
  */
39
-class CalendarList extends Component<Props> {
41
+class CalendarList extends AbstractPage<Props> {
40 42
     /**
41 43
      * Initializes a new {@code CalendarList} instance.
42 44
      *
@@ -50,6 +52,21 @@ class CalendarList extends Component<Props> {
50 52
             = this._getRenderListEmptyComponent.bind(this);
51 53
     }
52 54
 
55
+    /**
56
+     * Public API method for {@code Component}s rendered in
57
+     * {@link AbstractPagedList}. When invoked, refreshes the calendar entries
58
+     * in the app.
59
+     *
60
+     * @param {Function} dispatch - The Redux dispatch function.
61
+     * @param {boolean} isInteractive - If true this refresh was caused by
62
+     * direct user interaction, false otherwise.
63
+     * @public
64
+     * @returns {void}
65
+     */
66
+    static refresh(dispatch, isInteractive) {
67
+        dispatch(refreshCalendar(false, isInteractive));
68
+    }
69
+
53 70
     /**
54 71
      * Implements React's {@link Component#render}.
55 72
      *
@@ -59,8 +76,8 @@ class CalendarList extends Component<Props> {
59 76
         const { disabled } = this.props;
60 77
 
61 78
         return (
62
-            BaseCalendarList
63
-                ? <BaseCalendarList
79
+            CalendarListContent
80
+                ? <CalendarListContent
64 81
                     disabled = { disabled }
65 82
                     renderListEmptyComponent
66 83
                         = { this._getRenderListEmptyComponent() } />

+ 6
- 5
react/features/calendar-sync/components/CalendarList.web.js View File

@@ -2,10 +2,11 @@
2 2
 
3 3
 import Button from '@atlaskit/button';
4 4
 import Spinner from '@atlaskit/spinner';
5
-import React, { Component } from 'react';
5
+import React from 'react';
6 6
 import { connect } from 'react-redux';
7 7
 
8 8
 import { translate } from '../../base/i18n';
9
+import { AbstractPage } from '../../base/react';
9 10
 import { openSettingsDialog, SETTINGS_TABS } from '../../settings';
10 11
 import {
11 12
     createCalendarClickedEvent,
@@ -15,7 +16,7 @@ import {
15 16
 import { refreshCalendar } from '../actions';
16 17
 import { isCalendarEnabled } from '../functions';
17 18
 
18
-import BaseCalendarList from './BaseCalendarList';
19
+import CalendarListContent from './CalendarListContent';
19 20
 
20 21
 declare var interfaceConfig: Object;
21 22
 
@@ -53,7 +54,7 @@ type Props = {
53 54
 /**
54 55
  * Component to display a list of events from the user's calendar.
55 56
  */
56
-class CalendarList extends Component<Props> {
57
+class CalendarList extends AbstractPage<Props> {
57 58
     /**
58 59
      * Initializes a new {@code CalendarList} instance.
59 60
      *
@@ -78,8 +79,8 @@ class CalendarList extends Component<Props> {
78 79
         const { disabled } = this.props;
79 80
 
80 81
         return (
81
-            BaseCalendarList
82
-                ? <BaseCalendarList
82
+            CalendarListContent
83
+                ? <CalendarListContent
83 84
                     disabled = { disabled }
84 85
                     renderListEmptyComponent
85 86
                         = { this._getRenderListEmptyComponent() } />

react/features/calendar-sync/components/BaseCalendarList.js → react/features/calendar-sync/components/CalendarListContent.js View File

@@ -13,7 +13,6 @@ import { getLocalizedDateFormatter, translate } from '../../base/i18n';
13 13
 import { NavigateSectionList } from '../../base/react';
14 14
 
15 15
 import { refreshCalendar, openUpdateCalendarEventDialog } from '../actions';
16
-
17 16
 import { isCalendarEnabled } from '../functions';
18 17
 
19 18
 import AddMeetingUrlButton from './AddMeetingUrlButton';
@@ -21,7 +20,7 @@ import JoinButton from './JoinButton';
21 20
 
22 21
 /**
23 22
  * The type of the React {@code Component} props of
24
- * {@link BaseCalendarList}.
23
+ * {@link CalendarListContent}.
25 24
  */
26 25
 type Props = {
27 26
 
@@ -54,7 +53,7 @@ type Props = {
54 53
 /**
55 54
  * Component to display a list of events from a connected calendar.
56 55
  */
57
-class BaseCalendarList extends Component<Props> {
56
+class CalendarListContent extends Component<Props> {
58 57
     /**
59 58
      * Default values for the component's props.
60 59
      */
@@ -63,26 +62,7 @@ class BaseCalendarList extends Component<Props> {
63 62
     };
64 63
 
65 64
     /**
66
-     * Public API method for {@code Component}s rendered in
67
-     * {@link AbstractPagedList}. When invoked, refreshes the calendar entries
68
-     * in the app.
69
-     *
70
-     * Note: It is a static method as the {@code Component} may not be
71
-     * initialized yet when the UI invokes refresh (e.g. {@link TabBarIOS} tab
72
-     * change).
73
-     *
74
-     * @param {Function} dispatch - The Redux dispatch function.
75
-     * @param {boolean} isInteractive - If true this refresh was caused by
76
-     * direct user interaction, false otherwise.
77
-     * @public
78
-     * @returns {void}
79
-     */
80
-    static refresh(dispatch, isInteractive) {
81
-        dispatch(refreshCalendar(false, isInteractive));
82
-    }
83
-
84
-    /**
85
-     * Initializes a new {@code BaseCalendarList} instance.
65
+     * Initializes a new {@code CalendarListContent} instance.
86 66
      *
87 67
      * @inheritdoc
88 68
      */
@@ -318,5 +298,5 @@ function _mapStateToProps(state: Object) {
318 298
 }
319 299
 
320 300
 export default isCalendarEnabled()
321
-    ? translate(connect(_mapStateToProps)(BaseCalendarList))
301
+    ? translate(connect(_mapStateToProps)(CalendarListContent))
322 302
     : undefined;

+ 10
- 0
react/features/recent-list/actionTypes.js View File

@@ -1,5 +1,15 @@
1 1
 // @flow
2 2
 
3
+/**
4
+ * Action type to signal the deletion of a list entry.
5
+ *
6
+ * {
7
+ *     type: DELETE_RECENT_LIST_ENTRY,
8
+ *     entryId: Object
9
+ * }
10
+ */
11
+export const DELETE_RECENT_LIST_ENTRY = Symbol('DELETE_RECENT_LIST_ENTRY');
12
+
3 13
 /**
4 14
  * Action type to signal a new addition to the list.
5 15
  *

+ 19
- 1
react/features/recent-list/actions.js View File

@@ -2,9 +2,27 @@
2 2
 
3 3
 import {
4 4
     _STORE_CURRENT_CONFERENCE,
5
-    _UPDATE_CONFERENCE_DURATION
5
+    _UPDATE_CONFERENCE_DURATION,
6
+    DELETE_RECENT_LIST_ENTRY
6 7
 } from './actionTypes';
7 8
 
9
+/**
10
+ * Deletes a recent list entry based on url and date.
11
+ *
12
+ * @param {Object} entryId - An object constructed of the url and the date of
13
+ * the entry for easy identification.
14
+ * @returns {{
15
+ *     type: DELETE_RECENT_LIST_ENTRY,
16
+ *     entryId: Object
17
+ * }}
18
+ */
19
+export function deleteRecentListEntry(entryId: Object) {
20
+    return {
21
+        type: DELETE_RECENT_LIST_ENTRY,
22
+        entryId
23
+    };
24
+}
25
+
8 26
 /**
9 27
  * Action to initiate a new addition to the list.
10 28
  *

+ 30
- 4
react/features/recent-list/components/RecentList.js View File

@@ -1,5 +1,5 @@
1 1
 // @flow
2
-import React, { Component } from 'react';
2
+import React from 'react';
3 3
 import { connect } from 'react-redux';
4 4
 
5 5
 import {
@@ -9,9 +9,15 @@ import {
9 9
 } from '../../analytics';
10 10
 import { appNavigate, getDefaultURL } from '../../app';
11 11
 import { translate } from '../../base/i18n';
12
-import { Container, NavigateSectionList, Text } from '../../base/react';
12
+import {
13
+    AbstractPage,
14
+    Container,
15
+    NavigateSectionList,
16
+    Text
17
+} from '../../base/react';
13 18
 import type { Section } from '../../base/react';
14 19
 
20
+import { deleteRecentListEntry } from '../actions';
15 21
 import { isRecentListEnabled, toDisplayableList } from '../functions';
16 22
 
17 23
 import styles from './styles';
@@ -51,7 +57,7 @@ type Props = {
51 57
  * The cross platform container rendering the list of the recently joined rooms.
52 58
  *
53 59
  */
54
-class RecentList extends Component<Props> {
60
+class RecentList extends AbstractPage<Props> {
55 61
     /**
56 62
      * Initializes a new {@code RecentList} instance.
57 63
      *
@@ -60,6 +66,7 @@ class RecentList extends Component<Props> {
60 66
     constructor(props: Props) {
61 67
         super(props);
62 68
 
69
+        this._onDelete = this._onDelete.bind(this);
63 70
         this._onPress = this._onPress.bind(this);
64 71
     }
65 72
 
@@ -85,6 +92,11 @@ class RecentList extends Component<Props> {
85 92
         }
86 93
         const { disabled, t, _defaultServerURL, _recentList } = this.props;
87 94
         const recentList = toDisplayableList(_recentList, t, _defaultServerURL);
95
+        const slideActions = [ {
96
+            backgroundColor: 'red',
97
+            onPress: this._onDelete,
98
+            text: t('welcomepage.recentListDelete')
99
+        } ];
88 100
 
89 101
         return (
90 102
             <NavigateSectionList
@@ -92,7 +104,8 @@ class RecentList extends Component<Props> {
92 104
                 onPress = { this._onPress }
93 105
                 renderListEmptyComponent
94 106
                     = { this._getRenderListEmptyComponent() }
95
-                sections = { recentList } />
107
+                sections = { recentList }
108
+                slideActions = { slideActions } />
96 109
         );
97 110
     }
98 111
 
@@ -121,6 +134,19 @@ class RecentList extends Component<Props> {
121 134
         );
122 135
     }
123 136
 
137
+    _onDelete: Object => void
138
+
139
+    /**
140
+     * Callback for the delete action of the list.
141
+     *
142
+     * @param {Object} itemId - The ID of the entry thats deletion is
143
+     * requested.
144
+     * @returns {void}
145
+     */
146
+    _onDelete(itemId) {
147
+        this.props.dispatch(deleteRecentListEntry(itemId));
148
+    }
149
+
124 150
     _onPress: string => Function;
125 151
 
126 152
     /**

+ 4
- 0
react/features/recent-list/functions.any.js View File

@@ -20,6 +20,10 @@ export function toDisplayableItem(item, defaultServerURL, t) {
20 20
 
21 21
     return {
22 22
         colorBase: serverName,
23
+        id: {
24
+            date: item.date,
25
+            url: item.conference
26
+        },
23 27
         key: `key-${item.conference}-${item.date}`,
24 28
         lines: [
25 29
             _toDateString(item.date, t),

+ 17
- 1
react/features/recent-list/reducer.js View File

@@ -6,7 +6,8 @@ import { PersistenceRegistry } from '../base/storage';
6 6
 
7 7
 import {
8 8
     _STORE_CURRENT_CONFERENCE,
9
-    _UPDATE_CONFERENCE_DURATION
9
+    _UPDATE_CONFERENCE_DURATION,
10
+    DELETE_RECENT_LIST_ENTRY
10 11
 } from './actionTypes';
11 12
 import { isRecentListEnabled } from './functions';
12 13
 
@@ -54,6 +55,8 @@ ReducerRegistry.register(
54 55
             switch (action.type) {
55 56
             case APP_WILL_MOUNT:
56 57
                 return _appWillMount(state);
58
+            case DELETE_RECENT_LIST_ENTRY:
59
+                return _deleteRecentListEntry(state, action.entryId);
57 60
             case _STORE_CURRENT_CONFERENCE:
58 61
                 return _storeCurrentConference(state, action);
59 62
 
@@ -67,6 +70,19 @@ ReducerRegistry.register(
67 70
         return state;
68 71
     });
69 72
 
73
+/**
74
+ * Deletes a recent list entry based on the url and date of the item.
75
+ *
76
+ * @param {Array<Object>} state - The Redux state.
77
+ * @param {Object} entryId - The ID object of the entry.
78
+ * @returns {Array<Object>}
79
+ */
80
+function _deleteRecentListEntry(
81
+        state: Array<Object>, entryId: Object): Array<Object> {
82
+    return state.filter(entry =>
83
+        entry.conference !== entryId.url || entry.date !== entryId.date);
84
+}
85
+
70 86
 /**
71 87
  * Reduces the redux action {@link APP_WILL_MOUNT}.
72 88
  *

+ 2
- 14
react/features/welcome/components/WelcomePageLists.js View File

@@ -1,7 +1,6 @@
1 1
 // @flow
2 2
 
3 3
 import React, { Component } from 'react';
4
-import { Platform } from 'react-native';
5 4
 import { connect } from 'react-redux';
6 5
 
7 6
 import { translate } from '../../base/i18n';
@@ -37,16 +36,6 @@ type Props = {
37 36
     t: Function
38 37
 };
39 38
 
40
-/**
41
- * Icon to be used for the calendar page on iOS.
42
- */
43
-const IOS_CALENDAR_ICON = require('../../../../images/calendar.png');
44
-
45
-/**
46
- * Icon to be used for the recent list page on iOS.
47
- */
48
-const IOS_RECENT_LIST_ICON = require('../../../../images/history.png');
49
-
50 39
 /**
51 40
  * Implements the lists displayed on the mobile welcome screen.
52 41
  */
@@ -73,17 +62,16 @@ class WelcomePageLists extends Component<Props> {
73 62
         super(props);
74 63
 
75 64
         const { t } = props;
76
-        const android = Platform.OS === 'android';
77 65
 
78 66
         this.pages = [
79 67
             {
80 68
                 component: RecentList,
81
-                icon: android ? 'restore' : IOS_RECENT_LIST_ICON,
69
+                icon: 'restore',
82 70
                 title: t('welcomepage.recentList')
83 71
             },
84 72
             {
85 73
                 component: CalendarList,
86
-                icon: android ? 'event_note' : IOS_CALENDAR_ICON,
74
+                icon: 'event_note',
87 75
                 title: t('welcomepage.calendar')
88 76
             }
89 77
         ];

Loading…
Cancel
Save