Browse Source

Hide side bar items in accordions

Arnaud Vergnet 4 years ago
parent
commit
f433edf902
2 changed files with 214 additions and 101 deletions
  1. 156
    0
      src/components/Sidebar/SideBarSection.js
  2. 58
    101
      src/components/Sidebar/Sidebar.js

+ 156
- 0
src/components/Sidebar/SideBarSection.js View File

@@ -0,0 +1,156 @@
1
+// @flow
2
+
3
+import * as React from 'react';
4
+import {FlatList} from "react-native";
5
+import {Drawer, List, withTheme} from 'react-native-paper';
6
+import {openBrowser} from "../../utils/WebBrowser";
7
+
8
+type Props = {
9
+    navigation: Object,
10
+    startOpen: boolean,
11
+    isLoggedIn: boolean,
12
+    sectionName: string,
13
+    activeRoute: string,
14
+    listKey: string,
15
+    listData: Array<Object>,
16
+}
17
+
18
+type State = {
19
+    expanded: boolean
20
+}
21
+
22
+const LIST_ITEM_HEIGHT = 48;
23
+
24
+class SideBarSection extends React.PureComponent<Props, State> {
25
+
26
+    state = {
27
+        expanded: this.props.startOpen,
28
+    };
29
+
30
+    colors: Object;
31
+    shouldExpand: boolean;
32
+
33
+    constructor(props) {
34
+        super(props);
35
+        this.colors = props.theme.colors;
36
+    }
37
+
38
+    /**
39
+     * Searches if the current route is contained in the given list data.
40
+     * If this is the case and the list is collapsed, we should expand this list.
41
+     *
42
+     * @return boolean
43
+     */
44
+    shouldExpandList() {
45
+        for (let i = 0; i < this.props.listData.length; i++) {
46
+            if (this.props.listData[i].route === this.props.activeRoute) {
47
+                return this.state.expanded === false;
48
+            }
49
+        }
50
+        return false;
51
+    }
52
+
53
+    /**
54
+     * Callback when a drawer item is pressed.
55
+     * It will either navigate to the associated screen, or open the browser to the associated link
56
+     *
57
+     * @param item The item pressed
58
+     */
59
+    onListItemPress(item: Object) {
60
+        if (item.link !== undefined)
61
+            openBrowser(item.link, this.colors.primary);
62
+        else if (item.action !== undefined)
63
+            item.action();
64
+        else
65
+            this.props.navigation.navigate(item.route);
66
+    }
67
+
68
+    /**
69
+     * Key extractor for list items
70
+     *
71
+     * @param item The item to extract the key from
72
+     * @return {string} The extracted key
73
+     */
74
+    listKeyExtractor = (item: Object) => item.route;
75
+
76
+    shouldHideItem(item: Object) {
77
+        const onlyWhenLoggedOut = item.onlyWhenLoggedOut !== undefined && item.onlyWhenLoggedOut === true;
78
+        const onlyWhenLoggedIn = item.onlyWhenLoggedIn !== undefined && item.onlyWhenLoggedIn === true;
79
+        return (onlyWhenLoggedIn && !this.props.isLoggedIn || onlyWhenLoggedOut && this.props.isLoggedIn);
80
+    }
81
+
82
+    /**
83
+     * Gets the render item for the given list item
84
+     *
85
+     * @param item The item to render
86
+     * @return {*}
87
+     */
88
+    getRenderItem = ({item}: Object) => {
89
+        const onListItemPress = this.onListItemPress.bind(this, item);
90
+        if (this.shouldHideItem(item))
91
+            return null;
92
+        return (
93
+            <Drawer.Item
94
+                label={item.name}
95
+                active={this.props.activeRoute === item.route}
96
+                icon={item.icon}
97
+                onPress={onListItemPress}
98
+                style={{
99
+                    height: LIST_ITEM_HEIGHT,
100
+                    justifyContent: 'center',
101
+                }}
102
+            />
103
+        );
104
+    };
105
+
106
+    toggleAccordion = () => {
107
+        if ((!this.state.expanded && this.shouldExpand) || !this.shouldExpand)
108
+            this.setState({expanded: !this.state.expanded})
109
+    };
110
+
111
+    shouldRenderAccordion() {
112
+        let itemsToRender = 0;
113
+        for (let i = 0; i < this.props.listData.length; i++) {
114
+            if (!this.shouldHideItem(this.props.listData[i]))
115
+                itemsToRender += 1;
116
+        }
117
+        return itemsToRender > 1;
118
+    }
119
+
120
+    itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
121
+
122
+    getFlatList() {
123
+        return (
124
+            // $FlowFixMe
125
+            <FlatList
126
+                data={this.props.listData}
127
+                extraData={this.props.isLoggedIn.toString() + this.props.activeRoute}
128
+                renderItem={this.getRenderItem}
129
+                keyExtractor={this.listKeyExtractor}
130
+                listKey={this.props.listKey}
131
+                // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
132
+                getItemLayout={this.itemLayout}
133
+            />
134
+        );
135
+    }
136
+
137
+    render() {
138
+        if (this.shouldRenderAccordion()) {
139
+            this.shouldExpand = this.shouldExpandList();
140
+            if (this.shouldExpand)
141
+                this.toggleAccordion();
142
+            return (
143
+                <List.Accordion
144
+                    title={this.props.sectionName}
145
+                    expanded={this.state.expanded}
146
+                    onPress={this.toggleAccordion}
147
+                >
148
+                    {this.getFlatList()}
149
+                </List.Accordion>
150
+            );
151
+        } else
152
+            return this.getFlatList();
153
+    }
154
+}
155
+
156
+export default withTheme(SideBarSection);

+ 58
- 101
src/components/Sidebar/Sidebar.js View File

@@ -1,15 +1,14 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {Dimensions, FlatList, Image, Platform, StyleSheet, View,} from 'react-native';
4
+import {Dimensions, FlatList, Image, StyleSheet, View,} from 'react-native';
5 5
 import i18n from "i18n-js";
6
-import {openBrowser} from "../../utils/WebBrowser";
7
-import {Drawer, TouchableRipple, withTheme} from "react-native-paper";
6
+import {TouchableRipple} from "react-native-paper";
8 7
 import ConnectionManager from "../../managers/ConnectionManager";
9 8
 import LogoutDialog from "../Amicale/LogoutDialog";
9
+import SideBarSection from "./SideBarSection";
10 10
 
11 11
 const deviceWidth = Dimensions.get("window").width;
12
-const LIST_ITEM_HEIGHT = 48;
13 12
 
14 13
 type Props = {
15 14
     navigation: Object,
@@ -30,8 +29,6 @@ class SideBar extends React.Component<Props, State> {
30 29
 
31 30
     dataSet: Array<Object>;
32 31
 
33
-    colors: Object;
34
-
35 32
     /**
36 33
      * Generate the dataset
37 34
      *
@@ -40,16 +37,14 @@ class SideBar extends React.Component<Props, State> {
40 37
     constructor(props: Props) {
41 38
         super(props);
42 39
         // Dataset used to render the drawer
43
-        this.dataSet = [
40
+        const mainData = [
44 41
             {
45 42
                 name: i18n.t('screens.home'),
46 43
                 route: "Main",
47 44
                 icon: "home",
48 45
             },
49
-            {
50
-                name: i18n.t('sidenav.divider4'),
51
-                route: "Divider4"
52
-            },
46
+        ];
47
+        const amicaleData = [
53 48
             {
54 49
                 name: i18n.t('screens.login'),
55 50
                 route: "LoginScreen",
@@ -82,10 +77,8 @@ class SideBar extends React.Component<Props, State> {
82 77
                 icon: "logout",
83 78
                 onlyWhenLoggedIn: true,
84 79
             },
85
-            {
86
-                name: i18n.t('sidenav.divider2'),
87
-                route: "Divider2"
88
-            },
80
+        ];
81
+        const servicesData = [
89 82
             {
90 83
                 name: i18n.t('screens.menuSelf'),
91 84
                 route: "SelfMenuScreen",
@@ -113,10 +106,8 @@ class SideBar extends React.Component<Props, State> {
113 106
                 link: "https://ent.insa-toulouse.fr/",
114 107
                 icon: "notebook",
115 108
             },
116
-            {
117
-                name: i18n.t('sidenav.divider1'),
118
-                route: "Divider1"
119
-            },
109
+        ];
110
+        const websitesData = [
120 111
             {
121 112
                 name: "Amicale",
122 113
                 route: "AmicaleScreen",
@@ -141,10 +132,8 @@ class SideBar extends React.Component<Props, State> {
141 132
                 link: "https://www.etud.insa-toulouse.fr/~tutorinsa/",
142 133
                 icon: "school",
143 134
             },
144
-            {
145
-                name: i18n.t('sidenav.divider3'),
146
-                route: "Divider3"
147
-            },
135
+        ];
136
+        const othersData = [
148 137
             {
149 138
                 name: i18n.t('screens.settings'),
150 139
                 route: "SettingsScreen",
@@ -156,7 +145,39 @@ class SideBar extends React.Component<Props, State> {
156 145
                 icon: "information",
157 146
             },
158 147
         ];
159
-        this.colors = props.theme.colors;
148
+
149
+        this.dataSet = [
150
+            {
151
+                key: '1',
152
+                name: i18n.t('screens.home'),
153
+                startOpen: true, // App always starts on Main
154
+                data: mainData
155
+            },
156
+            {
157
+                key: '2',
158
+                name: i18n.t('sidenav.divider4'),
159
+                startOpen: false, // TODO set by user preferences
160
+                data: amicaleData
161
+            },
162
+            {
163
+                key: '3',
164
+                name: i18n.t('sidenav.divider2'),
165
+                startOpen: false,
166
+                data: servicesData
167
+            },
168
+            {
169
+                key: '4',
170
+                name: i18n.t('sidenav.divider1'),
171
+                startOpen: false,
172
+                data: websitesData
173
+            },
174
+            {
175
+                key: '5',
176
+                name: i18n.t('sidenav.divider3'),
177
+                startOpen: false,
178
+                data: othersData
179
+            },
180
+        ];
160 181
         ConnectionManager.getInstance().addLoginStateListener(this.onLoginStateChange);
161 182
         this.props.navigation.addListener('state', this.onRouteChange);
162 183
         this.state = {
@@ -166,7 +187,7 @@ class SideBar extends React.Component<Props, State> {
166 187
         };
167 188
     }
168 189
 
169
-    onRouteChange = (event) => {
190
+    onRouteChange = (event: Object) => {
170 191
         try {
171 192
             const state = event.data.state.routes[0].state; // Get the Drawer's state if it exists
172 193
             // Get the current route name. This will only show Drawer routes.
@@ -174,7 +195,7 @@ class SideBar extends React.Component<Props, State> {
174 195
             const routeName = state.routeNames[state.index];
175 196
             if (this.state.activeRoute !== routeName)
176 197
                 this.setState({activeRoute: routeName});
177
-        } catch(e) {
198
+        } catch (e) {
178 199
             this.setState({activeRoute: 'Main'});
179 200
         }
180 201
 
@@ -184,81 +205,31 @@ class SideBar extends React.Component<Props, State> {
184 205
 
185 206
     hideDisconnectDialog = () => this.setState({dialogVisible: false});
186 207
 
187
-
188 208
     onLoginStateChange = (isLoggedIn: boolean) => this.setState({isLoggedIn: isLoggedIn});
189 209
 
190 210
     /**
191
-     * Callback when a drawer item is pressed.
192
-     * It will either navigate to the associated screen, or open the browser to the associated link
193
-     *
194
-     * @param item The item pressed
195
-     */
196
-    onListItemPress(item: Object) {
197
-        if (item.link !== undefined)
198
-            openBrowser(item.link, this.colors.primary);
199
-        else if (item.action !== undefined)
200
-            item.action();
201
-        else
202
-            this.props.navigation.navigate(item.route);
203
-    }
204
-
205
-    /**
206
-     * Key extractor for list items
207
-     *
208
-     * @param item The item to extract the key from
209
-     * @return {string} The extracted key
210
-     */
211
-    listKeyExtractor(item: Object): string {
212
-        return item.route;
213
-    }
214
-
215
-    /**
216 211
      * Gets the render item for the given list item
217 212
      *
218 213
      * @param item The item to render
219 214
      * @return {*}
220 215
      */
221 216
     getRenderItem = ({item}: Object) => {
222
-        const onListItemPress = this.onListItemPress.bind(this, item);
223
-        const onlyWhenLoggedOut = item.onlyWhenLoggedOut !== undefined && item.onlyWhenLoggedOut === true;
224
-        const onlyWhenLoggedIn = item.onlyWhenLoggedIn !== undefined && item.onlyWhenLoggedIn === true;
225
-        const shouldEmphasis = item.shouldEmphasis !== undefined && item.shouldEmphasis === true;
226
-        if (onlyWhenLoggedIn && !this.state.isLoggedIn || onlyWhenLoggedOut && this.state.isLoggedIn)
227
-            return null;
228
-        else if (item.icon !== undefined) {
229
-            return (
230
-                <Drawer.Item
231
-                    label={item.name}
232
-                    active={this.state.activeRoute === item.route}
233
-                    icon={item.icon}
234
-                    onPress={onListItemPress}
235
-                    style={{
236
-                        height: LIST_ITEM_HEIGHT,
237
-                        justifyContent: 'center',
238
-                    }}
239
-                />
240
-            );
241
-        } else {
242
-            return (
243
-                <Drawer.Item
244
-                    label={item.name}
245
-                    style={{
246
-                        height: LIST_ITEM_HEIGHT,
247
-                        justifyContent: 'center',
248
-                    }}
249
-                />
250
-            );
251
-        }
217
+        return <SideBarSection
218
+            {...this.props}
219
+            listKey={item.key}
220
+            activeRoute={this.state.activeRoute}
221
+            isLoggedIn={this.state.isLoggedIn}
222
+            sectionName={item.name}
223
+            startOpen={item.startOpen}
224
+            listData={item.data}
225
+        />
252 226
     };
253 227
 
254
-    itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
255
-
256 228
     render() {
257
-        const onPress = this.onListItemPress.bind(this, {route: 'TetrisScreen'});
258 229
         return (
259 230
             <View style={{height: '100%'}}>
260 231
                 <TouchableRipple
261
-                    onPress={onPress}
232
+                    onPress={() => this.props.navigation.navigate("TetrisScreen")}
262 233
                 >
263 234
                     <Image
264 235
                         source={require("../../../assets/drawer-cover.png")}
@@ -269,10 +240,7 @@ class SideBar extends React.Component<Props, State> {
269 240
                 <FlatList
270 241
                     data={this.dataSet}
271 242
                     extraData={this.state.isLoggedIn.toString() + this.state.activeRoute}
272
-                    keyExtractor={this.listKeyExtractor}
273 243
                     renderItem={this.getRenderItem}
274
-                    // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
275
-                    getItemLayout={this.itemLayout}
276 244
                 />
277 245
                 <LogoutDialog
278 246
                     {...this.props}
@@ -292,17 +260,6 @@ const styles = StyleSheet.create({
292 260
         marginBottom: 10,
293 261
         marginTop: 20
294 262
     },
295
-    text: {
296
-        fontWeight: Platform.OS === "ios" ? "500" : "400",
297
-        fontSize: 16,
298
-        marginLeft: 20
299
-    },
300
-    badgeText: {
301
-        fontSize: Platform.OS === "ios" ? 13 : 11,
302
-        fontWeight: "400",
303
-        textAlign: "center",
304
-        marginTop: Platform.OS === "android" ? -3 : undefined
305
-    }
306 263
 });
307 264
 
308
-export default withTheme(SideBar);
265
+export default SideBar;

Loading…
Cancel
Save