Browse Source

Added support for auto hide tab bar and changed auto hide component animations

Arnaud Vergnet 4 years ago
parent
commit
91853092be

+ 23
- 6
src/components/Custom/AnimatedBottomBar.js View File

@@ -3,8 +3,9 @@
3 3
 import * as React from 'react';
4 4
 import {StyleSheet, View} from "react-native";
5 5
 import {FAB, IconButton, Surface, withTheme} from "react-native-paper";
6
-import AutoHideComponent from "./AutoHideComponent";
6
+import AutoHideHandler from "../../utils/AutoHideHandler";
7 7
 import * as Animatable from 'react-native-animatable';
8
+import CustomTabBar from "../Tabbar/CustomTabBar";
8 9
 
9 10
 const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
10 11
 
@@ -28,6 +29,7 @@ const DISPLAY_MODES = {
28 29
 class AnimatedBottomBar extends React.Component<Props, State> {
29 30
 
30 31
     ref: Object;
32
+    hideHandler: AutoHideHandler;
31 33
 
32 34
     displayModeIcons: Object;
33 35
 
@@ -38,6 +40,9 @@ class AnimatedBottomBar extends React.Component<Props, State> {
38 40
     constructor() {
39 41
         super();
40 42
         this.ref = React.createRef();
43
+        this.hideHandler = new AutoHideHandler(false);
44
+        this.hideHandler.addListener(this.onHideChange);
45
+
41 46
         this.displayModeIcons = {};
42 47
         this.displayModeIcons[DISPLAY_MODES.DAY] = "calendar-text";
43 48
         this.displayModeIcons[DISPLAY_MODES.WEEK] = "calendar-week";
@@ -49,8 +54,17 @@ class AnimatedBottomBar extends React.Component<Props, State> {
49 54
             || (nextState.currentMode !== this.state.currentMode);
50 55
     }
51 56
 
57
+    onHideChange = (shouldHide: boolean) => {
58
+        if (this.ref.current) {
59
+            if (shouldHide)
60
+                this.ref.current.fadeOutDown(600);
61
+            else
62
+                this.ref.current.fadeInUp(500);
63
+        }
64
+    }
65
+
52 66
     onScroll = (event: Object) => {
53
-        this.ref.current.onScroll(event);
67
+        this.hideHandler.onScroll(event);
54 68
     };
55 69
 
56 70
     changeDisplayMode = () => {
@@ -74,9 +88,13 @@ class AnimatedBottomBar extends React.Component<Props, State> {
74 88
     render() {
75 89
         const buttonColor = this.props.theme.colors.primary;
76 90
         return (
77
-            <AutoHideComponent
91
+            <Animatable.View
78 92
                 ref={this.ref}
79
-                style={styles.container}>
93
+                useNativeDriver
94
+                style={{
95
+                    ...styles.container,
96
+                    bottom: 10 + CustomTabBar.TAB_BAR_HEIGHT
97
+                }}>
80 98
                 <Surface style={styles.surface}>
81 99
                     <View style={styles.fabContainer}>
82 100
                         <AnimatedFAB
@@ -114,7 +132,7 @@ class AnimatedBottomBar extends React.Component<Props, State> {
114 132
                             onPress={() => this.props.onPress('next', undefined)}/>
115 133
                     </View>
116 134
                 </Surface>
117
-            </AutoHideComponent>
135
+            </Animatable.View>
118 136
         );
119 137
     }
120 138
 }
@@ -123,7 +141,6 @@ const styles = StyleSheet.create({
123 141
     container: {
124 142
         position: 'absolute',
125 143
         left: '5%',
126
-        bottom: 10,
127 144
         width: '90%',
128 145
     },
129 146
     surface: {

+ 29
- 16
src/components/Custom/AnimatedFAB.js View File

@@ -3,42 +3,56 @@
3 3
 import * as React from 'react';
4 4
 import {StyleSheet} from "react-native";
5 5
 import {FAB} from "react-native-paper";
6
-import {AnimatedValue} from "react-native-reanimated";
7
-import AutoHideComponent from "./AutoHideComponent";
6
+import AutoHideHandler from "../../utils/AutoHideHandler";
7
+import * as Animatable from 'react-native-animatable';
8
+import CustomTabBar from "../Tabbar/CustomTabBar";
8 9
 
9 10
 type Props = {
11
+    navigation: Object,
10 12
     icon: string,
11 13
     onPress: Function,
12 14
 }
13 15
 
14
-type State = {
15
-    fabPosition: AnimatedValue
16
-}
16
+const AnimatedFab = Animatable.createAnimatableComponent(FAB);
17 17
 
18
-export default class AnimatedFAB extends React.Component<Props, State> {
18
+export default class AnimatedFAB extends React.Component<Props> {
19 19
 
20 20
     ref: Object;
21
+    hideHandler: AutoHideHandler;
21 22
 
22 23
     constructor() {
23 24
         super();
24 25
         this.ref = React.createRef();
26
+        this.hideHandler = new AutoHideHandler(false);
27
+        this.hideHandler.addListener(this.onHideChange);
25 28
     }
26 29
 
27 30
     onScroll = (event: Object) => {
28
-        this.ref.current.onScroll(event);
31
+        this.hideHandler.onScroll(event);
29 32
     };
30 33
 
34
+    onHideChange = (shouldHide: boolean) => {
35
+        if (this.ref.current) {
36
+            if (shouldHide)
37
+                this.ref.current.bounceOutDown(1000);
38
+            else
39
+                this.ref.current.bounceInUp(1000);
40
+        }
41
+    }
42
+
31 43
     render() {
32 44
         return (
33
-            <AutoHideComponent
45
+            <AnimatedFab
34 46
                 ref={this.ref}
35
-                style={styles.fab}>
36
-                <FAB
37
-                    icon={this.props.icon}
38
-                    onPress={this.props.onPress}
39
-                />
40
-            </AutoHideComponent>
41
-           );
47
+                useNativeDriver
48
+                icon={this.props.icon}
49
+                onPress={this.props.onPress}
50
+                style={{
51
+                    ...styles.fab,
52
+                    bottom: CustomTabBar.TAB_BAR_HEIGHT
53
+                }}
54
+            />
55
+        );
42 56
     }
43 57
 }
44 58
 
@@ -47,6 +61,5 @@ const styles = StyleSheet.create({
47 61
         position: 'absolute',
48 62
         margin: 16,
49 63
         right: 0,
50
-        bottom: 0,
51 64
     },
52 65
 });

+ 0
- 78
src/components/Custom/AutoHideComponent.js View File

@@ -1,78 +0,0 @@
1
-// @flow
2
-
3
-import * as React from 'react';
4
-import {Animated} from 'react-native'
5
-import {AnimatedValue} from "react-native-reanimated";
6
-
7
-type Props = {
8
-    children: React.Node,
9
-    style: Object,
10
-}
11
-
12
-type State = {
13
-    fabPosition: AnimatedValue
14
-}
15
-
16
-export default class AutoHideComponent extends React.Component<Props, State> {
17
-
18
-    isAnimationDownPlaying: boolean;
19
-    isAnimationUpPlaying: boolean;
20
-
21
-    downAnimation;
22
-    upAnimation;
23
-
24
-    lastOffset: number;
25
-
26
-    state = {
27
-        fabPosition: new Animated.Value(0),
28
-    };
29
-
30
-    constructor() {
31
-        super();
32
-    }
33
-
34
-    onScroll({nativeEvent}: Object) {
35
-        const speed = nativeEvent.contentOffset.y < 0 ? 0 : this.lastOffset - nativeEvent.contentOffset.y;
36
-        if (speed < -5) { // Go down
37
-            if (!this.isAnimationDownPlaying) {
38
-                this.isAnimationDownPlaying = true;
39
-                if (this.isAnimationUpPlaying)
40
-                    this.upAnimation.stop();
41
-                this.downAnimation = Animated.spring(this.state.fabPosition, {
42
-                    toValue: 100,
43
-                    duration: 50,
44
-                    useNativeDriver: true,
45
-                });
46
-                this.downAnimation.start(() => {
47
-                    this.isAnimationDownPlaying = false
48
-                });
49
-            }
50
-        } else if (speed > 5) { // Go up
51
-            if (!this.isAnimationUpPlaying) {
52
-                this.isAnimationUpPlaying = true;
53
-                if (this.isAnimationDownPlaying)
54
-                    this.downAnimation.stop();
55
-                this.upAnimation = Animated.spring(this.state.fabPosition, {
56
-                    toValue: 0,
57
-                    duration: 50,
58
-                    useNativeDriver: true,
59
-                });
60
-                this.upAnimation.start(() => {
61
-                    this.isAnimationUpPlaying = false
62
-                });
63
-            }
64
-        }
65
-        this.lastOffset = nativeEvent.contentOffset.y;
66
-    }
67
-
68
-    render() {
69
-        return (
70
-            <Animated.View style={{
71
-                ...this.props.style,
72
-                transform: [{translateY: this.state.fabPosition}]
73
-            }}>
74
-                {this.props.children}
75
-            </Animated.View>
76
-        );
77
-    }
78
-}

+ 17
- 1
src/components/Lists/WebSectionList.js View File

@@ -9,6 +9,8 @@ import ErrorView from "../Custom/ErrorView";
9 9
 import BasicLoadingScreen from "../Custom/BasicLoadingScreen";
10 10
 import {withCollapsible} from "../../utils/withCollapsible";
11 11
 import * as Animatable from 'react-native-animatable';
12
+import AutoHideHandler from "../../utils/AutoHideHandler";
13
+import CustomTabBar from "../Tabbar/CustomTabBar";
12 14
 
13 15
 type Props = {
14 16
     navigation: Object,
@@ -52,6 +54,7 @@ class WebSectionList extends React.PureComponent<Props, State> {
52 54
 
53 55
     refreshInterval: IntervalID;
54 56
     lastRefresh: Date;
57
+    hideHandler: AutoHideHandler;
55 58
 
56 59
     state = {
57 60
         refreshing: false,
@@ -72,6 +75,8 @@ class WebSectionList extends React.PureComponent<Props, State> {
72 75
         this.onFetchSuccess = this.onFetchSuccess.bind(this);
73 76
         this.onFetchError = this.onFetchError.bind(this);
74 77
         this.getEmptySectionHeader = this.getEmptySectionHeader.bind(this);
78
+        this.hideHandler = new AutoHideHandler(false);
79
+        this.hideHandler.addListener(this.onHideChange);
75 80
     }
76 81
 
77 82
     /**
@@ -199,6 +204,16 @@ class WebSectionList extends React.PureComponent<Props, State> {
199 204
         );
200 205
     }
201 206
 
207
+    onScroll = (event: Object) => {
208
+        this.hideHandler.onScroll(event);
209
+        if (this.props.onScroll)
210
+            this.props.onScroll(event);
211
+    }
212
+
213
+    onHideChange = (shouldHide: boolean) => {
214
+        this.props.navigation.setParams({hideTabBar: shouldHide});
215
+    }
216
+
202 217
     render() {
203 218
         let dataset = [];
204 219
         if (this.state.fetchedData !== undefined)
@@ -233,9 +248,10 @@ class WebSectionList extends React.PureComponent<Props, State> {
233 248
                     }
234 249
                     getItemLayout={this.props.itemHeight !== null ? this.itemLayout : undefined}
235 250
                     // Animations
236
-                    onScroll={onScrollWithListener(this.props.onScroll)}
251
+                    onScroll={onScrollWithListener(this.onScroll)}
237 252
                     contentContainerStyle={{
238 253
                         paddingTop: containerPaddingTop,
254
+                        paddingBottom: CustomTabBar.TAB_BAR_HEIGHT,
239 255
                         minHeight: '100%'
240 256
                     }}
241 257
                     scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}

+ 15
- 1
src/components/Screens/WebViewScreen.js View File

@@ -11,6 +11,7 @@ import {Linking} from "expo";
11 11
 import i18n from 'i18n-js';
12 12
 import {Animated, BackHandler} from "react-native";
13 13
 import {withCollapsible} from "../../utils/withCollapsible";
14
+import AutoHideHandler from "../../utils/AutoHideHandler";
14 15
 
15 16
 type Props = {
16 17
     navigation: Object,
@@ -33,6 +34,7 @@ class WebViewScreen extends React.PureComponent<Props> {
33 34
     };
34 35
 
35 36
     webviewRef: Object;
37
+    hideHandler: AutoHideHandler;
36 38
 
37 39
     canGoBack: boolean;
38 40
 
@@ -40,6 +42,8 @@ class WebViewScreen extends React.PureComponent<Props> {
40 42
         super();
41 43
         this.webviewRef = React.createRef();
42 44
         this.canGoBack = false;
45
+        this.hideHandler = new AutoHideHandler(false);
46
+        this.hideHandler.addListener(this.onHideChange);
43 47
     }
44 48
 
45 49
     /**
@@ -131,6 +135,16 @@ class WebViewScreen extends React.PureComponent<Props> {
131 135
         );
132 136
     }
133 137
 
138
+    onScroll = (event: Object) => {
139
+        this.hideHandler.onScroll(event);
140
+        if (this.props.onScroll)
141
+            this.props.onScroll(event);
142
+    }
143
+
144
+    onHideChange = (shouldHide: boolean) => {
145
+        this.props.navigation.setParams({hideTabBar: shouldHide});
146
+    }
147
+
134 148
     render() {
135 149
         const {containerPaddingTop, onScrollWithListener} = this.props.collapsibleStack;
136 150
         return (
@@ -151,7 +165,7 @@ class WebViewScreen extends React.PureComponent<Props> {
151 165
                 onMessage={this.props.onMessage}
152 166
                 onLoad={() => this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop))}
153 167
                 // Animations
154
-                onScroll={onScrollWithListener(this.props.onScroll)}
168
+                onScroll={onScrollWithListener(this.onScroll)}
155 169
             />
156 170
         );
157 171
     }

+ 34
- 6
src/components/Tabbar/CustomTabBar.js View File

@@ -11,16 +11,24 @@ type Props = {
11 11
     theme: Object,
12 12
 }
13 13
 
14
-const TAB_BAR_HEIGHT = 48;
15
-
16 14
 /**
17 15
  * Abstraction layer for Agenda component, using custom configuration
18 16
  */
19 17
 class CustomTabBar extends React.Component<Props> {
20 18
 
21
-    shouldComponentUpdate(nextProps: Props): boolean {
22
-        return (nextProps.theme.dark !== this.props.theme.dark)
23
-            || (nextProps.state.index !== this.props.state.index);
19
+    static TAB_BAR_HEIGHT = 48;
20
+
21
+    // shouldComponentUpdate(nextProps: Props): boolean {
22
+    //     return (nextProps.theme.dark !== this.props.theme.dark)
23
+    //         || (nextProps.state.index !== this.props.state.index);
24
+    // }
25
+
26
+    isHidden: boolean;
27
+    tabRef: Object;
28
+
29
+    constructor() {
30
+        super();
31
+        this.tabRef = React.createRef();
24 32
     }
25 33
 
26 34
     onItemPress(route: Object, currentIndex: number, destIndex: number) {
@@ -43,12 +51,18 @@ class CustomTabBar extends React.Component<Props> {
43 51
         const navigation = this.props.navigation;
44 52
         return (
45 53
             <Animatable.View
54
+                ref={this.tabRef}
46 55
                 animation={"fadeInUp"}
47 56
                 duration={500}
48 57
                 useNativeDriver
49 58
                 style={{
50 59
                     flexDirection: 'row',
51
-                    height: TAB_BAR_HEIGHT,
60
+                    height: CustomTabBar.TAB_BAR_HEIGHT,
61
+                    width: '100%',
62
+                    position: 'absolute',
63
+                    bottom: 0,
64
+                    left: 0,
65
+                    backgroundColor: this.props.theme.colors.surface,
52 66
                 }}
53 67
             >
54 68
                 {state.routes.map((route, index) => {
@@ -70,6 +84,20 @@ class CustomTabBar extends React.Component<Props> {
70 84
                             target: route.key,
71 85
                         });
72 86
                     };
87
+                    if (isFocused) {
88
+                        const tabVisible = options.tabBarVisible();
89
+                        console.log(tabVisible);
90
+                        if (this.tabRef.current) {
91
+                            if (this.isHidden && tabVisible) {
92
+                                this.isHidden = false;
93
+                                this.tabRef.current.slideInUp(300);
94
+                            } else if (!this.isHidden && !tabVisible){
95
+                                this.isHidden = true;
96
+                                this.tabRef.current.slideOutDown(300);
97
+                            }
98
+                        }
99
+
100
+                    }
73 101
 
74 102
                     const color = isFocused ? options.activeColor : options.inactiveColor;
75 103
                     const iconData = {focused: isFocused, color: color};

+ 10
- 1
src/navigation/MainTabNavigator.js View File

@@ -343,9 +343,18 @@ class TabNavigator extends React.Component<Props> {
343 343
                         else
344 344
                             return null;
345 345
                     },
346
+                    tabBarVisible: () => {
347
+                        const state = route.state;
348
+                        // Get the current route in the stack
349
+                        const screen = state ? state.routes[state.index] : undefined;
350
+                        const params = screen ? screen.params : undefined;
351
+                        const hideTabBar = params ? params.hideTabBar : undefined;
352
+                        return hideTabBar !== undefined ? !hideTabBar : true;
353
+                    },
354
+                    animationEnabled: true,
346 355
                     tabBarLabel: route.name !== 'home' ? undefined : '',
347 356
                     activeColor: this.props.theme.colors.primary,
348
-                    inactiveColor: this.props.theme.colors.tabIcon
357
+                    inactiveColor: this.props.theme.colors.tabIcon,
349 358
                 })}
350 359
                 tabBar={props => <CustomTabBar {...props} />}
351 360
             >

+ 1
- 0
src/screens/HomeScreen.js View File

@@ -485,6 +485,7 @@ class HomeScreen extends React.Component<Props, State> {
485 485
                     onScroll={this.onScroll}
486 486
                 />
487 487
                 <AnimatedFAB
488
+                    {...this.props}
488 489
                     ref={this.fabRef}
489 490
                     icon="qrcode-scan"
490 491
                     onPress={this.openScanner}

+ 20
- 3
src/screens/Proximo/ProximoListScreen.js View File

@@ -9,6 +9,8 @@ import {stringMatchQuery} from "../../utils/Search";
9 9
 import ProximoListItem from "../../components/Lists/ProximoListItem";
10 10
 import MaterialHeaderButtons, {Item} from "../../components/Custom/HeaderButton";
11 11
 import {withCollapsible} from "../../utils/withCollapsible";
12
+import CustomTabBar from "../../components/Tabbar/CustomTabBar";
13
+import AutoHideHandler from "../../utils/AutoHideHandler";
12 14
 
13 15
 function sortPrice(a, b) {
14 16
     return a.price - b.price;
@@ -57,6 +59,7 @@ class ProximoListScreen extends React.Component<Props, State> {
57 59
     modalRef: Object;
58 60
     listData: Array<Object>;
59 61
     shouldFocusSearchBar: boolean;
62
+    hideHandler: AutoHideHandler;
60 63
 
61 64
     constructor(props) {
62 65
         super(props);
@@ -67,6 +70,8 @@ class ProximoListScreen extends React.Component<Props, State> {
67 70
             currentSortMode: 3,
68 71
             modalCurrentDisplayItem: null,
69 72
         };
73
+        this.hideHandler = new AutoHideHandler(false);
74
+        this.hideHandler.addListener(this.onHideChange);
70 75
     }
71 76
 
72 77
 
@@ -296,8 +301,17 @@ class ProximoListScreen extends React.Component<Props, State> {
296 301
 
297 302
     itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
298 303
 
304
+
305
+    onScroll = (event: Object) => {
306
+        this.hideHandler.onScroll(event);
307
+    };
308
+
309
+    onHideChange = (shouldHide: boolean) => {
310
+        this.props.navigation.setParams({hideTabBar: shouldHide});
311
+    }
312
+
299 313
     render() {
300
-        const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
314
+        const {containerPaddingTop, scrollIndicatorInsetTop, onScrollWithListener} = this.props.collapsibleStack;
301 315
         return (
302 316
             <View style={{
303 317
                 height: '100%'
@@ -316,8 +330,11 @@ class ProximoListScreen extends React.Component<Props, State> {
316 330
                     getItemLayout={this.itemLayout}
317 331
                     initialNumToRender={10}
318 332
                     // Animations
319
-                    onScroll={onScroll}
320
-                    contentContainerStyle={{paddingTop: containerPaddingTop}}
333
+                    onScroll={onScrollWithListener(this.onScroll)}
334
+                    contentContainerStyle={{
335
+                        paddingTop: containerPaddingTop,
336
+                        paddingBottom: CustomTabBar.TAB_BAR_HEIGHT
337
+                    }}
321 338
                     scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
322 339
                 />
323 340
             </View>

+ 41
- 0
src/utils/AutoHideHandler.js View File

@@ -0,0 +1,41 @@
1
+// @flow
2
+
3
+import * as React from 'react';
4
+
5
+const speedOffset = 5;
6
+
7
+export default class AutoHideHandler {
8
+
9
+    lastOffset: number;
10
+    isHidden: boolean;
11
+
12
+    listeners: Array<Function>;
13
+
14
+    constructor(startHidden: boolean) {
15
+        this.listeners = [];
16
+        this.isHidden = startHidden;
17
+    }
18
+
19
+    addListener(listener: Function) {
20
+        this.listeners.push(listener);
21
+    }
22
+
23
+    notifyListeners(shouldHide: boolean) {
24
+        for (let i = 0; i < this.listeners.length; i++) {
25
+            this.listeners[i](shouldHide);
26
+        }
27
+    }
28
+
29
+    onScroll({nativeEvent}: Object) {
30
+        const speed = nativeEvent.contentOffset.y < 0 ? 0 : this.lastOffset - nativeEvent.contentOffset.y;
31
+        if (speed < -speedOffset && !this.isHidden) { // Go down
32
+            this.notifyListeners(true);
33
+            this.isHidden = true;
34
+        } else if (speed > speedOffset && this.isHidden) { // Go up
35
+            this.notifyListeners(false);
36
+            this.isHidden = false;
37
+        }
38
+        this.lastOffset = nativeEvent.contentOffset.y;
39
+    }
40
+
41
+}

Loading…
Cancel
Save