Browse Source

update react native collapsible

Arnaud Vergnet 2 years ago
parent
commit
286c1e6411

+ 24
- 20
App.tsx View File

37
 import initLocales from './src/utils/Locales';
37
 import initLocales from './src/utils/Locales';
38
 import { NavigationContainerRef } from '@react-navigation/core';
38
 import { NavigationContainerRef } from '@react-navigation/core';
39
 import GENERAL_STYLES from './src/constants/Styles';
39
 import GENERAL_STYLES from './src/constants/Styles';
40
+import CollapsibleProvider from './src/components/providers/CollapsibleProvider';
40
 
41
 
41
 // Native optimizations https://reactnavigation.org/docs/react-native-screens
42
 // Native optimizations https://reactnavigation.org/docs/react-native-screens
42
 // Crashes app when navigating away from webview on android 9+
43
 // Crashes app when navigating away from webview on android 9+
210
     }
211
     }
211
     return (
212
     return (
212
       <PaperProvider theme={state.currentTheme}>
213
       <PaperProvider theme={state.currentTheme}>
213
-        <OverflowMenuProvider>
214
-          <View
215
-            style={{
216
-              backgroundColor: ThemeManager.getCurrentTheme().colors.background,
217
-              ...GENERAL_STYLES.flex,
218
-            }}
219
-          >
220
-            <SafeAreaView style={GENERAL_STYLES.flex}>
221
-              <NavigationContainer
222
-                theme={state.currentTheme}
223
-                ref={this.navigatorRef}
224
-              >
225
-                <MainNavigator
226
-                  defaultHomeRoute={this.defaultHomeRoute}
227
-                  defaultHomeData={this.defaultHomeData}
228
-                />
229
-              </NavigationContainer>
230
-            </SafeAreaView>
231
-          </View>
232
-        </OverflowMenuProvider>
214
+        <CollapsibleProvider>
215
+          <OverflowMenuProvider>
216
+            <View
217
+              style={{
218
+                backgroundColor: ThemeManager.getCurrentTheme().colors
219
+                  .background,
220
+                ...GENERAL_STYLES.flex,
221
+              }}
222
+            >
223
+              <SafeAreaView style={GENERAL_STYLES.flex}>
224
+                <NavigationContainer
225
+                  theme={state.currentTheme}
226
+                  ref={this.navigatorRef}
227
+                >
228
+                  <MainNavigator
229
+                    defaultHomeRoute={this.defaultHomeRoute}
230
+                    defaultHomeData={this.defaultHomeData}
231
+                  />
232
+                </NavigationContainer>
233
+              </SafeAreaView>
234
+            </View>
235
+          </OverflowMenuProvider>
236
+        </CollapsibleProvider>
233
       </PaperProvider>
237
       </PaperProvider>
234
     );
238
     );
235
   }
239
   }

+ 38
- 8
package-lock.json View File

3931
       "version": "3.1.4",
3931
       "version": "3.1.4",
3932
       "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
3932
       "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
3933
       "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
3933
       "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
3934
-      "dev": true,
3935
       "requires": {
3934
       "requires": {
3936
         "node-fetch": "2.6.1"
3935
         "node-fetch": "2.6.1"
3937
       }
3936
       }
10596
       "integrity": "sha512-beZjdgbT9Y/Pg591Xy5XkKG20HffJiVad4n9bfcUF/f783A+tvOVXnqvbS58Lkaym93mi4jcDPMuW9Vc1t6rqg=="
10595
       "integrity": "sha512-beZjdgbT9Y/Pg591Xy5XkKG20HffJiVad4n9bfcUF/f783A+tvOVXnqvbS58Lkaym93mi4jcDPMuW9Vc1t6rqg=="
10597
     },
10596
     },
10598
     "react-native-gesture-handler": {
10597
     "react-native-gesture-handler": {
10599
-      "version": "1.8.0",
10600
-      "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.8.0.tgz",
10601
-      "integrity": "sha512-E2FZa0qZ5Bi0Z8Jg4n9DaFomHvedSjwbO2DPmUUHYRy1lH2yxXUpSrqJd6yymu+Efzmjg2+JZzsjFYA2Iq8VEQ==",
10598
+      "version": "1.10.3",
10599
+      "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",
10600
+      "integrity": "sha512-cBGMi1IEsIVMgoox4RvMx7V2r6bNKw0uR1Mu1o7NbuHS6BRSVLq0dP34l2ecnPlC+jpWd3le6Yg1nrdCjby2Mw==",
10602
       "requires": {
10601
       "requires": {
10603
         "@egjs/hammerjs": "^2.0.17",
10602
         "@egjs/hammerjs": "^2.0.17",
10603
+        "fbjs": "^3.0.0",
10604
         "hoist-non-react-statics": "^3.3.0",
10604
         "hoist-non-react-statics": "^3.3.0",
10605
         "invariant": "^2.2.4",
10605
         "invariant": "^2.2.4",
10606
         "prop-types": "^15.7.2"
10606
         "prop-types": "^15.7.2"
10607
+      },
10608
+      "dependencies": {
10609
+        "fbjs": {
10610
+          "version": "3.0.0",
10611
+          "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.0.tgz",
10612
+          "integrity": "sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg==",
10613
+          "requires": {
10614
+            "cross-fetch": "^3.0.4",
10615
+            "fbjs-css-vars": "^1.0.0",
10616
+            "loose-envify": "^1.0.0",
10617
+            "object-assign": "^4.1.0",
10618
+            "promise": "^7.1.1",
10619
+            "setimmediate": "^1.0.5",
10620
+            "ua-parser-js": "^0.7.18"
10621
+          }
10622
+        },
10623
+        "promise": {
10624
+          "version": "7.3.1",
10625
+          "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
10626
+          "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
10627
+          "requires": {
10628
+            "asap": "~2.0.3"
10629
+          }
10630
+        }
10607
       }
10631
       }
10608
     },
10632
     },
10609
     "react-native-image-pan-zoom": {
10633
     "react-native-image-pan-zoom": {
10871
       }
10895
       }
10872
     },
10896
     },
10873
     "react-navigation-collapsible": {
10897
     "react-navigation-collapsible": {
10874
-      "version": "5.6.4",
10875
-      "resolved": "https://registry.npmjs.org/react-navigation-collapsible/-/react-navigation-collapsible-5.6.4.tgz",
10876
-      "integrity": "sha512-dXMbDw2TQ6s5XLk9h+2hUShXoS8KPChfdh/xmmLqfKmntS5YteE01+x78gU5KogB3etDraH1kvhW7xDnbG9AfA==",
10898
+      "version": "5.9.1",
10899
+      "resolved": "https://registry.npmjs.org/react-navigation-collapsible/-/react-navigation-collapsible-5.9.1.tgz",
10900
+      "integrity": "sha512-yUwHe8Z7++A8ThrjPI+Mcm7LqBhIqJc+1F4XszpI7EoHz3bJElzczbfyfuEvjSbYU9AgW3MdBWzaRIDluxcEuA==",
10877
       "requires": {
10901
       "requires": {
10878
-        "react-native-iphone-x-helper": "^1.2.1"
10902
+        "react-native-iphone-x-helper": "^1.3.0",
10903
+        "shallowequal": "^1.1.0"
10879
       }
10904
       }
10880
     },
10905
     },
10881
     "react-navigation-header-buttons": {
10906
     "react-navigation-header-buttons": {
11453
         "kind-of": "^6.0.2"
11478
         "kind-of": "^6.0.2"
11454
       }
11479
       }
11455
     },
11480
     },
11481
+    "shallowequal": {
11482
+      "version": "1.1.0",
11483
+      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
11484
+      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
11485
+    },
11456
     "shebang-command": {
11486
     "shebang-command": {
11457
       "version": "1.2.0",
11487
       "version": "1.2.0",
11458
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
11488
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",

+ 1
- 1
package.json View File

53
     "react-native-splash-screen": "3.2.0",
53
     "react-native-splash-screen": "3.2.0",
54
     "react-native-vector-icons": "8.1.0",
54
     "react-native-vector-icons": "8.1.0",
55
     "react-native-webview": "11.4.3",
55
     "react-native-webview": "11.4.3",
56
-    "react-navigation-collapsible": "5.6.4",
56
+    "react-navigation-collapsible": "5.9.1",
57
     "react-navigation-header-buttons": "7.0.1"
57
     "react-navigation-header-buttons": "7.0.1"
58
   },
58
   },
59
   "devDependencies": {
59
   "devDependencies": {

+ 2
- 2
src/components/Animations/AnimatedBottomBar.tsx View File

28
 import * as Animatable from 'react-native-animatable';
28
 import * as Animatable from 'react-native-animatable';
29
 import { StackNavigationProp } from '@react-navigation/stack';
29
 import { StackNavigationProp } from '@react-navigation/stack';
30
 import AutoHideHandler from '../../utils/AutoHideHandler';
30
 import AutoHideHandler from '../../utils/AutoHideHandler';
31
-import CustomTabBar from '../Tabbar/CustomTabBar';
31
+import CustomTabBar, { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
32
 
32
 
33
 const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
33
 const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
34
 
34
 
159
         useNativeDriver
159
         useNativeDriver
160
         style={{
160
         style={{
161
           ...styles.container,
161
           ...styles.container,
162
-          bottom: 10 + CustomTabBar.TAB_BAR_HEIGHT,
162
+          bottom: 10 + TAB_BAR_HEIGHT,
163
         }}
163
         }}
164
       >
164
       >
165
         <Surface style={styles.surface}>
165
         <Surface style={styles.surface}>

+ 2
- 2
src/components/Animations/AnimatedFAB.tsx View File

27
 import { FAB } from 'react-native-paper';
27
 import { FAB } from 'react-native-paper';
28
 import * as Animatable from 'react-native-animatable';
28
 import * as Animatable from 'react-native-animatable';
29
 import AutoHideHandler from '../../utils/AutoHideHandler';
29
 import AutoHideHandler from '../../utils/AutoHideHandler';
30
-import CustomTabBar from '../Tabbar/CustomTabBar';
30
+import CustomTabBar, { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
31
 
31
 
32
 type PropsType = {
32
 type PropsType = {
33
   icon: string;
33
   icon: string;
82
         useNativeDriver={true}
82
         useNativeDriver={true}
83
         style={{
83
         style={{
84
           ...styles.fab,
84
           ...styles.fab,
85
-          bottom: CustomTabBar.TAB_BAR_HEIGHT,
85
+          bottom: TAB_BAR_HEIGHT,
86
         }}
86
         }}
87
       >
87
       >
88
         <FAB icon={props.icon} onPress={props.onPress} />
88
         <FAB icon={props.icon} onPress={props.onPress} />

+ 41
- 12
src/components/Collapsible/CollapsibleComponent.tsx View File

17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
  */
18
  */
19
 
19
 
20
-import * as React from 'react';
21
-import { useCollapsibleStack } from 'react-navigation-collapsible';
22
-import CustomTabBar from '../Tabbar/CustomTabBar';
20
+import React, { useCallback } from 'react';
21
+import { useCollapsibleHeader } from 'react-navigation-collapsible';
22
+import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
23
 import {
23
 import {
24
   NativeScrollEvent,
24
   NativeScrollEvent,
25
   NativeSyntheticEvent,
25
   NativeSyntheticEvent,
26
   StyleSheet,
26
   StyleSheet,
27
 } from 'react-native';
27
 } from 'react-native';
28
+import { useTheme } from 'react-native-paper';
29
+import { useCollapsible } from '../../utils/CollapsibleContext';
30
+import { useFocusEffect } from '@react-navigation/core';
28
 
31
 
29
 export type CollapsibleComponentPropsType = {
32
 export type CollapsibleComponentPropsType = {
30
   children?: React.ReactNode;
33
   children?: React.ReactNode;
31
   hasTab?: boolean;
34
   hasTab?: boolean;
32
   onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
35
   onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
36
+  paddedProps?: (paddingTop: number) => Record<string, any>;
37
+  headerColors: string;
33
 };
38
 };
34
 
39
 
35
-type PropsType = CollapsibleComponentPropsType & {
40
+type Props = CollapsibleComponentPropsType & {
36
   component: React.ComponentType<any>;
41
   component: React.ComponentType<any>;
37
 };
42
 };
38
 
43
 
42
   },
47
   },
43
 });
48
 });
44
 
49
 
45
-function CollapsibleComponent(props: PropsType) {
46
-  const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
47
-    if (props.onScroll) {
48
-      props.onScroll(event);
49
-    }
50
-  };
50
+function CollapsibleComponent(props: Props) {
51
+  const { paddedProps, headerColors } = props;
51
   const Comp = props.component;
52
   const Comp = props.component;
53
+  const theme = useTheme();
54
+  const { setCollapsible } = useCollapsible();
55
+
56
+  const collapsible = useCollapsibleHeader({
57
+    config: {
58
+      collapsedColor: headerColors ? headerColors : theme.colors.surface,
59
+      useNativeDriver: true,
60
+    },
61
+  });
62
+
63
+  useFocusEffect(
64
+    useCallback(() => {
65
+      setCollapsible(collapsible);
66
+    }, [collapsible, setCollapsible])
67
+  );
68
+
52
   const {
69
   const {
53
     containerPaddingTop,
70
     containerPaddingTop,
54
     scrollIndicatorInsetTop,
71
     scrollIndicatorInsetTop,
55
     onScrollWithListener,
72
     onScrollWithListener,
56
-  } = useCollapsibleStack();
57
-  const paddingBottom = props.hasTab ? CustomTabBar.TAB_BAR_HEIGHT : 0;
73
+  } = collapsible;
74
+
75
+  const paddingBottom = props.hasTab ? TAB_BAR_HEIGHT : 0;
76
+
77
+  const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
78
+    if (props.onScroll) {
79
+      props.onScroll(event);
80
+    }
81
+  };
82
+
83
+  const pprops =
84
+    paddedProps !== undefined ? paddedProps(containerPaddingTop) : undefined;
85
+
58
   return (
86
   return (
59
     <Comp
87
     <Comp
60
       {...props}
88
       {...props}
89
+      {...pprops}
61
       onScroll={onScrollWithListener(onScroll)}
90
       onScroll={onScrollWithListener(onScroll)}
62
       contentContainerStyle={{
91
       contentContainerStyle={{
63
         paddingTop: containerPaddingTop,
92
         paddingTop: containerPaddingTop,

+ 2
- 2
src/components/Overrides/CustomModal.tsx View File

21
 import { useTheme } from 'react-native-paper';
21
 import { useTheme } from 'react-native-paper';
22
 import { Modalize } from 'react-native-modalize';
22
 import { Modalize } from 'react-native-modalize';
23
 import { View } from 'react-native-animatable';
23
 import { View } from 'react-native-animatable';
24
-import CustomTabBar from '../Tabbar/CustomTabBar';
24
+import CustomTabBar, { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
25
 
25
 
26
 /**
26
 /**
27
  * Abstraction layer for Modalize component, using custom configuration
27
  * Abstraction layer for Modalize component, using custom configuration
45
     >
45
     >
46
       <View
46
       <View
47
         style={{
47
         style={{
48
-          paddingBottom: CustomTabBar.TAB_BAR_HEIGHT,
48
+          paddingBottom: TAB_BAR_HEIGHT,
49
         }}
49
         }}
50
       >
50
       >
51
         {children}
51
         {children}

+ 15
- 14
src/components/Screens/WebSectionList.tsx View File

32
 import { StackNavigationProp } from '@react-navigation/stack';
32
 import { StackNavigationProp } from '@react-navigation/stack';
33
 import ErrorView from './ErrorView';
33
 import ErrorView from './ErrorView';
34
 import BasicLoadingScreen from './BasicLoadingScreen';
34
 import BasicLoadingScreen from './BasicLoadingScreen';
35
-import withCollapsible from '../../utils/withCollapsible';
36
-import CustomTabBar from '../Tabbar/CustomTabBar';
35
+import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
37
 import { ERROR_TYPE, readData } from '../../utils/WebData';
36
 import { ERROR_TYPE, readData } from '../../utils/WebData';
38
 import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
37
 import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
38
+import GENERAL_STYLES from '../../constants/Styles';
39
 
39
 
40
 export type SectionListDataType<ItemT> = Array<{
40
 export type SectionListDataType<ItemT> = Array<{
41
   title: string;
41
   title: string;
260
       dataset = props.createDataset(state.fetchedData, state.refreshing);
260
       dataset = props.createDataset(state.fetchedData, state.refreshing);
261
     }
261
     }
262
 
262
 
263
-    const { containerPaddingTop } = props.collapsibleStack;
264
     return (
263
     return (
265
-      <View>
264
+      <View style={GENERAL_STYLES.flex}>
266
         <CollapsibleSectionList
265
         <CollapsibleSectionList
267
           sections={dataset}
266
           sections={dataset}
268
           extraData={props.updateData}
267
           extraData={props.updateData}
269
-          refreshControl={
270
-            <RefreshControl
271
-              progressViewOffset={containerPaddingTop}
272
-              refreshing={state.refreshing}
273
-              onRefresh={this.onRefresh}
274
-            />
275
-          }
268
+          paddedProps={(paddingTop) => ({
269
+            refreshControl: (
270
+              <RefreshControl
271
+                progressViewOffset={paddingTop}
272
+                refreshing={state.refreshing}
273
+                onRefresh={this.onRefresh}
274
+              />
275
+            ),
276
+          })}
276
           renderSectionHeader={this.getRenderSectionHeader}
277
           renderSectionHeader={this.getRenderSectionHeader}
277
           renderItem={this.getRenderItem}
278
           renderItem={this.getRenderItem}
278
           stickySectionHeadersEnabled={props.stickyHeader}
279
           stickySectionHeadersEnabled={props.stickyHeader}
299
               : undefined
300
               : undefined
300
           }
301
           }
301
           onScroll={this.onScroll}
302
           onScroll={this.onScroll}
302
-          hasTab
303
+          hasTab={true}
303
         />
304
         />
304
         <Snackbar
305
         <Snackbar
305
           visible={state.snackbarVisible}
306
           visible={state.snackbarVisible}
310
           }}
311
           }}
311
           duration={4000}
312
           duration={4000}
312
           style={{
313
           style={{
313
-            bottom: CustomTabBar.TAB_BAR_HEIGHT,
314
+            bottom: TAB_BAR_HEIGHT,
314
           }}
315
           }}
315
         >
316
         >
316
           {i18n.t('general.listUpdateFail')}
317
           {i18n.t('general.listUpdateFail')}
320
   }
321
   }
321
 }
322
 }
322
 
323
 
323
-export default withCollapsible(WebSectionList);
324
+export default WebSectionList;

+ 124
- 154
src/components/Screens/WebViewScreen.tsx View File

17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
  */
18
  */
19
 
19
 
20
-import * as React from 'react';
20
+import React, {
21
+  useCallback,
22
+  useEffect,
23
+  useLayoutEffect,
24
+  useRef,
25
+  useState,
26
+} from 'react';
21
 import WebView from 'react-native-webview';
27
 import WebView from 'react-native-webview';
22
 import {
28
 import {
23
   Divider,
29
   Divider,
34
   StyleSheet,
40
   StyleSheet,
35
 } from 'react-native';
41
 } from 'react-native';
36
 import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
42
 import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
37
-import { withTheme } from 'react-native-paper';
38
-import { StackNavigationProp } from '@react-navigation/stack';
39
-import { Collapsible } from 'react-navigation-collapsible';
40
-import withCollapsible from '../../utils/withCollapsible';
43
+import { useTheme } from 'react-native-paper';
44
+import { useCollapsibleHeader } from 'react-navigation-collapsible';
41
 import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton';
45
 import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton';
42
 import { ERROR_TYPE } from '../../utils/WebData';
46
 import { ERROR_TYPE } from '../../utils/WebData';
43
 import ErrorView from './ErrorView';
47
 import ErrorView from './ErrorView';
44
 import BasicLoadingScreen from './BasicLoadingScreen';
48
 import BasicLoadingScreen from './BasicLoadingScreen';
49
+import { useFocusEffect, useNavigation } from '@react-navigation/core';
50
+import { useCollapsible } from '../../utils/CollapsibleContext';
45
 
51
 
46
-type PropsType = {
47
-  navigation: StackNavigationProp<any>;
48
-  theme: ReactNativePaper.Theme;
52
+type Props = {
49
   url: string;
53
   url: string;
50
-  collapsibleStack: Collapsible;
51
-  onMessage: (event: { nativeEvent: { data: string } }) => void;
52
-  onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
53
-  customJS?: string;
54
+  onMessage?: (event: { nativeEvent: { data: string } }) => void;
55
+  onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
56
+  initialJS?: string;
57
+  injectJS?: string;
54
   customPaddingFunction?: null | ((padding: number) => string);
58
   customPaddingFunction?: null | ((padding: number) => string);
55
   showAdvancedControls?: boolean;
59
   showAdvancedControls?: boolean;
56
 };
60
 };
66
 /**
70
 /**
67
  * Class defining a webview screen.
71
  * Class defining a webview screen.
68
  */
72
  */
69
-class WebViewScreen extends React.PureComponent<PropsType> {
70
-  static defaultProps = {
71
-    customJS: '',
72
-    showAdvancedControls: true,
73
-    customPaddingFunction: null,
74
-  };
75
-
76
-  currentUrl: string;
77
-
78
-  webviewRef: { current: null | WebView };
73
+function WebViewScreen(props: Props) {
74
+  const [currentUrl, setCurrentUrl] = useState(props.url);
75
+  const [canGoBack, setCanGoBack] = useState(false);
76
+  const navigation = useNavigation();
77
+  const theme = useTheme();
78
+  const webviewRef = useRef<WebView>();
79
 
79
 
80
-  canGoBack: boolean;
80
+  const { setCollapsible } = useCollapsible();
81
+  const collapsible = useCollapsibleHeader({
82
+    config: { collapsedColor: theme.colors.surface, useNativeDriver: false },
83
+  });
84
+  const { containerPaddingTop, onScrollWithListener } = collapsible;
81
 
85
 
82
-  constructor(props: PropsType) {
83
-    super(props);
84
-    this.webviewRef = React.createRef();
85
-    this.canGoBack = false;
86
-    this.currentUrl = props.url;
87
-  }
86
+  const [currentInjectedJS, setCurrentInjectedJS] = useState(props.injectJS);
88
 
87
 
89
-  /**
90
-   * Creates header buttons and listens to events after mounting
91
-   */
92
-  componentDidMount() {
93
-    const { props } = this;
94
-    props.navigation.setOptions({
95
-      headerRight: props.showAdvancedControls
96
-        ? this.getAdvancedButtons
97
-        : this.getBasicButton,
98
-    });
99
-    props.navigation.addListener('focus', () => {
88
+  useFocusEffect(
89
+    useCallback(() => {
90
+      setCollapsible(collapsible);
100
       BackHandler.addEventListener(
91
       BackHandler.addEventListener(
101
         'hardwareBackPress',
92
         'hardwareBackPress',
102
-        this.onBackButtonPressAndroid
103
-      );
104
-    });
105
-    props.navigation.addListener('blur', () => {
106
-      BackHandler.removeEventListener(
107
-        'hardwareBackPress',
108
-        this.onBackButtonPressAndroid
93
+        onBackButtonPressAndroid
109
       );
94
       );
95
+      return () => {
96
+        BackHandler.removeEventListener(
97
+          'hardwareBackPress',
98
+          onBackButtonPressAndroid
99
+        );
100
+      };
101
+      // eslint-disable-next-line react-hooks/exhaustive-deps
102
+    }, [collapsible, setCollapsible])
103
+  );
104
+
105
+  useLayoutEffect(() => {
106
+    navigation.setOptions({
107
+      headerRight: props.showAdvancedControls
108
+        ? getAdvancedButtons
109
+        : getBasicButton,
110
     });
110
     });
111
-  }
111
+    // eslint-disable-next-line react-hooks/exhaustive-deps
112
+  }, [navigation, props.showAdvancedControls]);
112
 
113
 
113
-  /**
114
-   * Goes back on the webview or on the navigation stack if we cannot go back anymore
115
-   *
116
-   * @returns {boolean}
117
-   */
118
-  onBackButtonPressAndroid = (): boolean => {
119
-    if (this.canGoBack) {
120
-      this.onGoBackClicked();
114
+  useEffect(() => {
115
+    if (props.injectJS && props.injectJS !== currentInjectedJS) {
116
+      injectJavaScript(props.injectJS);
117
+      setCurrentInjectedJS(props.injectJS);
118
+    }
119
+    // eslint-disable-next-line react-hooks/exhaustive-deps
120
+  }, [props.injectJS]);
121
+
122
+  const onBackButtonPressAndroid = () => {
123
+    if (canGoBack) {
124
+      onGoBackClicked();
121
       return true;
125
       return true;
122
     }
126
     }
123
     return false;
127
     return false;
124
   };
128
   };
125
 
129
 
126
-  /**
127
-   * Gets header refresh and open in browser buttons
128
-   *
129
-   * @return {*}
130
-   */
131
-  getBasicButton = () => {
130
+  const getBasicButton = () => {
132
     return (
131
     return (
133
       <MaterialHeaderButtons>
132
       <MaterialHeaderButtons>
134
         <Item
133
         <Item
135
-          title="refresh"
136
-          iconName="refresh"
137
-          onPress={this.onRefreshClicked}
134
+          title={'refresh'}
135
+          iconName={'refresh'}
136
+          onPress={onRefreshClicked}
138
         />
137
         />
139
         <Item
138
         <Item
140
           title={i18n.t('general.openInBrowser')}
139
           title={i18n.t('general.openInBrowser')}
141
-          iconName="open-in-new"
142
-          onPress={this.onOpenClicked}
140
+          iconName={'open-in-new'}
141
+          onPress={onOpenClicked}
143
         />
142
         />
144
       </MaterialHeaderButtons>
143
       </MaterialHeaderButtons>
145
     );
144
     );
146
   };
145
   };
147
 
146
 
148
-  /**
149
-   * Creates advanced header control buttons.
150
-   * These buttons allows the user to refresh, go back, go forward and open in the browser.
151
-   *
152
-   * @returns {*}
153
-   */
154
-  getAdvancedButtons = () => {
155
-    const { props } = this;
147
+  const getAdvancedButtons = () => {
156
     return (
148
     return (
157
       <MaterialHeaderButtons>
149
       <MaterialHeaderButtons>
158
-        <Item
159
-          title="refresh"
160
-          iconName="refresh"
161
-          onPress={this.onRefreshClicked}
162
-        />
150
+        <Item title="refresh" iconName="refresh" onPress={onRefreshClicked} />
163
         <OverflowMenu
151
         <OverflowMenu
164
           style={styles.overflow}
152
           style={styles.overflow}
165
           OverflowIcon={
153
           OverflowIcon={
166
             <MaterialCommunityIcons
154
             <MaterialCommunityIcons
167
               name="dots-vertical"
155
               name="dots-vertical"
168
               size={26}
156
               size={26}
169
-              color={props.theme.colors.text}
157
+              color={theme.colors.text}
170
             />
158
             />
171
           }
159
           }
172
         >
160
         >
173
           <HiddenItem
161
           <HiddenItem
174
             title={i18n.t('general.goBack')}
162
             title={i18n.t('general.goBack')}
175
-            onPress={this.onGoBackClicked}
163
+            onPress={onGoBackClicked}
176
           />
164
           />
177
           <HiddenItem
165
           <HiddenItem
178
             title={i18n.t('general.goForward')}
166
             title={i18n.t('general.goForward')}
179
-            onPress={this.onGoForwardClicked}
167
+            onPress={onGoForwardClicked}
180
           />
168
           />
181
           <Divider />
169
           <Divider />
182
           <HiddenItem
170
           <HiddenItem
183
             title={i18n.t('general.openInBrowser')}
171
             title={i18n.t('general.openInBrowser')}
184
-            onPress={this.onOpenClicked}
172
+            onPress={onOpenClicked}
185
           />
173
           />
186
         </OverflowMenu>
174
         </OverflowMenu>
187
       </MaterialHeaderButtons>
175
       </MaterialHeaderButtons>
188
     );
176
     );
189
   };
177
   };
190
 
178
 
191
-  /**
192
-   * Gets the loading indicator
193
-   *
194
-   * @return {*}
195
-   */
196
-  getRenderLoading = () => <BasicLoadingScreen isAbsolute />;
179
+  const getRenderLoading = () => <BasicLoadingScreen isAbsolute={true} />;
197
 
180
 
198
   /**
181
   /**
199
    * Gets the javascript needed to generate a padding on top of the page
182
    * Gets the javascript needed to generate a padding on top of the page
202
    * @param padding The padding to add in pixels
185
    * @param padding The padding to add in pixels
203
    * @returns {string}
186
    * @returns {string}
204
    */
187
    */
205
-  getJavascriptPadding(padding: number): string {
206
-    const { props } = this;
188
+  const getJavascriptPadding = (padding: number) => {
207
     const customPadding =
189
     const customPadding =
208
       props.customPaddingFunction != null
190
       props.customPaddingFunction != null
209
         ? props.customPaddingFunction(padding)
191
         ? props.customPaddingFunction(padding)
210
         : '';
192
         : '';
211
     return `document.getElementsByTagName('body')[0].style.paddingTop = '${padding}px';${customPadding}true;`;
193
     return `document.getElementsByTagName('body')[0].style.paddingTop = '${padding}px';${customPadding}true;`;
212
-  }
194
+  };
213
 
195
 
214
-  /**
215
-   * Callback to use when refresh button is clicked. Reloads the webview.
216
-   */
217
-  onRefreshClicked = () => {
218
-    if (this.webviewRef.current != null) {
219
-      this.webviewRef.current.reload();
196
+  const onRefreshClicked = () => {
197
+    //@ts-ignore
198
+    if (webviewRef.current) {
199
+      //@ts-ignore
200
+      webviewRef.current.reload();
220
     }
201
     }
221
   };
202
   };
222
 
203
 
223
-  onGoBackClicked = () => {
224
-    if (this.webviewRef.current != null) {
225
-      this.webviewRef.current.goBack();
204
+  const onGoBackClicked = () => {
205
+    //@ts-ignore
206
+    if (webviewRef.current) {
207
+      //@ts-ignore
208
+      webviewRef.current.goBack();
226
     }
209
     }
227
   };
210
   };
228
 
211
 
229
-  onGoForwardClicked = () => {
230
-    if (this.webviewRef.current != null) {
231
-      this.webviewRef.current.goForward();
212
+  const onGoForwardClicked = () => {
213
+    //@ts-ignore
214
+    if (webviewRef.current) {
215
+      //@ts-ignore
216
+      webviewRef.current.goForward();
232
     }
217
     }
233
   };
218
   };
234
 
219
 
235
-  onOpenClicked = () => {
236
-    Linking.openURL(this.currentUrl);
237
-  };
220
+  const onOpenClicked = () => Linking.openURL(currentUrl);
238
 
221
 
239
-  onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
240
-    const { onScroll } = this.props;
241
-    if (onScroll) {
242
-      onScroll(event);
222
+  const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
223
+    if (props.onScroll) {
224
+      props.onScroll(event);
243
     }
225
     }
244
   };
226
   };
245
 
227
 
246
-  /**
247
-   * Injects the given javascript string into the web page
248
-   *
249
-   * @param script The script to inject
250
-   */
251
-  injectJavaScript = (script: string) => {
252
-    if (this.webviewRef.current != null) {
253
-      this.webviewRef.current.injectJavaScript(script);
228
+  const injectJavaScript = (script: string) => {
229
+    //@ts-ignore
230
+    if (webviewRef.current) {
231
+      //@ts-ignore
232
+      webviewRef.current.injectJavaScript(script);
254
     }
233
     }
255
   };
234
   };
256
 
235
 
257
-  render() {
258
-    const { props } = this;
259
-    const {
260
-      containerPaddingTop,
261
-      onScrollWithListener,
262
-    } = props.collapsibleStack;
263
-    return (
264
-      <AnimatedWebView
265
-        ref={this.webviewRef}
266
-        source={{ uri: props.url }}
267
-        startInLoadingState
268
-        injectedJavaScript={props.customJS}
269
-        javaScriptEnabled
270
-        renderLoading={this.getRenderLoading}
271
-        renderError={() => (
272
-          <ErrorView
273
-            errorCode={ERROR_TYPE.CONNECTION_ERROR}
274
-            onRefresh={this.onRefreshClicked}
275
-          />
276
-        )}
277
-        onNavigationStateChange={(navState) => {
278
-          this.currentUrl = navState.url;
279
-          this.canGoBack = navState.canGoBack;
280
-        }}
281
-        onMessage={props.onMessage}
282
-        onLoad={() => {
283
-          this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop));
284
-        }}
285
-        // Animations
286
-        onScroll={(event) => onScrollWithListener(this.onScroll)(event)}
287
-      />
288
-    );
289
-  }
236
+  return (
237
+    <AnimatedWebView
238
+      ref={webviewRef}
239
+      source={{ uri: props.url }}
240
+      startInLoadingState={true}
241
+      injectedJavaScript={props.initialJS}
242
+      javaScriptEnabled={true}
243
+      renderLoading={getRenderLoading}
244
+      renderError={() => (
245
+        <ErrorView
246
+          errorCode={ERROR_TYPE.CONNECTION_ERROR}
247
+          onRefresh={onRefreshClicked}
248
+        />
249
+      )}
250
+      onNavigationStateChange={(navState) => {
251
+        setCurrentUrl(navState.url);
252
+        setCanGoBack(navState.canGoBack);
253
+      }}
254
+      onMessage={props.onMessage}
255
+      onLoad={() => injectJavaScript(getJavascriptPadding(containerPaddingTop))}
256
+      // Animations
257
+      onScroll={onScrollWithListener(onScroll)}
258
+    />
259
+  );
290
 }
260
 }
291
 
261
 
292
-export default withCollapsible(withTheme(WebViewScreen));
262
+export default WebViewScreen;

+ 69
- 192
src/components/Tabbar/CustomTabBar.tsx View File

17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
  */
18
  */
19
 
19
 
20
-import * as React from 'react';
20
+import React from 'react';
21
+import type { BottomTabBarProps } from '@react-navigation/bottom-tabs';
21
 import { Animated, StyleSheet } from 'react-native';
22
 import { Animated, StyleSheet } from 'react-native';
22
-import { withTheme } from 'react-native-paper';
23
-import { Collapsible } from 'react-navigation-collapsible';
24
 import TabIcon from './TabIcon';
23
 import TabIcon from './TabIcon';
25
-import TabHomeIcon from './TabHomeIcon';
26
-import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
27
-import { NavigationState } from '@react-navigation/native';
28
-import {
29
-  PartialState,
30
-  Route,
31
-} from '@react-navigation/routers/lib/typescript/src/types';
32
-
33
-type RouteType = Route<string> & {
34
-  state?: NavigationState | PartialState<NavigationState>;
35
-};
24
+import { useTheme } from 'react-native-paper';
25
+import { useCollapsible } from '../../utils/CollapsibleContext';
26
+
27
+export const TAB_BAR_HEIGHT = 50;
28
+
29
+function CustomTabBar(
30
+  props: BottomTabBarProps<any> & {
31
+    icons: {
32
+      [key: string]: {
33
+        normal: string;
34
+        focused: string;
35
+      };
36
+    };
37
+    labels: {
38
+      [key: string]: string;
39
+    };
40
+  }
41
+) {
42
+  const state = props.state;
43
+  const theme = useTheme();
44
+
45
+  const { collapsible } = useCollapsible();
46
+  let translateY: number | Animated.AnimatedInterpolation = 0;
47
+  if (collapsible) {
48
+    translateY = Animated.multiply(-1.5, collapsible.translateY);
49
+  }
36
 
50
 
37
-interface PropsType extends BottomTabBarProps {
38
-  theme: ReactNativePaper.Theme;
51
+  return (
52
+    <Animated.View
53
+      style={{
54
+        ...styles.bar,
55
+        backgroundColor: theme.colors.surface,
56
+        transform: [{ translateY: translateY }],
57
+      }}
58
+    >
59
+      {state.routes.map(
60
+        (
61
+          route: {
62
+            key: string;
63
+            name: string;
64
+            params?: object | undefined;
65
+          },
66
+          index: number
67
+        ) => {
68
+          const iconData = props.icons[route.name];
69
+          return (
70
+            <TabIcon
71
+              isMiddle={index === 2}
72
+              onPress={() => props.navigation.navigate(route.name)}
73
+              icon={iconData.normal}
74
+              focusedIcon={iconData.focused}
75
+              label={props.labels[route.name]}
76
+              focused={state.index === index}
77
+              key={route.key}
78
+            />
79
+          );
80
+        }
81
+      )}
82
+    </Animated.View>
83
+  );
39
 }
84
 }
40
 
85
 
41
-type StateType = {
42
-  translateY: any;
43
-};
44
-
45
-type validRoutes = 'proxiwash' | 'services' | 'planning' | 'planex';
46
-
47
-const TAB_ICONS = {
48
-  proxiwash: 'tshirt-crew',
49
-  services: 'account-circle',
50
-  planning: 'calendar-range',
51
-  planex: 'clock',
52
-};
53
-
54
 const styles = StyleSheet.create({
86
 const styles = StyleSheet.create({
55
-  container: {
87
+  bar: {
56
     flexDirection: 'row',
88
     flexDirection: 'row',
57
     width: '100%',
89
     width: '100%',
90
+    height: 50,
58
     position: 'absolute',
91
     position: 'absolute',
59
     bottom: 0,
92
     bottom: 0,
60
     left: 0,
93
     left: 0,
61
   },
94
   },
62
 });
95
 });
63
 
96
 
64
-class CustomTabBar extends React.Component<PropsType, StateType> {
65
-  static TAB_BAR_HEIGHT = 48;
66
-
67
-  constructor(props: PropsType) {
68
-    super(props);
69
-    this.state = {
70
-      translateY: new Animated.Value(0),
71
-    };
72
-    // @ts-ignore
73
-    props.navigation.addListener('state', this.onRouteChange);
74
-  }
75
-
76
-  /**
77
-   * Navigates to the given route if it is different from the current one
78
-   *
79
-   * @param route Destination route
80
-   * @param currentIndex The current route index
81
-   * @param destIndex The destination route index
82
-   */
83
-  onItemPress(route: RouteType, currentIndex: number, destIndex: number) {
84
-    const { navigation } = this.props;
85
-    if (currentIndex !== destIndex) {
86
-      navigation.navigate(route.name);
87
-    }
88
-  }
89
-
90
-  /**
91
-   * Navigates to tetris screen on home button long press
92
-   *
93
-   * @param route
94
-   */
95
-  onItemLongPress(route: RouteType) {
96
-    const { navigation } = this.props;
97
-    if (route.name === 'home') {
98
-      navigation.navigate('game-start');
99
-    }
100
-  }
101
-
102
-  /**
103
-   * Finds the active route and syncs the tab bar animation with the header bar
104
-   */
105
-  onRouteChange = () => {
106
-    const { props } = this;
107
-    props.state.routes.map(this.syncTabBar);
108
-  };
109
-
110
-  /**
111
-   * Gets an icon for the given route if it is not the home one as it uses a custom button
112
-   *
113
-   * @param route
114
-   * @param focused
115
-   * @returns {null}
116
-   */
117
-  getTabBarIcon = (route: RouteType, focused: boolean) => {
118
-    let icon = TAB_ICONS[route.name as validRoutes];
119
-    icon = focused ? icon : `${icon}-outline`;
120
-    if (route.name !== 'home') {
121
-      return icon;
122
-    }
123
-    return '';
124
-  };
125
-
126
-  /**
127
-   * Gets a tab icon render.
128
-   * If the given route is focused, it syncs the tab bar and header bar animations together
129
-   *
130
-   * @param route The route for the icon
131
-   * @param index The index of the current route
132
-   * @returns {*}
133
-   */
134
-  getRenderIcon = (route: RouteType, index: number) => {
135
-    const { props } = this;
136
-    const { state } = props;
137
-    const { options } = props.descriptors[route.key];
138
-    let label;
139
-    if (options.tabBarLabel != null) {
140
-      label = options.tabBarLabel;
141
-    } else if (options.title != null) {
142
-      label = options.title;
143
-    } else {
144
-      label = route.name;
145
-    }
146
-
147
-    const onPress = () => {
148
-      this.onItemPress(route, state.index, index);
149
-    };
150
-    const onLongPress = () => {
151
-      this.onItemLongPress(route);
152
-    };
153
-    const isFocused = state.index === index;
154
-
155
-    const color = isFocused
156
-      ? props.theme.colors.primary
157
-      : props.theme.colors.tabIcon;
158
-    if (route.name !== 'home') {
159
-      return (
160
-        <TabIcon
161
-          onPress={onPress}
162
-          onLongPress={onLongPress}
163
-          icon={this.getTabBarIcon(route, isFocused)}
164
-          color={color}
165
-          label={label as string}
166
-          focused={isFocused}
167
-          extraData={state.index > index}
168
-          key={route.key}
169
-        />
170
-      );
171
-    }
172
-    return (
173
-      <TabHomeIcon
174
-        onPress={onPress}
175
-        onLongPress={onLongPress}
176
-        focused={isFocused}
177
-        key={route.key}
178
-        tabBarHeight={CustomTabBar.TAB_BAR_HEIGHT}
179
-      />
180
-    );
181
-  };
182
-
183
-  getIcons() {
184
-    const { props } = this;
185
-    return props.state.routes.map(this.getRenderIcon);
186
-  }
187
-
188
-  syncTabBar = (route: RouteType, index: number) => {
189
-    const { state } = this.props;
190
-    const isFocused = state.index === index;
191
-    if (isFocused) {
192
-      const stackState = route.state;
193
-      const stackRoute =
194
-        stackState && stackState.index != null
195
-          ? stackState.routes[stackState.index]
196
-          : null;
197
-      const params: { collapsible: Collapsible } | null | undefined = stackRoute
198
-        ? (stackRoute.params as { collapsible: Collapsible })
199
-        : null;
200
-      const collapsible = params != null ? params.collapsible : null;
201
-      if (collapsible != null) {
202
-        this.setState({
203
-          translateY: Animated.multiply(-1.5, collapsible.translateY), // Hide tab bar faster than header bar
204
-        });
205
-      }
206
-    }
207
-  };
208
-
209
-  render() {
210
-    const { props, state } = this;
211
-    const icons = this.getIcons();
212
-    return (
213
-      <Animated.View
214
-        style={{
215
-          height: CustomTabBar.TAB_BAR_HEIGHT,
216
-          backgroundColor: props.theme.colors.surface,
217
-          transform: [{ translateY: state.translateY }],
218
-          ...styles.container,
219
-        }}
220
-      >
221
-        {icons}
222
-      </Animated.View>
223
-    );
224
-  }
97
+function areEqual(
98
+  prevProps: BottomTabBarProps<any>,
99
+  nextProps: BottomTabBarProps<any>
100
+) {
101
+  return prevProps.state.index === nextProps.state.index;
225
 }
102
 }
226
 
103
 
227
-export default withTheme(CustomTabBar);
104
+export default React.memo(CustomTabBar, areEqual);

+ 80
- 101
src/components/Tabbar/TabHomeIcon.tsx View File

1
-/*
2
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
3
- *
4
- * This file is part of Campus INSAT.
5
- *
6
- * Campus INSAT is free software: you can redistribute it and/or modify
7
- *  it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Campus INSAT is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
- */
19
-
20
-import * as React from 'react';
21
-import { Image, StyleSheet, View } from 'react-native';
1
+import React from 'react';
2
+import { View, StyleSheet, Image } from 'react-native';
22
 import { FAB } from 'react-native-paper';
3
 import { FAB } from 'react-native-paper';
23
 import * as Animatable from 'react-native-animatable';
4
 import * as Animatable from 'react-native-animatable';
24
-const FOCUSED_ICON = require('../../../assets/tab-icon.png');
25
-const UNFOCUSED_ICON = require('../../../assets/tab-icon-outline.png');
26
 
5
 
27
-type PropsType = {
6
+interface Props {
7
+  icon: string;
8
+  focusedIcon: string;
28
   focused: boolean;
9
   focused: boolean;
29
   onPress: () => void;
10
   onPress: () => void;
30
-  onLongPress: () => void;
31
-  tabBarHeight: number;
32
-};
11
+}
33
 
12
 
34
-const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
13
+Animatable.initializeRegistryWithDefinitions({
14
+  fabFocusIn: {
15
+    0: {
16
+      // @ts-ignore
17
+      scale: 1,
18
+      translateY: 0,
19
+    },
20
+    0.4: {
21
+      // @ts-ignore
22
+      scale: 1.2,
23
+      translateY: -9,
24
+    },
25
+    0.6: {
26
+      // @ts-ignore
27
+      scale: 1.05,
28
+      translateY: -6,
29
+    },
30
+    0.8: {
31
+      // @ts-ignore
32
+      scale: 1.15,
33
+      translateY: -6,
34
+    },
35
+    1: {
36
+      // @ts-ignore
37
+      scale: 1.1,
38
+      translateY: -6,
39
+    },
40
+  },
41
+  fabFocusOut: {
42
+    0: {
43
+      // @ts-ignore
44
+      scale: 1.1,
45
+      translateY: -6,
46
+    },
47
+    1: {
48
+      // @ts-ignore
49
+      scale: 1,
50
+      translateY: 0,
51
+    },
52
+  },
53
+});
35
 
54
 
36
 const styles = StyleSheet.create({
55
 const styles = StyleSheet.create({
37
-  container: {
56
+  outer: {
38
     flex: 1,
57
     flex: 1,
39
     justifyContent: 'center',
58
     justifyContent: 'center',
40
   },
59
   },
41
-  subcontainer: {
60
+  inner: {
42
     position: 'absolute',
61
     position: 'absolute',
43
     bottom: 0,
62
     bottom: 0,
44
     left: 0,
63
     left: 0,
45
     width: '100%',
64
     width: '100%',
46
-    marginBottom: -15,
65
+    height: 60,
47
   },
66
   },
48
   fab: {
67
   fab: {
49
-    marginTop: 15,
50
     marginLeft: 'auto',
68
     marginLeft: 'auto',
51
     marginRight: 'auto',
69
     marginRight: 'auto',
52
   },
70
   },
53
 });
71
 });
54
 
72
 
55
-/**
56
- * Abstraction layer for Agenda component, using custom configuration
57
- */
58
-class TabHomeIcon extends React.Component<PropsType> {
59
-  constructor(props: PropsType) {
60
-    super(props);
61
-    Animatable.initializeRegistryWithDefinitions({
62
-      fabFocusIn: {
63
-        '0': {
64
-          // @ts-ignore
65
-          scale: 1,
66
-          translateY: 0,
67
-        },
68
-        '0.9': {
69
-          scale: 1.2,
70
-          translateY: -9,
71
-        },
72
-        '1': {
73
-          scale: 1.1,
74
-          translateY: -7,
75
-        },
76
-      },
77
-      fabFocusOut: {
78
-        '0': {
79
-          // @ts-ignore
80
-          scale: 1.1,
81
-          translateY: -6,
82
-        },
83
-        '1': {
84
-          scale: 1,
85
-          translateY: 0,
86
-        },
87
-      },
88
-    });
89
-  }
90
-
91
-  shouldComponentUpdate(nextProps: PropsType): boolean {
92
-    const { focused } = this.props;
93
-    return nextProps.focused !== focused;
94
-  }
73
+const FOCUSED_ICON = require('../../../assets/tab-icon.png');
74
+const UNFOCUSED_ICON = require('../../../assets/tab-icon-outline.png');
95
 
75
 
96
-  getIconRender = ({ size, color }: { size: number; color: string }) => {
97
-    const { focused } = this.props;
76
+function TabHomeIcon(props: Props) {
77
+  const getImage = (iconProps: { size: number; color: string }) => {
98
     return (
78
     return (
99
-      <Image
100
-        source={focused ? FOCUSED_ICON : UNFOCUSED_ICON}
101
-        style={{
102
-          width: size,
103
-          height: size,
104
-          tintColor: color,
105
-        }}
106
-      />
79
+      <Animatable.View useNativeDriver={true} animation={'rubberBand'}>
80
+        <Image
81
+          source={props.focused ? FOCUSED_ICON : UNFOCUSED_ICON}
82
+          style={{
83
+            width: iconProps.size,
84
+            height: iconProps.size,
85
+            tintColor: iconProps.color,
86
+          }}
87
+        />
88
+      </Animatable.View>
107
     );
89
     );
108
   };
90
   };
109
 
91
 
110
-  render() {
111
-    const { props } = this;
112
-    return (
113
-      <View style={styles.container}>
114
-        <View
115
-          style={{
116
-            height: props.tabBarHeight + 30,
117
-            ...styles.subcontainer,
118
-          }}
92
+  return (
93
+    <View style={styles.outer}>
94
+      <View style={styles.inner}>
95
+        <Animatable.View
96
+          style={styles.fab}
97
+          useNativeDriver={true}
98
+          duration={props.focused ? 500 : 200}
99
+          animation={props.focused ? 'fabFocusIn' : 'fabFocusOut'}
100
+          easing={'ease-out'}
119
         >
101
         >
120
-          <AnimatedFAB
121
-            duration={200}
122
-            easing="ease-out"
123
-            animation={props.focused ? 'fabFocusIn' : 'fabFocusOut'}
124
-            icon={this.getIconRender}
102
+          <FAB
125
             onPress={props.onPress}
103
             onPress={props.onPress}
126
-            onLongPress={props.onLongPress}
127
-            style={styles.fab}
104
+            animated={false}
105
+            icon={getImage}
106
+            color={'#fff'}
128
           />
107
           />
129
-        </View>
108
+        </Animatable.View>
130
       </View>
109
       </View>
131
-    );
132
-  }
110
+    </View>
111
+  );
133
 }
112
 }
134
 
113
 
135
 export default TabHomeIcon;
114
 export default TabHomeIcon;

+ 28
- 130
src/components/Tabbar/TabIcon.tsx View File

1
-/*
2
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
3
- *
4
- * This file is part of Campus INSAT.
5
- *
6
- * Campus INSAT is free software: you can redistribute it and/or modify
7
- *  it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Campus INSAT is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
- */
1
+import React from 'react';
2
+import TabHomeIcon from './TabHomeIcon';
3
+import TabSideIcon from './TabSideIcon';
19
 
4
 
20
-import * as React from 'react';
21
-import { StyleSheet, View } from 'react-native';
22
-import { TouchableRipple, withTheme } from 'react-native-paper';
23
-import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
24
-import * as Animatable from 'react-native-animatable';
25
-import GENERAL_STYLES from '../../constants/Styles';
26
-
27
-type PropsType = {
5
+interface Props {
6
+  isMiddle: boolean;
28
   focused: boolean;
7
   focused: boolean;
29
-  color: string;
30
-  label: string;
8
+  label: string | undefined;
31
   icon: string;
9
   icon: string;
10
+  focusedIcon: string;
32
   onPress: () => void;
11
   onPress: () => void;
33
-  onLongPress: () => void;
34
-  theme: ReactNativePaper.Theme;
35
-  extraData: null | boolean | number | string;
36
-};
37
-
38
-const styles = StyleSheet.create({
39
-  container: {
40
-    flex: 1,
41
-    justifyContent: 'center',
42
-    borderRadius: 10,
43
-  },
44
-  text: {
45
-    marginLeft: 'auto',
46
-    marginRight: 'auto',
47
-    fontSize: 10,
48
-  },
49
-});
50
-
51
-/**
52
- * Abstraction layer for Agenda component, using custom configuration
53
- */
54
-class TabIcon extends React.Component<PropsType> {
55
-  firstRender: boolean;
56
-
57
-  constructor(props: PropsType) {
58
-    super(props);
59
-    Animatable.initializeRegistryWithDefinitions({
60
-      focusIn: {
61
-        '0': {
62
-          // @ts-ignore
63
-          scale: 1,
64
-          translateY: 0,
65
-        },
66
-        '0.9': {
67
-          scale: 1.3,
68
-          translateY: 7,
69
-        },
70
-        '1': {
71
-          scale: 1.2,
72
-          translateY: 6,
73
-        },
74
-      },
75
-      focusOut: {
76
-        '0': {
77
-          // @ts-ignore
78
-          scale: 1.2,
79
-          translateY: 6,
80
-        },
81
-        '1': {
82
-          scale: 1,
83
-          translateY: 0,
84
-        },
85
-      },
86
-    });
87
-    this.firstRender = true;
88
-  }
89
-
90
-  componentDidMount() {
91
-    this.firstRender = false;
92
-  }
12
+}
93
 
13
 
94
-  shouldComponentUpdate(nextProps: PropsType): boolean {
95
-    const { props } = this;
14
+function TabIcon(props: Props) {
15
+  if (props.isMiddle) {
96
     return (
16
     return (
97
-      nextProps.focused !== props.focused ||
98
-      nextProps.theme.dark !== props.theme.dark ||
99
-      nextProps.extraData !== props.extraData
17
+      <TabHomeIcon
18
+        icon={props.icon}
19
+        focusedIcon={props.focusedIcon}
20
+        focused={props.focused}
21
+        onPress={props.onPress}
22
+      />
100
     );
23
     );
101
-  }
102
-
103
-  render() {
104
-    const { props } = this;
24
+  } else {
105
     return (
25
     return (
106
-      <TouchableRipple
26
+      <TabSideIcon
27
+        focused={props.focused}
28
+        label={props.label}
29
+        icon={props.icon}
30
+        focusedIcon={props.focusedIcon}
107
         onPress={props.onPress}
31
         onPress={props.onPress}
108
-        onLongPress={props.onLongPress}
109
-        rippleColor={props.theme.colors.primary}
110
-        borderless={true}
111
-        style={styles.container}
112
-      >
113
-        <View>
114
-          <Animatable.View
115
-            duration={200}
116
-            easing="ease-out"
117
-            animation={props.focused ? 'focusIn' : 'focusOut'}
118
-            useNativeDriver
119
-          >
120
-            <MaterialCommunityIcons
121
-              name={props.icon}
122
-              color={props.color}
123
-              size={26}
124
-              style={GENERAL_STYLES.centerHorizontal}
125
-            />
126
-          </Animatable.View>
127
-          <Animatable.Text
128
-            animation={props.focused ? 'fadeOutDown' : 'fadeIn'}
129
-            useNativeDriver
130
-            style={{
131
-              color: props.color,
132
-              ...styles.text,
133
-            }}
134
-          >
135
-            {props.label}
136
-          </Animatable.Text>
137
-        </View>
138
-      </TouchableRipple>
32
+      />
139
     );
33
     );
140
   }
34
   }
141
 }
35
 }
142
 
36
 
143
-export default withTheme(TabIcon);
37
+function areEqual(prevProps: Props, nextProps: Props) {
38
+  return prevProps.focused === nextProps.focused;
39
+}
40
+
41
+export default React.memo(TabIcon, areEqual);

+ 113
- 0
src/components/Tabbar/TabSideIcon.tsx View File

1
+import React from 'react';
2
+import { TouchableRipple, useTheme } from 'react-native-paper';
3
+import { StyleSheet, View } from 'react-native';
4
+import * as Animatable from 'react-native-animatable';
5
+import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
6
+import GENERAL_STYLES from '../../constants/Styles';
7
+
8
+interface Props {
9
+  focused: boolean;
10
+  label: string | undefined;
11
+  icon: string;
12
+  focusedIcon: string;
13
+  onPress: () => void;
14
+}
15
+
16
+Animatable.initializeRegistryWithDefinitions({
17
+  focusIn: {
18
+    0: {
19
+      // @ts-ignore
20
+      scale: 1,
21
+      translateY: 0,
22
+    },
23
+    0.4: {
24
+      // @ts-ignore
25
+      scale: 1.3,
26
+      translateY: 6,
27
+    },
28
+    0.6: {
29
+      // @ts-ignore
30
+      scale: 1.1,
31
+      translateY: 6,
32
+    },
33
+    0.8: {
34
+      // @ts-ignore
35
+      scale: 1.25,
36
+      translateY: 6,
37
+    },
38
+    1: {
39
+      // @ts-ignore
40
+      scale: 1.2,
41
+      translateY: 6,
42
+    },
43
+  },
44
+  focusOut: {
45
+    0: {
46
+      // @ts-ignore
47
+      scale: 1.2,
48
+      translateY: 6,
49
+    },
50
+    1: {
51
+      // @ts-ignore
52
+      scale: 1,
53
+      translateY: 0,
54
+    },
55
+  },
56
+});
57
+
58
+function TabSideIcon(props: Props) {
59
+  const theme = useTheme();
60
+  const color = props.focused ? theme.colors.primary : theme.colors.disabled;
61
+  let icon = props.focused ? props.focusedIcon : props.icon;
62
+  return (
63
+    <TouchableRipple
64
+      onPress={props.onPress}
65
+      borderless
66
+      rippleColor={theme.colors.primary}
67
+      style={{
68
+        ...styles.ripple,
69
+        borderTopEndRadius: theme.roundness,
70
+        borderTopStartRadius: theme.roundness,
71
+      }}
72
+    >
73
+      <View>
74
+        <Animatable.View
75
+          duration={props.focused ? 500 : 200}
76
+          easing="ease-out"
77
+          animation={props.focused ? 'focusIn' : 'focusOut'}
78
+          useNativeDriver
79
+        >
80
+          <MaterialCommunityIcons
81
+            name={icon}
82
+            color={color}
83
+            size={26}
84
+            style={GENERAL_STYLES.centerHorizontal}
85
+          />
86
+        </Animatable.View>
87
+        <Animatable.Text
88
+          animation={props.focused ? 'fadeOutDown' : 'fadeIn'}
89
+          useNativeDriver
90
+          style={{
91
+            color: color,
92
+            ...styles.text,
93
+          }}
94
+        >
95
+          {props.label}
96
+        </Animatable.Text>
97
+      </View>
98
+    </TouchableRipple>
99
+  );
100
+}
101
+
102
+const styles = StyleSheet.create({
103
+  ripple: {
104
+    flex: 1,
105
+    justifyContent: 'center',
106
+  },
107
+  text: {
108
+    ...GENERAL_STYLES.centerHorizontal,
109
+    fontSize: 10,
110
+  },
111
+});
112
+
113
+export default TabSideIcon;

+ 33
- 0
src/components/providers/CollapsibleProvider.tsx View File

1
+import React, { useState } from 'react';
2
+import { Collapsible } from 'react-navigation-collapsible';
3
+import {
4
+  CollapsibleContext,
5
+  CollapsibleContextType,
6
+} from '../../utils/CollapsibleContext';
7
+
8
+type Props = {
9
+  children: React.ReactChild;
10
+};
11
+
12
+export default function CollapsibleProvider(props: Props) {
13
+  const setCollapsible = (collapsible: Collapsible) => {
14
+    setCurrentCollapsible((prevState) => ({
15
+      ...prevState,
16
+      collapsible,
17
+    }));
18
+  };
19
+
20
+  const [
21
+    currentCollapsible,
22
+    setCurrentCollapsible,
23
+  ] = useState<CollapsibleContextType>({
24
+    collapsible: undefined,
25
+    setCollapsible: setCollapsible,
26
+  });
27
+
28
+  return (
29
+    <CollapsibleContext.Provider value={currentCollapsible}>
30
+      {props.children}
31
+    </CollapsibleContext.Provider>
32
+  );
33
+}

+ 162
- 165
src/navigation/MainNavigator.tsx View File

18
  */
18
  */
19
 
19
 
20
 import * as React from 'react';
20
 import * as React from 'react';
21
-import {
22
-  createStackNavigator,
23
-  TransitionPresets,
24
-} from '@react-navigation/stack';
21
+import { createStackNavigator } from '@react-navigation/stack';
25
 import i18n from 'i18n-js';
22
 import i18n from 'i18n-js';
26
-import { Platform } from 'react-native';
27
 import SettingsScreen from '../screens/Other/Settings/SettingsScreen';
23
 import SettingsScreen from '../screens/Other/Settings/SettingsScreen';
28
 import AboutScreen from '../screens/About/AboutScreen';
24
 import AboutScreen from '../screens/About/AboutScreen';
29
 import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen';
25
 import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen';
40
 import ClubListScreen from '../screens/Amicale/Clubs/ClubListScreen';
36
 import ClubListScreen from '../screens/Amicale/Clubs/ClubListScreen';
41
 import ClubAboutScreen from '../screens/Amicale/Clubs/ClubAboutScreen';
37
 import ClubAboutScreen from '../screens/Amicale/Clubs/ClubAboutScreen';
42
 import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
38
 import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
43
-import {
44
-  CreateScreenCollapsibleStack,
45
-  getWebsiteStack,
46
-} from '../utils/CollapsibleUtils';
47
 import BugReportScreen from '../screens/Other/FeedbackScreen';
39
 import BugReportScreen from '../screens/Other/FeedbackScreen';
48
 import WebsiteScreen from '../screens/Services/WebsiteScreen';
40
 import WebsiteScreen from '../screens/Services/WebsiteScreen';
49
 import EquipmentScreen, {
41
 import EquipmentScreen, {
54
 import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
46
 import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
55
 import GameStartScreen from '../screens/Game/screens/GameStartScreen';
47
 import GameStartScreen from '../screens/Game/screens/GameStartScreen';
56
 import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
48
 import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
49
+import Test from '../screens/Test';
57
 
50
 
58
 export enum MainRoutes {
51
 export enum MainRoutes {
59
   Main = 'main',
52
   Main = 'main',
83
 
76
 
84
 type DefaultParams = { [key in MainRoutes]: object | undefined };
77
 type DefaultParams = { [key in MainRoutes]: object | undefined };
85
 
78
 
86
-export interface FullParamsList extends DefaultParams {
79
+export type FullParamsList = DefaultParams & {
87
   'login': { nextScreen: string };
80
   'login': { nextScreen: string };
88
   'equipment-confirm': {
81
   'equipment-confirm': {
89
     item?: DeviceType;
82
     item?: DeviceType;
91
   };
84
   };
92
   'equipment-rent': { item?: DeviceType };
85
   'equipment-rent': { item?: DeviceType };
93
   'gallery': { images: Array<{ url: string }> };
86
   'gallery': { images: Array<{ url: string }> };
94
-}
87
+};
95
 
88
 
96
 // Don't know why but TS is complaining without this
89
 // Don't know why but TS is complaining without this
97
 // See: https://stackoverflow.com/questions/63652687/interface-does-not-satisfy-the-constraint-recordstring-object-undefined
90
 // See: https://stackoverflow.com/questions/63652687/interface-does-not-satisfy-the-constraint-recordstring-object-undefined
98
 export type MainStackParamsList = FullParamsList &
91
 export type MainStackParamsList = FullParamsList &
99
   Record<string, object | undefined>;
92
   Record<string, object | undefined>;
100
 
93
 
101
-const modalTransition =
102
-  Platform.OS === 'ios'
103
-    ? TransitionPresets.ModalPresentationIOS
104
-    : TransitionPresets.ModalTransition;
105
-
106
-const defaultScreenOptions = {
107
-  gestureEnabled: true,
108
-  cardOverlayEnabled: true,
109
-  ...TransitionPresets.SlideFromRightIOS,
110
-};
111
-
112
 const MainStack = createStackNavigator<MainStackParamsList>();
94
 const MainStack = createStackNavigator<MainStackParamsList>();
113
 
95
 
114
-function MainStackComponent(props: { createTabNavigator: () => JSX.Element }) {
96
+function MainStackComponent(props: {
97
+  createTabNavigator: () => React.ReactElement;
98
+}) {
115
   const { createTabNavigator } = props;
99
   const { createTabNavigator } = props;
116
   return (
100
   return (
117
-    <MainStack.Navigator
118
-      initialRouteName={MainRoutes.Main}
119
-      headerMode="screen"
120
-      screenOptions={defaultScreenOptions}
121
-    >
101
+    <MainStack.Navigator initialRouteName={MainRoutes.Main} headerMode="screen">
102
+      <MainStack.Screen name={'test'} component={Test} />
122
       <MainStack.Screen
103
       <MainStack.Screen
123
         name={MainRoutes.Main}
104
         name={MainRoutes.Main}
124
         component={createTabNavigator}
105
         component={createTabNavigator}
132
         component={ImageGalleryScreen}
113
         component={ImageGalleryScreen}
133
         options={{
114
         options={{
134
           headerShown: false,
115
           headerShown: false,
135
-          ...modalTransition,
136
         }}
116
         }}
137
       />
117
       />
138
-      {CreateScreenCollapsibleStack(
139
-        MainRoutes.Settings,
140
-        MainStack,
141
-        SettingsScreen,
142
-        i18n.t('screens.settings.title')
143
-      )}
144
-      {CreateScreenCollapsibleStack(
145
-        MainRoutes.DashboardEdit,
146
-        MainStack,
147
-        DashboardEditScreen,
148
-        i18n.t('screens.settings.dashboardEdit.title')
149
-      )}
150
-      {CreateScreenCollapsibleStack(
151
-        MainRoutes.About,
152
-        MainStack,
153
-        AboutScreen,
154
-        i18n.t('screens.about.title')
155
-      )}
156
-      {CreateScreenCollapsibleStack(
157
-        MainRoutes.Dependencies,
158
-        MainStack,
159
-        AboutDependenciesScreen,
160
-        i18n.t('screens.about.libs')
161
-      )}
162
-      {CreateScreenCollapsibleStack(
163
-        MainRoutes.Debug,
164
-        MainStack,
165
-        DebugScreen,
166
-        i18n.t('screens.about.debug')
167
-      )}
168
-
169
-      {CreateScreenCollapsibleStack(
170
-        MainRoutes.GameStart,
171
-        MainStack,
172
-        GameStartScreen,
173
-        i18n.t('screens.game.title'),
174
-        true,
175
-        undefined,
176
-        'transparent'
177
-      )}
118
+      <MainStack.Screen
119
+        name={MainRoutes.Settings}
120
+        component={SettingsScreen}
121
+        options={{
122
+          title: i18n.t('screens.settings.title'),
123
+        }}
124
+      />
125
+      <MainStack.Screen
126
+        name={MainRoutes.DashboardEdit}
127
+        component={DashboardEditScreen}
128
+        options={{
129
+          title: i18n.t('screens.settings.dashboardEdit.title'),
130
+        }}
131
+      />
132
+      <MainStack.Screen
133
+        name={MainRoutes.About}
134
+        component={AboutScreen}
135
+        options={{
136
+          title: i18n.t('screens.about.title'),
137
+        }}
138
+      />
139
+      <MainStack.Screen
140
+        name={MainRoutes.Dependencies}
141
+        component={AboutDependenciesScreen}
142
+        options={{
143
+          title: i18n.t('screens.about.libs'),
144
+        }}
145
+      />
146
+      <MainStack.Screen
147
+        name={MainRoutes.Debug}
148
+        component={DebugScreen}
149
+        options={{
150
+          title: i18n.t('screens.about.debug'),
151
+        }}
152
+      />
153
+      <MainStack.Screen
154
+        name={MainRoutes.GameStart}
155
+        component={GameStartScreen}
156
+        options={{
157
+          title: i18n.t('screens.game.title'),
158
+          headerStyle: {
159
+            backgroundColor: 'transparent',
160
+          },
161
+        }}
162
+      />
178
       <MainStack.Screen
163
       <MainStack.Screen
179
         name={MainRoutes.GameMain}
164
         name={MainRoutes.GameMain}
180
         component={GameMainScreen}
165
         component={GameMainScreen}
182
           title: i18n.t('screens.game.title'),
167
           title: i18n.t('screens.game.title'),
183
         }}
168
         }}
184
       />
169
       />
185
-      {CreateScreenCollapsibleStack(
186
-        MainRoutes.Login,
187
-        MainStack,
188
-        LoginScreen,
189
-        i18n.t('screens.login.title'),
190
-        true,
191
-        { headerTintColor: '#fff' },
192
-        'transparent'
193
-      )}
194
-      {getWebsiteStack('website', MainStack, WebsiteScreen, '')}
195
-
196
-      {CreateScreenCollapsibleStack(
197
-        MainRoutes.SelfMenu,
198
-        MainStack,
199
-        SelfMenuScreen,
200
-        i18n.t('screens.menu.title')
201
-      )}
202
-      {CreateScreenCollapsibleStack(
203
-        MainRoutes.Proximo,
204
-        MainStack,
205
-        ProximoMainScreen,
206
-        i18n.t('screens.proximo.title')
207
-      )}
208
-      {CreateScreenCollapsibleStack(
209
-        MainRoutes.ProximoList,
210
-        MainStack,
211
-        ProximoListScreen,
212
-        i18n.t('screens.proximo.articleList')
213
-      )}
214
-      {CreateScreenCollapsibleStack(
215
-        MainRoutes.ProximoAbout,
216
-        MainStack,
217
-        ProximoAboutScreen,
218
-        i18n.t('screens.proximo.title'),
219
-        true,
220
-        { ...modalTransition }
221
-      )}
222
-
223
-      {CreateScreenCollapsibleStack(
224
-        MainRoutes.Profile,
225
-        MainStack,
226
-        ProfileScreen,
227
-        i18n.t('screens.profile.title')
228
-      )}
229
-      {CreateScreenCollapsibleStack(
230
-        MainRoutes.ClubList,
231
-        MainStack,
232
-        ClubListScreen,
233
-        i18n.t('screens.clubs.title')
234
-      )}
235
-      {CreateScreenCollapsibleStack(
236
-        MainRoutes.ClubInformation,
237
-        MainStack,
238
-        ClubDisplayScreen,
239
-        i18n.t('screens.clubs.details'),
240
-        true,
241
-        { ...modalTransition }
242
-      )}
243
-      {CreateScreenCollapsibleStack(
244
-        MainRoutes.ClubAbout,
245
-        MainStack,
246
-        ClubAboutScreen,
247
-        i18n.t('screens.clubs.title'),
248
-        true,
249
-        { ...modalTransition }
250
-      )}
251
-      {CreateScreenCollapsibleStack(
252
-        MainRoutes.EquipmentList,
253
-        MainStack,
254
-        EquipmentScreen,
255
-        i18n.t('screens.equipment.title')
256
-      )}
257
-      {CreateScreenCollapsibleStack(
258
-        MainRoutes.EquipmentRent,
259
-        MainStack,
260
-        EquipmentLendScreen,
261
-        i18n.t('screens.equipment.book')
262
-      )}
263
-      {CreateScreenCollapsibleStack(
264
-        MainRoutes.EquipmentConfirm,
265
-        MainStack,
266
-        EquipmentConfirmScreen,
267
-        i18n.t('screens.equipment.confirm')
268
-      )}
269
-      {CreateScreenCollapsibleStack(
270
-        MainRoutes.Vote,
271
-        MainStack,
272
-        VoteScreen,
273
-        i18n.t('screens.vote.title')
274
-      )}
275
-      {CreateScreenCollapsibleStack(
276
-        MainRoutes.Feedback,
277
-        MainStack,
278
-        BugReportScreen,
279
-        i18n.t('screens.feedback.title')
280
-      )}
170
+      <MainStack.Screen
171
+        name={MainRoutes.Login}
172
+        component={LoginScreen}
173
+        options={{
174
+          title: i18n.t('screens.login.title'),
175
+          headerStyle: {
176
+            backgroundColor: 'transparent',
177
+          },
178
+        }}
179
+      />
180
+      <MainStack.Screen
181
+        name={'website'}
182
+        component={WebsiteScreen}
183
+        options={{
184
+          title: '',
185
+        }}
186
+      />
187
+      <MainStack.Screen
188
+        name={MainRoutes.SelfMenu}
189
+        component={SelfMenuScreen}
190
+        options={{
191
+          title: i18n.t('screens.menu.title'),
192
+        }}
193
+      />
194
+      <MainStack.Screen
195
+        name={MainRoutes.Proximo}
196
+        component={ProximoMainScreen}
197
+        options={{
198
+          title: i18n.t('screens.proximo.title'),
199
+        }}
200
+      />
201
+      <MainStack.Screen
202
+        name={MainRoutes.ProximoList}
203
+        component={ProximoListScreen}
204
+        options={{
205
+          title: i18n.t('screens.proximo.articleList'),
206
+        }}
207
+      />
208
+      <MainStack.Screen
209
+        name={MainRoutes.ProximoAbout}
210
+        component={ProximoAboutScreen}
211
+        options={{
212
+          title: i18n.t('screens.proximo.title'),
213
+        }}
214
+      />
215
+      <MainStack.Screen
216
+        name={MainRoutes.Profile}
217
+        component={ProfileScreen}
218
+        options={{
219
+          title: i18n.t('screens.profile.title'),
220
+        }}
221
+      />
222
+      <MainStack.Screen
223
+        name={MainRoutes.ClubList}
224
+        component={ClubListScreen}
225
+        options={{
226
+          title: i18n.t('screens.clubs.title'),
227
+        }}
228
+      />
229
+      <MainStack.Screen
230
+        name={MainRoutes.ClubInformation}
231
+        component={ClubDisplayScreen}
232
+        options={{
233
+          title: i18n.t('screens.clubs.details'),
234
+        }}
235
+      />
236
+      <MainStack.Screen
237
+        name={MainRoutes.ClubAbout}
238
+        component={ClubAboutScreen}
239
+        options={{
240
+          title: i18n.t('screens.clubs.title'),
241
+        }}
242
+      />
243
+      <MainStack.Screen
244
+        name={MainRoutes.EquipmentList}
245
+        component={EquipmentScreen}
246
+        options={{
247
+          title: i18n.t('screens.equipment.title'),
248
+        }}
249
+      />
250
+      <MainStack.Screen
251
+        name={MainRoutes.EquipmentRent}
252
+        component={EquipmentLendScreen}
253
+        options={{
254
+          title: i18n.t('screens.equipment.book'),
255
+        }}
256
+      />
257
+      <MainStack.Screen
258
+        name={MainRoutes.EquipmentConfirm}
259
+        component={EquipmentConfirmScreen}
260
+        options={{
261
+          title: i18n.t('screens.equipment.confirm'),
262
+        }}
263
+      />
264
+      <MainStack.Screen
265
+        name={MainRoutes.Vote}
266
+        component={VoteScreen}
267
+        options={{
268
+          title: i18n.t('screens.vote.title'),
269
+        }}
270
+      />
271
+      <MainStack.Screen
272
+        name={MainRoutes.Feedback}
273
+        component={BugReportScreen}
274
+        options={{
275
+          title: i18n.t('screens.feedback.title'),
276
+        }}
277
+      />
281
     </MainStack.Navigator>
278
     </MainStack.Navigator>
282
   );
279
   );
283
 }
280
 }

+ 149
- 159
src/navigation/TabNavigator.tsx View File

18
  */
18
  */
19
 
19
 
20
 import * as React from 'react';
20
 import * as React from 'react';
21
-import {
22
-  createStackNavigator,
23
-  TransitionPresets,
24
-} from '@react-navigation/stack';
21
+import { createStackNavigator } from '@react-navigation/stack';
25
 import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
22
 import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
26
 
23
 
27
 import { Title, useTheme } from 'react-native-paper';
24
 import { Title, useTheme } from 'react-native-paper';
28
-import { Platform, StyleSheet } from 'react-native';
25
+import { StyleSheet } from 'react-native';
29
 import i18n from 'i18n-js';
26
 import i18n from 'i18n-js';
30
-import { createCollapsibleStack } from 'react-navigation-collapsible';
31
 import { View } from 'react-native-animatable';
27
 import { View } from 'react-native-animatable';
32
 import HomeScreen from '../screens/Home/HomeScreen';
28
 import HomeScreen from '../screens/Home/HomeScreen';
33
 import PlanningScreen from '../screens/Planning/PlanningScreen';
29
 import PlanningScreen from '../screens/Planning/PlanningScreen';
44
 import WebsitesHomeScreen from '../screens/Services/ServicesScreen';
40
 import WebsitesHomeScreen from '../screens/Services/ServicesScreen';
45
 import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
41
 import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
46
 import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
42
 import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
47
-import {
48
-  CreateScreenCollapsibleStack,
49
-  getWebsiteStack,
50
-} from '../utils/CollapsibleUtils';
51
 import Mascot, { MASCOT_STYLE } from '../components/Mascot/Mascot';
43
 import Mascot, { MASCOT_STYLE } from '../components/Mascot/Mascot';
52
 
44
 
53
-const modalTransition =
54
-  Platform.OS === 'ios'
55
-    ? TransitionPresets.ModalPresentationIOS
56
-    : TransitionPresets.ModalTransition;
57
-
58
-const defaultScreenOptions = {
59
-  gestureEnabled: true,
60
-  cardOverlayEnabled: true,
61
-  ...modalTransition,
62
-};
63
-
64
 const styles = StyleSheet.create({
45
 const styles = StyleSheet.create({
65
   header: {
46
   header: {
66
     flexDirection: 'row',
47
     flexDirection: 'row',
79
 
60
 
80
 function ServicesStackComponent() {
61
 function ServicesStackComponent() {
81
   return (
62
   return (
82
-    <ServicesStack.Navigator
83
-      initialRouteName="index"
84
-      headerMode="screen"
85
-      screenOptions={defaultScreenOptions}
86
-    >
87
-      {CreateScreenCollapsibleStack(
88
-        'index',
89
-        ServicesStack,
90
-        WebsitesHomeScreen,
91
-        i18n.t('screens.services.title')
92
-      )}
93
-      {CreateScreenCollapsibleStack(
94
-        'services-section',
95
-        ServicesStack,
96
-        ServicesSectionScreen,
97
-        'SECTION'
98
-      )}
99
-      {CreateScreenCollapsibleStack(
100
-        'amicale-contact',
101
-        ServicesStack,
102
-        AmicaleContactScreen,
103
-        i18n.t('screens.amicaleAbout.title')
104
-      )}
63
+    <ServicesStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
64
+      <ServicesStack.Screen
65
+        name={'index'}
66
+        component={WebsitesHomeScreen}
67
+        options={{ title: i18n.t('screens.services.title') }}
68
+      />
69
+      <ServicesStack.Screen
70
+        name={'services-section'}
71
+        component={ServicesSectionScreen}
72
+        options={{ title: 'SECTION' }}
73
+      />
74
+      <ServicesStack.Screen
75
+        name={'amicale-contact'}
76
+        component={AmicaleContactScreen}
77
+        options={{ title: i18n.t('screens.amicaleAbout.title') }}
78
+      />
105
     </ServicesStack.Navigator>
79
     </ServicesStack.Navigator>
106
   );
80
   );
107
 }
81
 }
110
 
84
 
111
 function ProxiwashStackComponent() {
85
 function ProxiwashStackComponent() {
112
   return (
86
   return (
113
-    <ProxiwashStack.Navigator
114
-      initialRouteName="index"
115
-      headerMode="screen"
116
-      screenOptions={defaultScreenOptions}
117
-    >
118
-      {CreateScreenCollapsibleStack(
119
-        'index',
120
-        ProxiwashStack,
121
-        ProxiwashScreen,
122
-        i18n.t('screens.proxiwash.title')
123
-      )}
124
-      {CreateScreenCollapsibleStack(
125
-        'proxiwash-about',
126
-        ProxiwashStack,
127
-        ProxiwashAboutScreen,
128
-        i18n.t('screens.proxiwash.title')
129
-      )}
87
+    <ProxiwashStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
88
+      <ProxiwashStack.Screen
89
+        name={'index-contact'}
90
+        component={ProxiwashScreen}
91
+        options={{ title: i18n.t('screens.proxiwash.title') }}
92
+      />
93
+      <ProxiwashStack.Screen
94
+        name={'proxiwash-about'}
95
+        component={ProxiwashAboutScreen}
96
+        options={{ title: i18n.t('screens.proxiwash.title') }}
97
+      />
130
     </ProxiwashStack.Navigator>
98
     </ProxiwashStack.Navigator>
131
   );
99
   );
132
 }
100
 }
135
 
103
 
136
 function PlanningStackComponent() {
104
 function PlanningStackComponent() {
137
   return (
105
   return (
138
-    <PlanningStack.Navigator
139
-      initialRouteName="index"
140
-      headerMode="screen"
141
-      screenOptions={defaultScreenOptions}
142
-    >
106
+    <PlanningStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
143
       <PlanningStack.Screen
107
       <PlanningStack.Screen
144
-        name="index"
108
+        name={'index'}
145
         component={PlanningScreen}
109
         component={PlanningScreen}
146
         options={{ title: i18n.t('screens.planning.title') }}
110
         options={{ title: i18n.t('screens.planning.title') }}
147
       />
111
       />
148
-      {CreateScreenCollapsibleStack(
149
-        'planning-information',
150
-        PlanningStack,
151
-        PlanningDisplayScreen,
152
-        i18n.t('screens.planning.eventDetails')
153
-      )}
112
+      <PlanningStack.Screen
113
+        name={'planning-information'}
114
+        component={PlanningDisplayScreen}
115
+        options={{ title: i18n.t('screens.planning.eventDetails') }}
116
+      />
154
     </PlanningStack.Navigator>
117
     </PlanningStack.Navigator>
155
   );
118
   );
156
 }
119
 }
167
   }
130
   }
168
   const { colors } = useTheme();
131
   const { colors } = useTheme();
169
   return (
132
   return (
170
-    <HomeStack.Navigator
171
-      initialRouteName="index"
172
-      headerMode="screen"
173
-      screenOptions={defaultScreenOptions}
174
-    >
175
-      {createCollapsibleStack(
176
-        <HomeStack.Screen
177
-          name="index"
178
-          component={HomeScreen}
179
-          options={{
180
-            title: i18n.t('screens.home.title'),
181
-            headerStyle: {
182
-              backgroundColor: colors.surface,
183
-            },
184
-            headerTitle: () => (
185
-              <View style={styles.header}>
186
-                <Mascot
187
-                  style={styles.mascot}
188
-                  emotion={MASCOT_STYLE.RANDOM}
189
-                  animated
190
-                  entryAnimation={{
191
-                    animation: 'bounceIn',
192
-                    duration: 1000,
193
-                  }}
194
-                  loopAnimation={{
195
-                    animation: 'pulse',
196
-                    duration: 2000,
197
-                    iterationCount: 'infinite',
198
-                  }}
199
-                />
200
-                <Title style={styles.title}>
201
-                  {i18n.t('screens.home.title')}
202
-                </Title>
203
-              </View>
204
-            ),
205
-          }}
206
-          initialParams={params}
207
-        />,
208
-        {
209
-          collapsedColor: colors.surface,
210
-          useNativeDriver: true,
211
-        }
212
-      )}
133
+    <HomeStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
213
       <HomeStack.Screen
134
       <HomeStack.Screen
214
-        name="scanner"
135
+        name={'index'}
136
+        component={HomeScreen}
137
+        options={{
138
+          title: i18n.t('screens.home.title'),
139
+          headerStyle: {
140
+            backgroundColor: colors.surface,
141
+          },
142
+          headerTitle: (headerProps) => (
143
+            <View style={styles.header}>
144
+              <Mascot
145
+                style={styles.mascot}
146
+                emotion={MASCOT_STYLE.RANDOM}
147
+                animated
148
+                entryAnimation={{
149
+                  animation: 'bounceIn',
150
+                  duration: 1000,
151
+                }}
152
+                loopAnimation={{
153
+                  animation: 'pulse',
154
+                  duration: 2000,
155
+                  iterationCount: 'infinite',
156
+                }}
157
+              />
158
+              <Title style={styles.title}>{headerProps.children}</Title>
159
+            </View>
160
+          ),
161
+        }}
162
+        initialParams={params}
163
+      />
164
+      <HomeStack.Screen
165
+        name={'scanner'}
215
         component={ScannerScreen}
166
         component={ScannerScreen}
216
         options={{ title: i18n.t('screens.scanner.title') }}
167
         options={{ title: i18n.t('screens.scanner.title') }}
217
       />
168
       />
218
-
219
-      {CreateScreenCollapsibleStack(
220
-        'club-information',
221
-        HomeStack,
222
-        ClubDisplayScreen,
223
-        i18n.t('screens.clubs.details')
224
-      )}
225
-      {CreateScreenCollapsibleStack(
226
-        'feed-information',
227
-        HomeStack,
228
-        FeedItemScreen,
229
-        i18n.t('screens.home.feed')
230
-      )}
231
-      {CreateScreenCollapsibleStack(
232
-        'planning-information',
233
-        HomeStack,
234
-        PlanningDisplayScreen,
235
-        i18n.t('screens.planning.eventDetails')
236
-      )}
169
+      <HomeStack.Screen
170
+        name={'club-information'}
171
+        component={ClubDisplayScreen}
172
+        options={{
173
+          title: i18n.t('screens.clubs.details'),
174
+        }}
175
+      />
176
+      <HomeStack.Screen
177
+        name={'feed-information'}
178
+        component={FeedItemScreen}
179
+        options={{
180
+          title: i18n.t('screens.home.feed'),
181
+        }}
182
+      />
183
+      <HomeStack.Screen
184
+        name={'planning-information'}
185
+        component={PlanningDisplayScreen}
186
+        options={{
187
+          title: i18n.t('screens.planning.eventDetails'),
188
+        }}
189
+      />
237
     </HomeStack.Navigator>
190
     </HomeStack.Navigator>
238
   );
191
   );
239
 }
192
 }
242
 
195
 
243
 function PlanexStackComponent() {
196
 function PlanexStackComponent() {
244
   return (
197
   return (
245
-    <PlanexStack.Navigator
246
-      initialRouteName="index"
247
-      headerMode="screen"
248
-      screenOptions={defaultScreenOptions}
249
-    >
250
-      {getWebsiteStack(
251
-        'index',
252
-        PlanexStack,
253
-        PlanexScreen,
254
-        i18n.t('screens.planex.title')
255
-      )}
256
-      {CreateScreenCollapsibleStack(
257
-        'group-select',
258
-        PlanexStack,
259
-        GroupSelectionScreen,
260
-        ''
261
-      )}
198
+    <PlanexStack.Navigator initialRouteName={'index'} headerMode={'screen'}>
199
+      <PlanexStack.Screen
200
+        name={'index'}
201
+        component={PlanexScreen}
202
+        options={{
203
+          title: i18n.t('screens.planex.title'),
204
+        }}
205
+      />
206
+      <PlanexStack.Screen
207
+        name={'group-select'}
208
+        component={GroupSelectionScreen}
209
+        options={{
210
+          title: '',
211
+        }}
212
+      />
262
     </PlanexStack.Navigator>
213
     </PlanexStack.Navigator>
263
   );
214
   );
264
 }
215
 }
270
   defaultHomeData: { [key: string]: string };
221
   defaultHomeData: { [key: string]: string };
271
 };
222
 };
272
 
223
 
224
+const ICONS: {
225
+  [key: string]: {
226
+    normal: string;
227
+    focused: string;
228
+  };
229
+} = {
230
+  services: {
231
+    normal: 'account-circle-outline',
232
+    focused: 'account-circle',
233
+  },
234
+  proxiwash: {
235
+    normal: 'tshirt-crew-outline',
236
+    focused: 'tshirt-crew',
237
+  },
238
+  home: {
239
+    normal: '',
240
+    focused: '',
241
+  },
242
+  planning: {
243
+    normal: 'calendar-range-outline',
244
+    focused: 'calendar-range',
245
+  },
246
+  planex: {
247
+    normal: 'clock-outline',
248
+    focused: 'clock',
249
+  },
250
+};
251
+
273
 export default class TabNavigator extends React.Component<PropsType> {
252
 export default class TabNavigator extends React.Component<PropsType> {
274
   defaultRoute: string;
253
   defaultRoute: string;
275
   createHomeStackComponent: () => any;
254
   createHomeStackComponent: () => any;
287
   }
266
   }
288
 
267
 
289
   render() {
268
   render() {
269
+    const LABELS: {
270
+      [key: string]: string;
271
+    } = {
272
+      services: i18n.t('screens.services.title'),
273
+      proxiwash: i18n.t('screens.proxiwash.title'),
274
+      home: i18n.t('screens.home.title'),
275
+      planning: i18n.t('screens.planning.title'),
276
+      planex: i18n.t('screens.planex.title'),
277
+    };
290
     return (
278
     return (
291
       <Tab.Navigator
279
       <Tab.Navigator
292
         initialRouteName={this.defaultRoute}
280
         initialRouteName={this.defaultRoute}
293
-        tabBar={(tabProps) => <CustomTabBar {...tabProps} />}
281
+        tabBar={(tabProps) => (
282
+          <CustomTabBar {...tabProps} labels={LABELS} icons={ICONS} />
283
+        )}
294
       >
284
       >
295
         <Tab.Screen
285
         <Tab.Screen
296
-          name="services"
286
+          name={'services'}
297
           component={ServicesStackComponent}
287
           component={ServicesStackComponent}
298
           options={{ title: i18n.t('screens.services.title') }}
288
           options={{ title: i18n.t('screens.services.title') }}
299
         />
289
         />
300
         <Tab.Screen
290
         <Tab.Screen
301
-          name="proxiwash"
291
+          name={'proxiwash'}
302
           component={ProxiwashStackComponent}
292
           component={ProxiwashStackComponent}
303
           options={{ title: i18n.t('screens.proxiwash.title') }}
293
           options={{ title: i18n.t('screens.proxiwash.title') }}
304
         />
294
         />
305
         <Tab.Screen
295
         <Tab.Screen
306
-          name="home"
296
+          name={'home'}
307
           component={this.createHomeStackComponent}
297
           component={this.createHomeStackComponent}
308
           options={{ title: i18n.t('screens.home.title') }}
298
           options={{ title: i18n.t('screens.home.title') }}
309
         />
299
         />
310
         <Tab.Screen
300
         <Tab.Screen
311
-          name="planning"
301
+          name={'planning'}
312
           component={PlanningStackComponent}
302
           component={PlanningStackComponent}
313
           options={{ title: i18n.t('screens.planning.title') }}
303
           options={{ title: i18n.t('screens.planning.title') }}
314
         />
304
         />
315
         <Tab.Screen
305
         <Tab.Screen
316
-          name="planex"
306
+          name={'planex'}
317
           component={PlanexStackComponent}
307
           component={PlanexStackComponent}
318
           options={{ title: i18n.t('screens.planex.title') }}
308
           options={{ title: i18n.t('screens.planex.title') }}
319
         />
309
         />

+ 4
- 2
src/screens/Amicale/Clubs/ClubDisplayScreen.tsx View File

31
 import { StackNavigationProp } from '@react-navigation/stack';
31
 import { StackNavigationProp } from '@react-navigation/stack';
32
 import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
32
 import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
33
 import CustomHTML from '../../../components/Overrides/CustomHTML';
33
 import CustomHTML from '../../../components/Overrides/CustomHTML';
34
-import CustomTabBar from '../../../components/Tabbar/CustomTabBar';
34
+import CustomTabBar, {
35
+  TAB_BAR_HEIGHT,
36
+} from '../../../components/Tabbar/CustomTabBar';
35
 import type { ClubCategoryType, ClubType } from './ClubListScreen';
37
 import type { ClubCategoryType, ClubType } from './ClubListScreen';
36
 import { ERROR_TYPE } from '../../../utils/WebData';
38
 import { ERROR_TYPE } from '../../../utils/WebData';
37
 import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
39
 import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
174
     return (
176
     return (
175
       <Card
177
       <Card
176
         style={{
178
         style={{
177
-          marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20,
179
+          marginBottom: TAB_BAR_HEIGHT + 20,
178
           ...styles.card,
180
           ...styles.card,
179
         }}
181
         }}
180
       >
182
       >

+ 1
- 1
src/screens/Amicale/LoginScreen.tsx View File

441
           enabled
441
           enabled
442
           keyboardVerticalOffset={100}
442
           keyboardVerticalOffset={100}
443
         >
443
         >
444
-          <CollapsibleScrollView>
444
+          <CollapsibleScrollView headerColors={'transparent'}>
445
             <View style={GENERAL_STYLES.flex}>{this.getMainCard()}</View>
445
             <View style={GENERAL_STYLES.flex}>{this.getMainCard()}</View>
446
             <MascotPopup
446
             <MascotPopup
447
               visible={mascotDialogVisible}
447
               visible={mascotDialogVisible}

+ 4
- 4
src/screens/Home/FeedItemScreen.tsx View File

25
 import MaterialHeaderButtons, {
25
 import MaterialHeaderButtons, {
26
   Item,
26
   Item,
27
 } from '../../components/Overrides/CustomHeaderButton';
27
 } from '../../components/Overrides/CustomHeaderButton';
28
-import CustomTabBar from '../../components/Tabbar/CustomTabBar';
28
+import CustomTabBar, {
29
+  TAB_BAR_HEIGHT,
30
+} from '../../components/Tabbar/CustomTabBar';
29
 import type { FeedItemType } from './HomeScreen';
31
 import type { FeedItemType } from './HomeScreen';
30
 import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
32
 import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
31
 import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
33
 import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
117
             style={styles.button}
119
             style={styles.button}
118
           />
120
           />
119
         ) : null}
121
         ) : null}
120
-        <Card.Content
121
-          style={{ paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20 }}
122
-        >
122
+        <Card.Content style={{ paddingBottom: TAB_BAR_HEIGHT + 20 }}>
123
           {this.displayData.message !== undefined ? (
123
           {this.displayData.message !== undefined ? (
124
             <Autolink
124
             <Autolink
125
               text={this.displayData.message}
125
               text={this.displayData.message}

+ 4
- 2
src/screens/Home/ScannerScreen.tsx View File

26
 import { PERMISSIONS, request, RESULTS } from 'react-native-permissions';
26
 import { PERMISSIONS, request, RESULTS } from 'react-native-permissions';
27
 import URLHandler from '../../utils/URLHandler';
27
 import URLHandler from '../../utils/URLHandler';
28
 import AlertDialog from '../../components/Dialogs/AlertDialog';
28
 import AlertDialog from '../../components/Dialogs/AlertDialog';
29
-import CustomTabBar from '../../components/Tabbar/CustomTabBar';
29
+import CustomTabBar, {
30
+  TAB_BAR_HEIGHT,
31
+} from '../../components/Tabbar/CustomTabBar';
30
 import LoadingConfirmDialog from '../../components/Dialogs/LoadingConfirmDialog';
32
 import LoadingConfirmDialog from '../../components/Dialogs/LoadingConfirmDialog';
31
 import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
33
 import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
32
 import MascotPopup from '../../components/Mascot/MascotPopup';
34
 import MascotPopup from '../../components/Mascot/MascotPopup';
218
       <View
220
       <View
219
         style={{
221
         style={{
220
           ...styles.container,
222
           ...styles.container,
221
-          marginBottom: CustomTabBar.TAB_BAR_HEIGHT,
223
+          marginBottom: TAB_BAR_HEIGHT,
222
         }}
224
         }}
223
       >
225
       >
224
         {state.hasPermission ? this.getScanner() : this.getPermissionScreen()}
226
         {state.hasPermission ? this.getScanner() : this.getPermissionScreen()}

+ 23
- 35
src/screens/Planex/PlanexScreen.tsx View File

54
   dialogTitle: string | React.ReactNode;
54
   dialogTitle: string | React.ReactNode;
55
   dialogMessage: string;
55
   dialogMessage: string;
56
   currentGroup: PlanexGroupType;
56
   currentGroup: PlanexGroupType;
57
+  injectJS: string;
57
 };
58
 };
58
 
59
 
59
 const PLANEX_URL = 'http://planex.insa-toulouse.fr/';
60
 const PLANEX_URL = 'http://planex.insa-toulouse.fr/';
153
  * This screen uses a webview to render the page
154
  * This screen uses a webview to render the page
154
  */
155
  */
155
 class PlanexScreen extends React.Component<PropsType, StateType> {
156
 class PlanexScreen extends React.Component<PropsType, StateType> {
156
-  webScreenRef: { current: null | WebViewScreen };
157
-
158
   barRef: { current: null | AnimatedBottomBar };
157
   barRef: { current: null | AnimatedBottomBar };
159
 
158
 
160
-  customInjectedJS: string;
161
-
162
   /**
159
   /**
163
    * Defines custom injected JavaScript to improve the page display on mobile
160
    * Defines custom injected JavaScript to improve the page display on mobile
164
    */
161
    */
165
   constructor(props: PropsType) {
162
   constructor(props: PropsType) {
166
     super(props);
163
     super(props);
167
-    this.webScreenRef = React.createRef();
168
     this.barRef = React.createRef();
164
     this.barRef = React.createRef();
169
-    this.customInjectedJS = '';
170
     let currentGroupString = AsyncStorageManager.getString(
165
     let currentGroupString = AsyncStorageManager.getString(
171
       AsyncStorageManager.PREFERENCES.planexCurrentGroup.key
166
       AsyncStorageManager.PREFERENCES.planexCurrentGroup.key
172
     );
167
     );
184
       dialogTitle: '',
179
       dialogTitle: '',
185
       dialogMessage: '',
180
       dialogMessage: '',
186
       currentGroup,
181
       currentGroup,
182
+      injectJS: '',
187
     };
183
     };
188
-    this.generateInjectedJS(currentGroup.id);
189
   }
184
   }
190
 
185
 
191
   /**
186
   /**
197
   }
192
   }
198
 
193
 
199
   /**
194
   /**
200
-   * Only update the screen if the dark theme changed
201
-   *
202
-   * @param nextProps
203
-   * @returns {boolean}
204
-   */
205
-  shouldComponentUpdate(nextProps: PropsType): boolean {
206
-    const { props, state } = this;
207
-    if (nextProps.theme.dark !== props.theme.dark) {
208
-      this.generateInjectedJS(state.currentGroup.id);
209
-    }
210
-    return true;
211
-  }
212
-
213
-  /**
214
    * Gets the Webview, with an error view on top if no group is selected.
195
    * Gets the Webview, with an error view on top if no group is selected.
215
    *
196
    *
216
    * @returns {*}
197
    * @returns {*}
218
   getWebView() {
199
   getWebView() {
219
     const { props, state } = this;
200
     const { props, state } = this;
220
     const showWebview = state.currentGroup.id !== -1;
201
     const showWebview = state.currentGroup.id !== -1;
202
+    console.log(state.injectJS);
221
 
203
 
222
     return (
204
     return (
223
       <View style={GENERAL_STYLES.flex}>
205
       <View style={GENERAL_STYLES.flex}>
230
           />
212
           />
231
         ) : null}
213
         ) : null}
232
         <WebViewScreen
214
         <WebViewScreen
233
-          ref={this.webScreenRef}
234
-          navigation={props.navigation}
235
           url={PLANEX_URL}
215
           url={PLANEX_URL}
236
-          customJS={this.customInjectedJS}
216
+          initialJS={this.generateInjectedJS(this.state.currentGroup.id)}
217
+          injectJS={this.state.injectJS}
237
           onMessage={this.onMessage}
218
           onMessage={this.onMessage}
238
           onScroll={this.onScroll}
219
           onScroll={this.onScroll}
239
           showAdvancedControls={false}
220
           showAdvancedControls={false}
269
     } else {
250
     } else {
270
       command = `$('#calendar').fullCalendar('${action}', '${data}')`;
251
       command = `$('#calendar').fullCalendar('${action}', '${data}')`;
271
     }
252
     }
272
-    if (this.webScreenRef.current != null) {
273
-      this.webScreenRef.current.injectJavaScript(`${command};true;`);
274
-    } // Injected javascript must end with true
253
+    // String must resolve to true to prevent crash on iOS
254
+    command += ';true;';
255
+    // Change the injected
256
+    if (command === this.state.injectJS) {
257
+      command += ';true;';
258
+    }
259
+    this.setState({ injectJS: command });
275
   };
260
   };
276
 
261
 
277
   /**
262
   /**
373
       group
358
       group
374
     );
359
     );
375
     navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
360
     navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
376
-    this.generateInjectedJS(group.id);
377
   }
361
   }
378
 
362
 
379
   /**
363
   /**
382
    * @param groupID The current group selected
366
    * @param groupID The current group selected
383
    */
367
    */
384
   generateInjectedJS(groupID: number) {
368
   generateInjectedJS(groupID: number) {
385
-    this.customInjectedJS = `$(document).ready(function() {${OBSERVE_MUTATIONS_INJECTED}${FULL_CALENDAR_SETTINGS}displayAde(${groupID});${
386
-      // Reset Ade
387
-      DateManager.isWeekend(new Date()) ? 'calendar.next()' : ''
388
-    }${INJECT_STYLE}`;
389
-
369
+    let customInjectedJS = `$(document).ready(function() {
370
+      ${OBSERVE_MUTATIONS_INJECTED}
371
+      ${FULL_CALENDAR_SETTINGS}
372
+      displayAde(${groupID});
373
+      ${INJECT_STYLE}`;
374
+    if (DateManager.isWeekend(new Date())) {
375
+      customInjectedJS += `calendar.next();`;
376
+    }
390
     if (ThemeManager.getNightMode()) {
377
     if (ThemeManager.getNightMode()) {
391
-      this.customInjectedJS += `$('head').append('<style>${CUSTOM_CSS_DARK}</style>');`;
378
+      customInjectedJS += `$('head').append('<style>${CUSTOM_CSS_DARK}</style>');`;
392
     }
379
     }
393
 
380
 
394
-    this.customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
381
+    customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
382
+    return customInjectedJS;
395
   }
383
   }
396
 
384
 
397
   render() {
385
   render() {

+ 4
- 4
src/screens/Planning/PlanningDisplayScreen.tsx View File

28
 import { apiRequest, ERROR_TYPE } from '../../utils/WebData';
28
 import { apiRequest, ERROR_TYPE } from '../../utils/WebData';
29
 import ErrorView from '../../components/Screens/ErrorView';
29
 import ErrorView from '../../components/Screens/ErrorView';
30
 import CustomHTML from '../../components/Overrides/CustomHTML';
30
 import CustomHTML from '../../components/Overrides/CustomHTML';
31
-import CustomTabBar from '../../components/Tabbar/CustomTabBar';
31
+import CustomTabBar, {
32
+  TAB_BAR_HEIGHT,
33
+} from '../../components/Tabbar/CustomTabBar';
32
 import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
34
 import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
33
 import type { PlanningEventType } from '../../utils/Planning';
35
 import type { PlanningEventType } from '../../utils/Planning';
34
 import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
36
 import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
145
         ) : null}
147
         ) : null}
146
 
148
 
147
         {displayData.description !== null ? (
149
         {displayData.description !== null ? (
148
-          <Card.Content
149
-            style={{ paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20 }}
150
-          >
150
+          <Card.Content style={{ paddingBottom: TAB_BAR_HEIGHT + 20 }}>
151
             <CustomHTML html={displayData.description} />
151
             <CustomHTML html={displayData.description} />
152
           </Card.Content>
152
           </Card.Content>
153
         ) : (
153
         ) : (

+ 4
- 2
src/screens/Services/Proximo/ProximoAboutScreen.tsx View File

21
 import { Image, StyleSheet, View } from 'react-native';
21
 import { Image, StyleSheet, View } from 'react-native';
22
 import i18n from 'i18n-js';
22
 import i18n from 'i18n-js';
23
 import { Card, Avatar, Paragraph, Text } from 'react-native-paper';
23
 import { Card, Avatar, Paragraph, Text } from 'react-native-paper';
24
-import CustomTabBar from '../../../components/Tabbar/CustomTabBar';
24
+import CustomTabBar, {
25
+  TAB_BAR_HEIGHT,
26
+} from '../../../components/Tabbar/CustomTabBar';
25
 import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
27
 import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
26
 
28
 
27
 const LOGO = 'https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png';
29
 const LOGO = 'https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png';
72
       <Card
74
       <Card
73
         style={{
75
         style={{
74
           ...styles.card,
76
           ...styles.card,
75
-          marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20,
77
+          marginBottom: TAB_BAR_HEIGHT + 20,
76
         }}
78
         }}
77
       >
79
       >
78
         <Card.Title
80
         <Card.Title

+ 22
- 16
src/screens/Services/WebsiteScreen.tsx View File

23
 import AvailableWebsites from '../../constants/AvailableWebsites';
23
 import AvailableWebsites from '../../constants/AvailableWebsites';
24
 import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
24
 import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
25
 
25
 
26
-type PropsType = {
26
+type Props = {
27
   navigation: StackNavigationProp<any>;
27
   navigation: StackNavigationProp<any>;
28
   route: { params: { host: string; path: string | null; title: string } };
28
   route: { params: { host: string; path: string | null; title: string } };
29
 };
29
 };
30
 
30
 
31
+type State = {
32
+  url: string;
33
+};
34
+
31
 const ENABLE_MOBILE_STRING =
35
 const ENABLE_MOBILE_STRING =
32
   '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
36
   '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
33
 
37
 
43
   '</a>' +
47
   '</a>' +
44
   '</div>';
48
   '</div>';
45
 
49
 
46
-class WebsiteScreen extends React.Component<PropsType> {
47
-  fullUrl: string;
48
-
50
+class WebsiteScreen extends React.Component<Props, State> {
49
   injectedJS: { [key: string]: string };
51
   injectedJS: { [key: string]: string };
50
 
52
 
51
-  customPaddingFunctions: { [key: string]: (padding: string) => string };
53
+  customPaddingFunctions: { [key: string]: (padding: number) => string };
52
 
54
 
53
   host: string;
55
   host: string;
54
 
56
 
55
-  constructor(props: PropsType) {
57
+  constructor(props: Props) {
56
     super(props);
58
     super(props);
57
-    this.fullUrl = '';
59
+    this.state = {
60
+      url: '',
61
+    };
58
     this.host = '';
62
     this.host = '';
59
     props.navigation.addListener('focus', this.onScreenFocus);
63
     props.navigation.addListener('focus', this.onScreenFocus);
60
     this.injectedJS = {};
64
     this.injectedJS = {};
70
       `$(".hero-unit-form").append("${BIB_BACK_BUTTON}");true;`;
74
       `$(".hero-unit-form").append("${BIB_BACK_BUTTON}");true;`;
71
 
75
 
72
     this.customPaddingFunctions[AvailableWebsites.websites.BLUEMIND] = (
76
     this.customPaddingFunctions[AvailableWebsites.websites.BLUEMIND] = (
73
-      padding: string
77
+      padding: number
74
     ): string => {
78
     ): string => {
75
       return (
79
       return (
76
         `$('head').append('${ENABLE_MOBILE_STRING}');` +
80
         `$('head').append('${ENABLE_MOBILE_STRING}');` +
79
       );
83
       );
80
     };
84
     };
81
     this.customPaddingFunctions[AvailableWebsites.websites.WIKETUD] = (
85
     this.customPaddingFunctions[AvailableWebsites.websites.WIKETUD] = (
82
-      padding: string
86
+      padding: number
83
     ): string => {
87
     ): string => {
84
       return (
88
       return (
85
         `$('#p-logo-text').css('top', 10 + ${padding});` +
89
         `$('#p-logo-text').css('top', 10 + ${padding});` +
99
    */
103
    */
100
   handleNavigationParams() {
104
   handleNavigationParams() {
101
     const { route, navigation } = this.props;
105
     const { route, navigation } = this.props;
106
+
102
     if (route.params != null) {
107
     if (route.params != null) {
108
+      console.log(route.params);
103
       this.host = route.params.host;
109
       this.host = route.params.host;
104
       let { path } = route.params;
110
       let { path } = route.params;
105
       const { title } = route.params;
111
       const { title } = route.params;
112
+      let fullUrl = '';
106
       if (this.host != null && path != null) {
113
       if (this.host != null && path != null) {
107
         path = path.replace(this.host, '');
114
         path = path.replace(this.host, '');
108
-        this.fullUrl = this.host + path;
115
+        fullUrl = this.host + path;
109
       } else {
116
       } else {
110
-        this.fullUrl = this.host;
117
+        fullUrl = this.host;
111
       }
118
       }
119
+      this.setState({ url: fullUrl });
112
 
120
 
113
       if (title != null) {
121
       if (title != null) {
114
         navigation.setOptions({ title });
122
         navigation.setOptions({ title });
117
   }
125
   }
118
 
126
 
119
   render() {
127
   render() {
120
-    const { navigation } = this.props;
121
     let injectedJavascript = '';
128
     let injectedJavascript = '';
122
     let customPadding = null;
129
     let customPadding = null;
123
     if (this.host != null && this.injectedJS[this.host] != null) {
130
     if (this.host != null && this.injectedJS[this.host] != null) {
127
       customPadding = this.customPaddingFunctions[this.host];
134
       customPadding = this.customPaddingFunctions[this.host];
128
     }
135
     }
129
 
136
 
130
-    if (this.fullUrl != null) {
137
+    if (this.state.url) {
131
       return (
138
       return (
132
         <WebViewScreen
139
         <WebViewScreen
133
-          navigation={navigation}
134
-          url={this.fullUrl}
135
-          customJS={injectedJavascript}
140
+          url={this.state.url}
141
+          initialJS={injectedJavascript}
136
           customPaddingFunction={customPadding}
142
           customPaddingFunction={customPadding}
137
         />
143
         />
138
       );
144
       );

+ 157
- 0
src/screens/Test.tsx View File

1
+import { useNavigation } from '@react-navigation/core';
2
+import { StackNavigationProp } from '@react-navigation/stack';
3
+import React from 'react';
4
+import { Animated, View } from 'react-native';
5
+import { Text } from 'react-native-paper';
6
+import {
7
+  Collapsible,
8
+  useCollapsibleHeader,
9
+} from 'react-navigation-collapsible';
10
+import CollapsibleFlatList from '../components/Collapsible/CollapsibleFlatList';
11
+import FeedItem from '../components/Home/FeedItem';
12
+import WebSectionList from '../components/Screens/WebSectionList';
13
+import withCollapsible from '../utils/withCollapsible';
14
+import { FeedItemType } from './Home/HomeScreen';
15
+import i18n from 'i18n-js';
16
+import CollapsibleSectionList from '../components/Collapsible/CollapsibleSectionList';
17
+
18
+// export default function Test() {
19
+//   const {
20
+//     onScroll /* Event handler */,
21
+//     onScrollWithListener /* Event handler creator */,
22
+//     containerPaddingTop /* number */,
23
+//     scrollIndicatorInsetTop /* number */,
24
+//     /* Animated.AnimatedValue contentOffset from scrolling */
25
+//     positionY /* 0.0 ~ length of scrollable component (contentOffset)
26
+//     /* Animated.AnimatedInterpolation by scrolling */,
27
+//     translateY /* 0.0 ~ -headerHeight */,
28
+//     progress /* 0.0 ~ 1.0 */,
29
+//     opacity /* 1.0 ~ 0.0 */,
30
+//   } = useCollapsibleHeader();
31
+
32
+//   const renderItem = () => {
33
+//     return (
34
+//       <View
35
+//         style={{
36
+//           marginTop: 50,
37
+//           marginBottom: 50,
38
+//         }}
39
+//       >
40
+//         <Text>TEST</Text>
41
+//       </View>
42
+//     );
43
+//   };
44
+
45
+//   return (
46
+//     <Animated.FlatList
47
+//       onScroll={onScroll}
48
+//       contentContainerStyle={{ paddingTop: containerPaddingTop }}
49
+//       scrollIndicatorInsets={{ top: scrollIndicatorInsetTop }}
50
+//       data={[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
51
+//       renderItem={renderItem}
52
+//     />
53
+//   );
54
+// }
55
+
56
+type Props = {
57
+  navigation: StackNavigationProp<any>;
58
+  collapsibleStack: Collapsible;
59
+};
60
+
61
+const DATA_URL =
62
+  'https://etud.insa-toulouse.fr/~amicale_app/v2/dashboard/dashboard_data.json';
63
+const FEED_ITEM_HEIGHT = 500;
64
+
65
+const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
66
+class Test extends React.Component<Props> {
67
+  createDataset = (): Array<{
68
+    title: string;
69
+    data: [] | Array<FeedItemType>;
70
+    id: string;
71
+  }> => {
72
+    return [
73
+      {
74
+        title: 'title',
75
+        data: [
76
+          {
77
+            id: '0',
78
+            message: 'message',
79
+            image: '',
80
+            link: '',
81
+            page_id: 'amicale.deseleves',
82
+            time: 0,
83
+            url: '',
84
+            video: '',
85
+          },
86
+          {
87
+            id: '1',
88
+            message: 'message',
89
+            image: '',
90
+            link: '',
91
+            page_id: 'amicale.deseleves',
92
+            time: 0,
93
+            url: '',
94
+            video: '',
95
+          },
96
+          {
97
+            id: '2',
98
+            message: 'message',
99
+            image: '',
100
+            link: '',
101
+            page_id: 'amicale.deseleves',
102
+            time: 0,
103
+            url: '',
104
+            video: '',
105
+          },
106
+        ],
107
+        id: '0',
108
+      },
109
+    ];
110
+  };
111
+  getRenderItem = ({ item }: { item: FeedItemType }) => (
112
+    <FeedItem item={item} height={FEED_ITEM_HEIGHT} />
113
+  );
114
+
115
+  render() {
116
+    const renderItem = () => {
117
+      return (
118
+        <View
119
+          style={{
120
+            marginTop: 50,
121
+            marginBottom: 50,
122
+          }}
123
+        >
124
+          <Text>TEST</Text>
125
+        </View>
126
+      );
127
+    };
128
+
129
+    const props = this.props;
130
+    // return (
131
+    //   <CollapsibleFlatList
132
+    //     data={[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
133
+    //     renderItem={renderItem}
134
+    //   />
135
+    // );
136
+    // return (
137
+    //   <CollapsibleSectionList
138
+    //     sections={[{ title: '', data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }]}
139
+    //     renderItem={renderItem}
140
+    //   />
141
+    // );
142
+    return (
143
+      <WebSectionList
144
+        navigation={props.navigation}
145
+        createDataset={this.createDataset}
146
+        autoRefreshTime={REFRESH_TIME}
147
+        refreshOnFocus
148
+        fetchUrl={DATA_URL}
149
+        renderItem={this.getRenderItem}
150
+        itemHeight={FEED_ITEM_HEIGHT}
151
+        showError={false}
152
+      />
153
+    );
154
+  }
155
+}
156
+
157
+export default Test;

+ 16
- 0
src/utils/CollapsibleContext.ts View File

1
+import React, { useContext } from 'react';
2
+import { Collapsible } from 'react-navigation-collapsible';
3
+
4
+export type CollapsibleContextType = {
5
+  collapsible?: Collapsible;
6
+  setCollapsible: (collapsible: Collapsible) => void;
7
+};
8
+
9
+export const CollapsibleContext = React.createContext<CollapsibleContextType>({
10
+  collapsible: undefined,
11
+  setCollapsible: () => undefined,
12
+});
13
+
14
+export function useCollapsible() {
15
+  return useContext(CollapsibleContext);
16
+}

+ 0
- 101
src/utils/CollapsibleUtils.tsx View File

1
-/*
2
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
3
- *
4
- * This file is part of Campus INSAT.
5
- *
6
- * Campus INSAT is free software: you can redistribute it and/or modify
7
- *  it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Campus INSAT is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
- */
19
-
20
-import * as React from 'react';
21
-import { useTheme } from 'react-native-paper';
22
-import { createCollapsibleStack } from 'react-navigation-collapsible';
23
-import StackNavigator, {
24
-  StackNavigationOptions,
25
-} from '@react-navigation/stack';
26
-import { StackNavigationState, TypedNavigator } from '@react-navigation/native';
27
-import { StackNavigationEventMap } from '@react-navigation/stack/lib/typescript/src/types';
28
-
29
-type StackNavigatorType = import('@react-navigation/native').TypedNavigator<
30
-  Record<string, object | undefined>,
31
-  StackNavigationState<any>,
32
-  StackNavigationOptions,
33
-  StackNavigationEventMap,
34
-  typeof StackNavigator
35
->;
36
-
37
-/**
38
- * Creates a navigation stack with the collapsible library, allowing the header to collapse on scroll.
39
- *
40
- * Please use the getWebsiteStack function if your screen uses a webview as their main component as it needs special parameters.
41
- *
42
- * @param name The screen name in the navigation stack
43
- * @param Stack The stack component
44
- * @param component The screen component
45
- * @param title The screen title shown in the header (needs to be translated)
46
- * @param useNativeDriver Whether to use the native driver for animations.
47
- * Set to false if the screen uses a webview as this component does not support native driver.
48
- * In all other cases, set it to true for increase performance.
49
- * @param options Screen options to use, or null if no options are necessary.
50
- * @param headerColor The color of the header. Uses default color if not specified
51
- * @returns {JSX.Element}
52
- */
53
-export function CreateScreenCollapsibleStack(
54
-  name: string,
55
-  Stack: TypedNavigator<any, any, any, any, any>,
56
-  component: React.ComponentType<any>,
57
-  title: string,
58
-  useNativeDriver: boolean = true,
59
-  options: StackNavigationOptions = {},
60
-  headerColor?: string
61
-) {
62
-  const { colors } = useTheme();
63
-  return createCollapsibleStack(
64
-    <Stack.Screen
65
-      name={name}
66
-      component={component}
67
-      options={{
68
-        title,
69
-        headerStyle: {
70
-          backgroundColor: headerColor != null ? headerColor : colors.surface,
71
-        },
72
-        ...options,
73
-      }}
74
-    />,
75
-    {
76
-      collapsedColor: headerColor != null ? headerColor : colors.surface,
77
-      useNativeDriver: useNativeDriver, // native driver does not work with webview
78
-    }
79
-  );
80
-}
81
-
82
-/**
83
- * Creates a navigation stack with the collapsible library, allowing the header to collapse on scroll.
84
- *
85
- * This is a preset for screens using a webview as their main component, as it uses special parameters to work.
86
- * (aka a dirty workaround)
87
- *
88
- * @param name
89
- * @param Stack
90
- * @param component
91
- * @param title
92
- * @returns {JSX.Element}
93
- */
94
-export function getWebsiteStack(
95
-  name: string,
96
-  Stack: TypedNavigator<any, any, any, any, any>,
97
-  component: React.ComponentType<any>,
98
-  title: string
99
-) {
100
-  return CreateScreenCollapsibleStack(name, Stack, component, title, false);
101
-}

+ 23
- 24
src/utils/withCollapsible.tsx View File

18
  */
18
  */
19
 
19
 
20
 import * as React from 'react';
20
 import * as React from 'react';
21
-import { useCollapsibleStack } from 'react-navigation-collapsible';
21
+import { useTheme } from 'react-native-paper';
22
+import {
23
+  useCollapsibleHeader,
24
+  UseCollapsibleOptions,
25
+} from 'react-navigation-collapsible';
22
 
26
 
23
-/**
24
- * Function used to manipulate Collapsible Hooks from a class.
25
- *
26
- * Usage :
27
- *
28
- * export withCollapsible(Component)
29
- *
30
- * replacing Component with the one you want to use.
31
- * This component will then receive the collapsibleStack prop.
32
- *
33
- * @param Component The component to use Collapsible with
34
- * @returns {React.ComponentType<any>}
35
- */
36
-export default function withCollapsible(Component: React.ComponentType<any>) {
37
-  return React.forwardRef((props: any, ref: any) => {
38
-    return (
39
-      <Component
40
-        collapsibleStack={useCollapsibleStack()}
41
-        ref={ref}
42
-        {...props}
43
-      />
44
-    );
45
-  });
27
+export default function withCollapsible<T>(
28
+  Component: React.ComponentType<any>,
29
+  options?: UseCollapsibleOptions
30
+) {
31
+  return function WrappedComponent(props: T) {
32
+    const theme = useTheme();
33
+    if (!options?.config?.collapsedColor) {
34
+      options = {
35
+        ...options,
36
+        config: {
37
+          ...options?.config,
38
+          collapsedColor: theme.colors.surface,
39
+        },
40
+      };
41
+    }
42
+    const collapsible = useCollapsibleHeader(options);
43
+    return <Component {...props} collapsible={collapsible} />;
44
+  };
46
 }
45
 }

Loading…
Cancel
Save