Browse Source

Create animated accordion component for reuse in the app

Arnaud Vergnet 3 years ago
parent
commit
7f24eb77ac

+ 87
- 0
src/components/Animations/AnimatedAccordion.js View File

@@ -0,0 +1,87 @@
1
+// @flow
2
+
3
+import * as React from 'react';
4
+import {View} from "react-native";
5
+import {List, withTheme} from 'react-native-paper';
6
+import Collapsible from "react-native-collapsible";
7
+import * as Animatable from "react-native-animatable";
8
+import type {CustomTheme} from "../../managers/ThemeManager";
9
+
10
+type Props = {
11
+    theme: CustomTheme,
12
+    title: string,
13
+    subtitle?: string,
14
+    left?: (props: { [keys: string]: any }) =>  React.Node,
15
+    startOpen: boolean,
16
+    keepOpen: boolean,
17
+    unmountWhenCollapsed: boolean,
18
+    children?: React.Node,
19
+}
20
+
21
+type State = {
22
+    expanded: boolean
23
+}
24
+
25
+const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
26
+
27
+class AnimatedAccordion extends React.PureComponent<Props, State> {
28
+
29
+    static defaultProps = {
30
+        startOpen: false,
31
+        keepOpen: false,
32
+        unmountWhenCollapsed: false,
33
+    }
34
+    chevronRef: { current: null | AnimatedListIcon };
35
+    state = {
36
+        expanded: false,
37
+    }
38
+
39
+    constructor(props) {
40
+        super(props);
41
+        this.chevronRef = React.createRef();
42
+    }
43
+
44
+    componentDidMount() {
45
+        if (this.props.startOpen)
46
+            this.toggleAccordion();
47
+    }
48
+
49
+    toggleAccordion = () => {
50
+        if (!this.props.keepOpen) {
51
+            if (this.chevronRef.current != null)
52
+                this.chevronRef.current.transitionTo({rotate: this.state.expanded ? '0deg' : '180deg'});
53
+            this.setState({expanded: !this.state.expanded})
54
+        }
55
+    };
56
+
57
+    render() {
58
+        const colors = this.props.theme.colors;
59
+        return (
60
+            <View>
61
+                <List.Item
62
+                    {...this.props}
63
+                    title={this.props.title}
64
+                    subtitle={this.props.subtitle}
65
+                    titleStyle={this.state.expanded ? {color: colors.primary} : undefined}
66
+                    onPress={this.toggleAccordion}
67
+                    right={(props) => <AnimatedListIcon
68
+                        ref={this.chevronRef}
69
+                        {...props}
70
+                        icon={"chevron-down"}
71
+                        color={this.state.expanded ? colors.primary : undefined}
72
+                        useNativeDriver
73
+                    />}
74
+                    left={this.props.left}
75
+                />
76
+                <Collapsible collapsed={!this.props.keepOpen && !this.state.expanded}>
77
+                    {!this.props.unmountWhenCollapsed || (this.props.unmountWhenCollapsed && this.state.expanded)
78
+                        ? this.props.children
79
+                        : null}
80
+                </Collapsible>
81
+            </View>
82
+        );
83
+    }
84
+
85
+}
86
+
87
+export default withTheme(AnimatedAccordion);

+ 5
- 14
src/components/Lists/Clubs/ClubListHeader.js View File

@@ -4,21 +4,15 @@ import * as React from 'react';
4 4
 import {Card, List, Text, withTheme} from 'react-native-paper';
5 5
 import {StyleSheet, View} from "react-native";
6 6
 import i18n from 'i18n-js';
7
+import AnimatedAccordion from "../../Animations/AnimatedAccordion";
7 8
 
8 9
 type Props = {
9 10
     categoryRender: Function,
10 11
     categories: Array<Object>,
11 12
 }
12 13
 
13
-type State = {
14
-    expanded: boolean,
15
-}
16
-
17
-class ClubListHeader extends React.Component<Props, State> {
14
+class ClubListHeader extends React.Component<Props> {
18 15
 
19
-    state = {
20
-        expanded: true
21
-    };
22 16
 
23 17
     colors: Object;
24 18
 
@@ -35,22 +29,19 @@ class ClubListHeader extends React.Component<Props, State> {
35 29
         return final;
36 30
     }
37 31
 
38
-    onPress = () => this.setState({expanded: !this.state.expanded});
39
-
40 32
     render() {
41 33
         return (
42 34
             <Card style={styles.card}>
43
-                <List.Accordion
35
+                <AnimatedAccordion
44 36
                     title={i18n.t("clubs.categories")}
45 37
                     left={props => <List.Icon {...props} icon="star"/>}
46
-                    expanded={this.state.expanded}
47
-                    onPress={this.onPress}
38
+                    startOpen={true}
48 39
                 >
49 40
                     <Text style={styles.text}>{i18n.t("clubs.categoriesFilterMessage")}</Text>
50 41
                     <View style={styles.chipContainer}>
51 42
                         {this.getCategoriesRender()}
52 43
                     </View>
53
-                </List.Accordion>
44
+                </AnimatedAccordion>
54 45
             </Card>
55 46
         );
56 47
     }

+ 14
- 41
src/components/Lists/PlanexGroups/GroupListAccordion.js View File

@@ -4,9 +4,9 @@ import * as React from 'react';
4 4
 import {List, withTheme} from 'react-native-paper';
5 5
 import {FlatList, View} from "react-native";
6 6
 import {stringMatchQuery} from "../../../utils/Search";
7
-import Collapsible from "react-native-collapsible";
8 7
 import * as Animatable from "react-native-animatable";
9 8
 import GroupListItem from "./GroupListItem";
9
+import AnimatedAccordion from "../../Animations/AnimatedAccordion";
10 10
 
11 11
 type Props = {
12 12
     item: Object,
@@ -47,11 +47,6 @@ class GroupListAccordion extends React.Component<Props, State> {
47 47
             || (nextProps.item.content.length !== this.props.item.content.length);
48 48
     }
49 49
 
50
-    onPress = () => {
51
-        this.chevronRef.current.transitionTo({rotate: this.state.expanded ? '0deg' : '180deg'});
52
-        this.setState({expanded: !this.state.expanded})
53
-    };
54
-
55 50
     keyExtractor = (item: Object) => item.id.toString();
56 51
 
57 52
     renderItem = ({item}: Object) => {
@@ -73,20 +68,14 @@ class GroupListAccordion extends React.Component<Props, State> {
73 68
 
74 69
     render() {
75 70
         const item = this.props.item;
76
-        const accordionColor = this.state.expanded
77
-            ? this.props.theme.colors.primary
78
-            : this.props.theme.colors.text;
79
-        // console.log(item.id);
80 71
         return (
81 72
             <View>
82
-                <List.Item
73
+                <AnimatedAccordion
83 74
                     title={item.name}
84
-                    onPress={this.onPress}
85 75
                     style={{
86 76
                         height: this.props.height,
87 77
                         justifyContent: 'center',
88 78
                     }}
89
-                    titleStyle={{color: accordionColor}}
90 79
                     left={props =>
91 80
                         item.id === "0"
92 81
                             ? <List.Icon
@@ -95,35 +84,19 @@ class GroupListAccordion extends React.Component<Props, State> {
95 84
                                 color={this.props.theme.colors.tetrisScore}
96 85
                             />
97 86
                             : null}
98
-                    right={(props) => <AnimatedListIcon
99
-                        ref={this.chevronRef}
100
-                        {...props}
101
-                        icon={"chevron-down"}
102
-                        color={this.state.expanded
103
-                            ? this.props.theme.colors.primary
104
-                            : props.color
105
-                        }
106
-                        useNativeDriver
107
-                    />}
108
-                />
109
-                <Collapsible
110
-                    collapsed={!this.state.expanded}
111
-                    ease={"easeInOut"}
87
+                    unmountWhenCollapsed={true}// Only render list if expanded for increased performance
112 88
                 >
113
-                    {this.state.expanded // Only render list if expanded for increased performance
114
-                        ? <FlatList
115
-                            data={item.content}
116
-                            extraData={this.props.currentSearchString}
117
-                            renderItem={this.renderItem}
118
-                            keyExtractor={this.keyExtractor}
119
-                            listKey={item.id}
120
-                            // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
121
-                            getItemLayout={this.itemLayout} // Broken with search
122
-                            removeClippedSubviews={true}
123
-                        />
124
-                        : null}
125
-
126
-                </Collapsible>
89
+                    <FlatList
90
+                        data={item.content}
91
+                        extraData={this.props.currentSearchString}
92
+                        renderItem={this.renderItem}
93
+                        keyExtractor={this.keyExtractor}
94
+                        listKey={item.id}
95
+                        // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
96
+                        getItemLayout={this.itemLayout} // Broken with search
97
+                        removeClippedSubviews={true}
98
+                    />
99
+                </AnimatedAccordion>
127 100
             </View>
128 101
         );
129 102
     }

+ 13
- 45
src/components/Sidebar/SideBarSection.js View File

@@ -1,11 +1,10 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {FlatList, View} from "react-native";
5
-import {Drawer, List, withTheme} from 'react-native-paper';
4
+import {FlatList} from "react-native";
5
+import {Drawer, withTheme} from 'react-native-paper';
6 6
 import {Linking} from "expo";
7
-import Collapsible from "react-native-collapsible";
8
-import * as Animatable from "react-native-animatable";
7
+import AnimatedAccordion from "../Animations/AnimatedAccordion";
9 8
 
10 9
 type Props = {
11 10
     navigation: Object,
@@ -17,28 +16,17 @@ type Props = {
17 16
     listData: Array<Object>,
18 17
 }
19 18
 
20
-type State = {
21
-    expanded: boolean
22
-}
23
-
24
-const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
25
-
26 19
 const LIST_ITEM_HEIGHT = 48;
27 20
 
28
-class SideBarSection extends React.PureComponent<Props, State> {
29
-
30
-    state = {
31
-        expanded: this.props.startOpen,
32
-    };
21
+class SideBarSection extends React.PureComponent<Props> {
33 22
 
34 23
     colors: Object;
35
-    shouldExpand: boolean;
36
-    chevronRef: Object;
24
+    accordionRef: {current: null | AnimatedAccordion};
37 25
 
38 26
     constructor(props) {
39 27
         super(props);
40 28
         this.colors = props.theme.colors;
41
-        this.chevronRef = React.createRef();
29
+        this.accordionRef = React.createRef();
42 30
     }
43 31
 
44 32
     /**
@@ -50,7 +38,7 @@ class SideBarSection extends React.PureComponent<Props, State> {
50 38
     shouldExpandList() {
51 39
         for (let i = 0; i < this.props.listData.length; i++) {
52 40
             if (this.props.listData[i].route === this.props.activeRoute) {
53
-                return this.state.expanded === false;
41
+                return true;
54 42
             }
55 43
         }
56 44
         return false;
@@ -109,13 +97,6 @@ class SideBarSection extends React.PureComponent<Props, State> {
109 97
         );
110 98
     };
111 99
 
112
-    toggleAccordion = () => {
113
-        if ((!this.state.expanded && this.shouldExpand) || !this.shouldExpand) {
114
-            this.chevronRef.current.transitionTo({ rotate: this.state.expanded ? '0deg' : '180deg' });
115
-            this.setState({expanded: !this.state.expanded})
116
-        }
117
-    };
118
-
119 100
     shouldRenderAccordion() {
120 101
         let itemsToRender = 0;
121 102
         for (let i = 0; i < this.props.listData.length; i++) {
@@ -144,26 +125,13 @@ class SideBarSection extends React.PureComponent<Props, State> {
144 125
 
145 126
     render() {
146 127
         if (this.shouldRenderAccordion()) {
147
-            this.shouldExpand = this.shouldExpandList();
148
-            if (this.shouldExpand)
149
-                this.toggleAccordion();
150 128
             return (
151
-                <View>
152
-                    <List.Item
153
-                        title={this.props.sectionName}
154
-                        // expanded={this.state.expanded}
155
-                        onPress={this.toggleAccordion}
156
-                        right={(props) => <AnimatedListIcon
157
-                            ref={this.chevronRef}
158
-                            {...props}
159
-                            icon={"chevron-down"}
160
-                            useNativeDriver
161
-                        />}
162
-                    />
163
-                    <Collapsible collapsed={!this.state.expanded}>
164
-                        {this.getFlatList()}
165
-                    </Collapsible>
166
-                </View>
129
+                <AnimatedAccordion
130
+                    title={this.props.sectionName}
131
+                    keepOpen={this.shouldExpandList()}
132
+                >
133
+                    {this.getFlatList()}
134
+                </AnimatedAccordion>
167 135
             );
168 136
         } else
169 137
             return this.getFlatList();

+ 8
- 5
src/screens/Other/SettingsScreen.js View File

@@ -8,6 +8,7 @@ import AsyncStorageManager from "../../managers/AsyncStorageManager";
8 8
 import {setMachineReminderNotificationTime} from "../../utils/Notifications";
9 9
 import {Card, List, Switch, ToggleButton} from 'react-native-paper';
10 10
 import {Appearance} from "react-native-appearance";
11
+import AnimatedAccordion from "../../components/Animations/AnimatedAccordion";
11 12
 
12 13
 type Props = {
13 14
     navigation: Object,
@@ -89,6 +90,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
89 90
             <ToggleButton.Row
90 91
                 onValueChange={this.onProxiwashNotifPickerValueChange}
91 92
                 value={this.state.proxiwashNotifPickerSelected}
93
+                style={{marginLeft: 'auto', marginRight: 'auto'}}
92 94
             >
93 95
                 <ToggleButton icon="close" value="never"/>
94 96
                 <ToggleButton icon="numeric-2" value="2"/>
@@ -107,6 +109,7 @@ export default class SettingsScreen extends React.Component<Props, State> {
107 109
             <ToggleButton.Row
108 110
                 onValueChange={this.onStartScreenPickerValueChange}
109 111
                 value={this.state.startScreenPickerSelected}
112
+                style={{marginLeft: 'auto', marginRight: 'auto'}}
110 113
             >
111 114
                 <ToggleButton icon="shopping" value="proximo"/>
112 115
                 <ToggleButton icon="calendar-range" value="planning"/>
@@ -188,25 +191,25 @@ export default class SettingsScreen extends React.Component<Props, State> {
188 191
                                     this.state.nightMode
189 192
                                 ) : null
190 193
                         }
191
-                        <List.Accordion
194
+                        <AnimatedAccordion
192 195
                             title={i18n.t('settingsScreen.startScreen')}
193
-                            description={i18n.t('settingsScreen.startScreenSub')}
196
+                            subtitle={i18n.t('settingsScreen.startScreenSub')}
194 197
                             left={props => <List.Icon {...props} icon="power"/>}
195 198
                         >
196 199
                             {this.getStartScreenPicker()}
197
-                        </List.Accordion>
200
+                        </AnimatedAccordion>
198 201
                     </List.Section>
199 202
                 </Card>
200 203
                 <Card style={{margin: 5}}>
201 204
                     <Card.Title title="Proxiwash"/>
202 205
                     <List.Section>
203
-                        <List.Accordion
206
+                        <AnimatedAccordion
204 207
                             title={i18n.t('settingsScreen.proxiwashNotifReminder')}
205 208
                             description={i18n.t('settingsScreen.proxiwashNotifReminderSub')}
206 209
                             left={props => <List.Icon {...props} icon="washing-machine"/>}
207 210
                         >
208 211
                             {this.getProxiwashNotifPicker()}
209
-                        </List.Accordion>
212
+                        </AnimatedAccordion>
210 213
                     </List.Section>
211 214
                 </Card>
212 215
             </ScrollView>

Loading…
Cancel
Save