Created a component to handle hide on scroll behavior

This commit is contained in:
Arnaud Vergnet 2020-04-14 20:14:39 +02:00
parent c684872a54
commit d0847dc0fd
6 changed files with 215 additions and 62 deletions

View file

@ -0,0 +1,64 @@
// @flow
import * as React from 'react';
import {StyleSheet} from "react-native";
import {IconButton, withTheme} from "react-native-paper";
import {AnimatedValue} from "react-native-reanimated";
import AutoHideComponent from "./AutoHideComponent";
type Props = {
theme: Object,
}
type State = {
fabPosition: AnimatedValue
}
class AnimatedBottomBar extends React.Component<Props, State> {
ref: Object;
constructor() {
super();
this.ref = React.createRef();
}
onScroll = (event: Object) => {
this.ref.current.onScroll(event);
};
render() {
return (
<AutoHideComponent
ref={this.ref}
style={{
...styles.bottom,
backgroundColor: this.props.theme.colors.surface,
}}>
<IconButton
icon="chevron-left"
size={40}
onPress={() => console.log('previous')}/>
<IconButton
icon="chevron-right"
size={40}
onPress={() => console.log('next')}/>
</AutoHideComponent>
);
}
}
const styles = StyleSheet.create({
bottom: {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
position: 'absolute',
left: 0,
bottom: 0,
width: '100%',
height: 100,
},
});
export default withTheme(AnimatedBottomBar);

View file

@ -0,0 +1,52 @@
// @flow
import * as React from 'react';
import {StyleSheet} from "react-native";
import {FAB} from "react-native-paper";
import {AnimatedValue} from "react-native-reanimated";
import AutoHideComponent from "./AutoHideComponent";
type Props = {
icon: string,
onPress: Function,
}
type State = {
fabPosition: AnimatedValue
}
export default class AnimatedFAB extends React.Component<Props, State> {
ref: Object;
constructor() {
super();
this.ref = React.createRef();
}
onScroll = (event: Object) => {
this.ref.current.onScroll(event);
};
render() {
return (
<AutoHideComponent
ref={this.ref}
style={styles.fab}>
<FAB
icon={this.props.icon}
onPress={this.props.onPress}
/>
</AutoHideComponent>
);
}
}
const styles = StyleSheet.create({
fab: {
position: 'absolute',
margin: 16,
right: 0,
bottom: 0,
},
});

View file

@ -0,0 +1,74 @@
// @flow
import * as React from 'react';
import {Animated} from 'react-native'
import {AnimatedValue} from "react-native-reanimated";
type Props = {
children: React.Node,
style: Object,
}
type State = {
fabPosition: AnimatedValue
}
export default class AutoHideComponent extends React.Component<Props, State> {
isAnimationDownPlaying: boolean;
isAnimationUpPlaying: boolean;
downAnimation;
upAnimation;
state = {
fabPosition: new Animated.Value(0),
};
constructor() {
super();
}
onScroll({nativeEvent}: Object) {
if (nativeEvent.velocity.y > 0.2) { // Go down
if (!this.isAnimationDownPlaying) {
this.isAnimationDownPlaying = true;
if (this.isAnimationUpPlaying)
this.upAnimation.stop();
this.downAnimation = Animated.spring(this.state.fabPosition, {
toValue: 100,
duration: 50,
useNativeDriver: true,
});
this.downAnimation.start(() => {
this.isAnimationDownPlaying = false
});
}
} else if (nativeEvent.velocity.y < -0.2) { // Go up
if (!this.isAnimationUpPlaying) {
this.isAnimationUpPlaying = true;
if (this.isAnimationDownPlaying)
this.downAnimation.stop();
this.upAnimation = Animated.spring(this.state.fabPosition, {
toValue: 0,
duration: 50,
useNativeDriver: true,
});
this.upAnimation.start(() => {
this.isAnimationUpPlaying = false
});
}
}
}
render() {
return (
<Animated.View style={{
...this.props.style,
transform: [{translateY: this.state.fabPosition}]
}}>
{this.props.children}
</Animated.View>
);
}
}

View file

@ -18,6 +18,7 @@ type Props = {
customJS: string,
collapsibleStack: Object,
onMessage: Function,
onScroll: Function,
}
const AnimatedWebView = Animated.createAnimatedComponent(WebView);
@ -155,7 +156,7 @@ class WebViewScreen extends React.PureComponent<Props> {
}
render() {
const {containerPaddingTop, onScroll} = this.props.collapsibleStack;
const {containerPaddingTop, onScrollWithListener} = this.props.collapsibleStack;
const customJS = this.getJavascriptPadding(containerPaddingTop);
return (
<AnimatedWebView
@ -174,7 +175,7 @@ class WebViewScreen extends React.PureComponent<Props> {
}}
onMessage={this.props.onMessage}
// Animations
onScroll={onScroll}
onScroll={onScrollWithListener(this.props.onScroll)}
/>
);
}

View file

@ -1,11 +1,11 @@
// @flow
import * as React from 'react';
import {Animated, FlatList, StyleSheet, View} from 'react-native';
import {Animated, FlatList, View} from 'react-native';
import i18n from "i18n-js";
import DashboardItem from "../components/Home/EventDashboardItem";
import WebSectionList from "../components/Lists/WebSectionList";
import {FAB, withTheme} from 'react-native-paper';
import {withTheme} from 'react-native-paper';
import FeedItem from "../components/Home/FeedItem";
import SquareDashboardItem from "../components/Home/SmallDashboardItem";
import PreviewEventDashboardItem from "../components/Home/PreviewEventDashboardItem";
@ -15,6 +15,7 @@ import ConnectionManager from "../managers/ConnectionManager";
import {CommonActions} from '@react-navigation/native';
import MaterialHeaderButtons, {Item} from "../components/Custom/HeaderButton";
import {AnimatedValue} from "react-native-reanimated";
import AnimatedFAB from "../components/Custom/AnimatedFAB";
// import DATA from "../dashboard_data.json";
@ -27,8 +28,6 @@ const SECTIONS_ID = [
'news_feed'
];
const AnimatedFAB = Animated.createAnimatedComponent(FAB);
const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
type Props = {
@ -38,7 +37,6 @@ type Props = {
}
type State = {
showFab: boolean,
fabPosition: AnimatedValue
}
@ -50,23 +48,18 @@ class HomeScreen extends React.Component<Props, State> {
colors: Object;
isLoggedIn: boolean | null;
isAnimationDownPlaying: boolean;
isAnimationUpPlaying: boolean;
downAnimation;
upAnimation;
fabRef: Object;
state = {
showFab: true,
fabPosition: new Animated.Value(0),
};
constructor(props) {
super(props);
this.colors = props.theme.colors;
this.isAnimationDownPlaying = false;
this.isAnimationUpPlaying = false;
this.isLoggedIn = null;
this.fabRef = React.createRef();
}
/**
@ -460,36 +453,8 @@ class HomeScreen extends React.Component<Props, State> {
openScanner = () => this.props.navigation.navigate("scanner");
onScroll = ({nativeEvent}: Object) => {
if (nativeEvent.velocity.y > 0.2) { // Go down
if (!this.isAnimationDownPlaying) {
this.isAnimationDownPlaying = true;
if (this.isAnimationUpPlaying)
this.upAnimation.stop();
this.downAnimation = Animated.spring(this.state.fabPosition, {
toValue: 100,
duration: 50,
useNativeDriver: true,
});
this.downAnimation.start(() => {
this.isAnimationDownPlaying = false
});
}
} else if (nativeEvent.velocity.y < -0.2) { // Go up
if (!this.isAnimationUpPlaying) {
this.isAnimationUpPlaying = true;
if (this.isAnimationDownPlaying)
this.downAnimation.stop();
this.upAnimation = Animated.spring(this.state.fabPosition, {
toValue: 0,
duration: 50,
useNativeDriver: true,
});
this.upAnimation.start(() => {
this.isAnimationUpPlaying = false
});
}
}
onScroll = (event: Object) => {
this.fabRef.current.onScroll(event);
};
render() {
@ -507,10 +472,7 @@ class HomeScreen extends React.Component<Props, State> {
onScroll={this.onScroll}
/>
<AnimatedFAB
style={{
...styles.fab,
transform: [{translateY: this.state.fabPosition}]
}}
ref={this.fabRef}
icon="qrcode-scan"
onPress={this.openScanner}
/>
@ -519,13 +481,4 @@ class HomeScreen extends React.Component<Props, State> {
}
}
const styles = StyleSheet.create({
fab: {
position: 'absolute',
margin: 16,
right: 0,
bottom: 0,
},
});
export default withTheme(HomeScreen);

View file

@ -3,7 +3,7 @@
import * as React from 'react';
import ThemeManager from "../../managers/ThemeManager";
import WebViewScreen from "../../components/Screens/WebViewScreen";
import {Avatar, Banner} from "react-native-paper";
import {Avatar, Banner, withTheme} from "react-native-paper";
import i18n from "i18n-js";
import {View} from "react-native";
import AsyncStorageManager from "../../managers/AsyncStorageManager";
@ -11,9 +11,11 @@ import AlertDialog from "../../components/Dialog/AlertDialog";
import {withCollapsible} from "../../utils/withCollapsible";
import {dateToString, getTimeOnlyString} from "../../utils/Planning";
import DateManager from "../../managers/DateManager";
import AnimatedBottomBar from "../../components/Custom/AnimatedBottomBar";
type Props = {
navigation: Object,
theme: Object,
collapsibleStack: Object,
}
@ -115,6 +117,7 @@ const LISTEN_TO_MESSAGES =
class PlanexScreen extends React.Component<Props, State> {
webScreenRef: Object;
barRef: Object;
customInjectedJS: string;
onHideBanner: Function;
@ -134,6 +137,7 @@ class PlanexScreen extends React.Component<Props, State> {
constructor() {
super();
this.webScreenRef = React.createRef();
this.barRef = React.createRef();
this.customInjectedJS =
"$(document).ready(function() {" +
OBSERVE_MUTATIONS_INJECTED +
@ -174,9 +178,8 @@ class PlanexScreen extends React.Component<Props, State> {
this.props.navigation.navigate('settings');
}
sendMessage = () => {
let data= 'coucou'
this.webScreenRef.current.postMessage(data);
sendMessage = (msg: string) => {
this.webScreenRef.current.postMessage(msg);
}
onMessage = (event: Object) => {
@ -202,6 +205,10 @@ class PlanexScreen extends React.Component<Props, State> {
});
};
onScroll = (event: Object) => {
this.barRef.current.onScroll(event);
};
render() {
const nav = this.props.navigation;
const {containerPaddingTop} = this.props.collapsibleStack;
@ -243,10 +250,12 @@ class PlanexScreen extends React.Component<Props, State> {
url={PLANEX_URL}
customJS={this.customInjectedJS}
onMessage={this.onMessage}
onScroll={this.onScroll}
/>
<AnimatedBottomBar ref={this.barRef}/>
</View>
);
}
}
export default withCollapsible(PlanexScreen);
export default withCollapsible(withTheme(PlanexScreen));