Improve animated components to match linter

This commit is contained in:
Arnaud Vergnet 2020-08-03 16:45:10 +02:00
parent 925bded69b
commit 9d92a88627
3 changed files with 308 additions and 290 deletions

View file

@ -1,101 +1,114 @@
// @flow
import * as React from 'react';
import {View} from "react-native";
import {View} from 'react-native';
import {List, withTheme} from 'react-native-paper';
import Collapsible from "react-native-collapsible";
import * as Animatable from "react-native-animatable";
import type {CustomTheme} from "../../managers/ThemeManager";
import Collapsible from 'react-native-collapsible';
import * as Animatable from 'react-native-animatable';
import type {CustomTheme} from '../../managers/ThemeManager';
type Props = {
theme: CustomTheme,
title: string,
subtitle?: string,
left?: (props: { [keys: string]: any }) => React.Node,
opened?: boolean,
unmountWhenCollapsed: boolean,
children?: React.Node,
}
type PropsType = {
theme: CustomTheme,
title: string,
subtitle?: string,
left?: () => React.Node,
opened?: boolean,
unmountWhenCollapsed?: boolean,
children?: React.Node,
};
type State = {
expanded: boolean,
}
type StateType = {
expanded: boolean,
};
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
class AnimatedAccordion extends React.Component<Props, State> {
class AnimatedAccordion extends React.Component<PropsType, StateType> {
static defaultProps = {
subtitle: '',
left: null,
opened: null,
unmountWhenCollapsed: false,
children: null,
};
static defaultProps = {
unmountWhenCollapsed: false,
}
chevronRef: { current: null | AnimatedListIcon };
chevronIcon: string;
animStart: string;
animEnd: string;
chevronRef: {current: null | AnimatedListIcon};
state = {
expanded: this.props.opened != null ? this.props.opened : false,
}
chevronIcon: string;
constructor(props) {
super(props);
this.chevronRef = React.createRef();
this.setupChevron();
}
animStart: string;
setupChevron() {
if (this.state.expanded) {
this.chevronIcon = "chevron-up";
this.animStart = "180deg";
this.animEnd = "0deg";
} else {
this.chevronIcon = "chevron-down";
this.animStart = "0deg";
this.animEnd = "180deg";
}
}
animEnd: string;
toggleAccordion = () => {
if (this.chevronRef.current != null) {
this.chevronRef.current.transitionTo({rotate: this.state.expanded ? this.animStart : this.animEnd});
this.setState({expanded: !this.state.expanded})
}
constructor(props: PropsType) {
super(props);
this.state = {
expanded: props.opened != null ? props.opened : false,
};
this.chevronRef = React.createRef();
this.setupChevron();
}
shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
if (nextProps.opened != null && nextProps.opened !== this.props.opened)
this.state.expanded = nextProps.opened;
return true;
shouldComponentUpdate(nextProps: PropsType): boolean {
const {state, props} = this;
if (nextProps.opened != null && nextProps.opened !== props.opened)
state.expanded = nextProps.opened;
return true;
}
setupChevron() {
const {state} = this;
if (state.expanded) {
this.chevronIcon = 'chevron-up';
this.animStart = '180deg';
this.animEnd = '0deg';
} else {
this.chevronIcon = 'chevron-down';
this.animStart = '0deg';
this.animEnd = '180deg';
}
}
render() {
const colors = this.props.theme.colors;
return (
<View>
<List.Item
{...this.props}
title={this.props.title}
subtitle={this.props.subtitle}
titleStyle={this.state.expanded ? {color: colors.primary} : undefined}
onPress={this.toggleAccordion}
right={(props) => <AnimatedListIcon
ref={this.chevronRef}
{...props}
icon={this.chevronIcon}
color={this.state.expanded ? colors.primary : undefined}
useNativeDriver
/>}
left={this.props.left}
/>
<Collapsible collapsed={!this.state.expanded}>
{!this.props.unmountWhenCollapsed || (this.props.unmountWhenCollapsed && this.state.expanded)
? this.props.children
: null}
</Collapsible>
</View>
);
toggleAccordion = () => {
const {state} = this;
if (this.chevronRef.current != null) {
this.chevronRef.current.transitionTo({
rotate: state.expanded ? this.animStart : this.animEnd,
});
this.setState({expanded: !state.expanded});
}
};
render(): React.Node {
const {props, state} = this;
const {colors} = props.theme;
return (
<View>
<List.Item
title={props.title}
subtitle={props.subtitle}
titleStyle={state.expanded ? {color: colors.primary} : undefined}
onPress={this.toggleAccordion}
right={({size}: {size: number}): React.Node => (
<AnimatedListIcon
ref={this.chevronRef}
size={size}
icon={this.chevronIcon}
color={state.expanded ? colors.primary : undefined}
useNativeDriver
/>
)}
left={props.left}
/>
<Collapsible collapsed={!state.expanded}>
{!props.unmountWhenCollapsed ||
(props.unmountWhenCollapsed && state.expanded)
? props.children
: null}
</Collapsible>
</View>
);
}
}
export default withTheme(AnimatedAccordion);

View file

@ -1,170 +1,178 @@
// @flow
import * as React from 'react';
import {StyleSheet, View} from "react-native";
import {FAB, IconButton, Surface, withTheme} from "react-native-paper";
import AutoHideHandler from "../../utils/AutoHideHandler";
import {StyleSheet, View} from 'react-native';
import {FAB, IconButton, Surface, withTheme} from 'react-native-paper';
import * as Animatable from 'react-native-animatable';
import CustomTabBar from "../Tabbar/CustomTabBar";
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../managers/ThemeManager";
import {StackNavigationProp} from '@react-navigation/stack';
import AutoHideHandler from '../../utils/AutoHideHandler';
import CustomTabBar from '../Tabbar/CustomTabBar';
import type {CustomTheme} from '../../managers/ThemeManager';
const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
type Props = {
navigation: StackNavigationProp,
theme: CustomTheme,
onPress: (action: string, data: any) => void,
seekAttention: boolean,
}
type PropsType = {
navigation: StackNavigationProp,
theme: CustomTheme,
onPress: (action: string, data?: string) => void,
seekAttention: boolean,
};
type State = {
currentMode: string,
}
type StateType = {
currentMode: string,
};
const DISPLAY_MODES = {
DAY: "agendaDay",
WEEK: "agendaWeek",
MONTH: "month",
}
class AnimatedBottomBar extends React.Component<Props, State> {
ref: { current: null | Animatable.View };
hideHandler: AutoHideHandler;
displayModeIcons: { [key: string]: string };
state = {
currentMode: DISPLAY_MODES.WEEK,
}
constructor() {
super();
this.ref = React.createRef();
this.hideHandler = new AutoHideHandler(false);
this.hideHandler.addListener(this.onHideChange);
this.displayModeIcons = {};
this.displayModeIcons[DISPLAY_MODES.DAY] = "calendar-text";
this.displayModeIcons[DISPLAY_MODES.WEEK] = "calendar-week";
this.displayModeIcons[DISPLAY_MODES.MONTH] = "calendar-range";
}
shouldComponentUpdate(nextProps: Props, nextState: State) {
return (nextProps.seekAttention !== this.props.seekAttention)
|| (nextState.currentMode !== this.state.currentMode);
}
onHideChange = (shouldHide: boolean) => {
if (this.ref.current != null) {
if (shouldHide)
this.ref.current.fadeOutDown(500);
else
this.ref.current.fadeInUp(500);
}
}
onScroll = (event: SyntheticEvent<EventTarget>) => {
this.hideHandler.onScroll(event);
};
changeDisplayMode = () => {
let newMode;
switch (this.state.currentMode) {
case DISPLAY_MODES.DAY:
newMode = DISPLAY_MODES.WEEK;
break;
case DISPLAY_MODES.WEEK:
newMode = DISPLAY_MODES.MONTH;
break;
case DISPLAY_MODES.MONTH:
newMode = DISPLAY_MODES.DAY;
break;
}
this.setState({currentMode: newMode});
this.props.onPress("changeView", newMode);
};
render() {
const buttonColor = this.props.theme.colors.primary;
return (
<Animatable.View
ref={this.ref}
useNativeDriver
style={{
...styles.container,
bottom: 10 + CustomTabBar.TAB_BAR_HEIGHT
}}>
<Surface style={styles.surface}>
<View style={styles.fabContainer}>
<AnimatedFAB
animation={this.props.seekAttention ? "bounce" : undefined}
easing="ease-out"
iterationDelay={500}
iterationCount="infinite"
useNativeDriver
style={styles.fab}
icon="account-clock"
onPress={() => this.props.navigation.navigate('group-select')}
/>
</View>
<View style={{flexDirection: 'row'}}>
<IconButton
icon={this.displayModeIcons[this.state.currentMode]}
color={buttonColor}
onPress={this.changeDisplayMode}/>
<IconButton
icon="clock-in"
color={buttonColor}
style={{marginLeft: 5}}
onPress={() => this.props.onPress('today', undefined)}/>
</View>
<View style={{flexDirection: 'row'}}>
<IconButton
icon="chevron-left"
color={buttonColor}
onPress={() => this.props.onPress('prev', undefined)}/>
<IconButton
icon="chevron-right"
color={buttonColor}
style={{marginLeft: 5}}
onPress={() => this.props.onPress('next', undefined)}/>
</View>
</Surface>
</Animatable.View>
);
}
}
DAY: 'agendaDay',
WEEK: 'agendaWeek',
MONTH: 'month',
};
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: '5%',
width: '90%',
},
surface: {
position: 'relative',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderRadius: 50,
elevation: 2,
},
fabContainer: {
position: "absolute",
left: 0,
right: 0,
alignItems: "center",
width: '100%',
height: '100%'
},
fab: {
position: 'absolute',
alignSelf: 'center',
top: '-25%',
}
container: {
position: 'absolute',
left: '5%',
width: '90%',
},
surface: {
position: 'relative',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderRadius: 50,
elevation: 2,
},
fabContainer: {
position: 'absolute',
left: 0,
right: 0,
alignItems: 'center',
width: '100%',
height: '100%',
},
fab: {
position: 'absolute',
alignSelf: 'center',
top: '-25%',
},
});
class AnimatedBottomBar extends React.Component<PropsType, StateType> {
ref: {current: null | Animatable.View};
hideHandler: AutoHideHandler;
displayModeIcons: {[key: string]: string};
constructor() {
super();
this.state = {
currentMode: DISPLAY_MODES.WEEK,
};
this.ref = React.createRef();
this.hideHandler = new AutoHideHandler(false);
this.hideHandler.addListener(this.onHideChange);
this.displayModeIcons = {};
this.displayModeIcons[DISPLAY_MODES.DAY] = 'calendar-text';
this.displayModeIcons[DISPLAY_MODES.WEEK] = 'calendar-week';
this.displayModeIcons[DISPLAY_MODES.MONTH] = 'calendar-range';
}
shouldComponentUpdate(nextProps: PropsType, nextState: StateType): boolean {
const {props, state} = this;
return (
nextProps.seekAttention !== props.seekAttention ||
nextState.currentMode !== state.currentMode
);
}
onHideChange = (shouldHide: boolean) => {
if (this.ref.current != null) {
if (shouldHide) this.ref.current.fadeOutDown(500);
else this.ref.current.fadeInUp(500);
}
};
onScroll = (event: SyntheticEvent<EventTarget>) => {
this.hideHandler.onScroll(event);
};
changeDisplayMode = () => {
const {props, state} = this;
let newMode;
switch (state.currentMode) {
case DISPLAY_MODES.DAY:
newMode = DISPLAY_MODES.WEEK;
break;
case DISPLAY_MODES.WEEK:
newMode = DISPLAY_MODES.MONTH;
break;
case DISPLAY_MODES.MONTH:
newMode = DISPLAY_MODES.DAY;
break;
default:
newMode = DISPLAY_MODES.WEEK;
break;
}
this.setState({currentMode: newMode});
props.onPress('changeView', newMode);
};
render(): React.Node {
const {props, state} = this;
const buttonColor = props.theme.colors.primary;
return (
<Animatable.View
ref={this.ref}
useNativeDriver
style={{
...styles.container,
bottom: 10 + CustomTabBar.TAB_BAR_HEIGHT,
}}>
<Surface style={styles.surface}>
<View style={styles.fabContainer}>
<AnimatedFAB
animation={props.seekAttention ? 'bounce' : undefined}
easing="ease-out"
iterationDelay={500}
iterationCount="infinite"
useNativeDriver
style={styles.fab}
icon="account-clock"
onPress={(): void => props.navigation.navigate('group-select')}
/>
</View>
<View style={{flexDirection: 'row'}}>
<IconButton
icon={this.displayModeIcons[state.currentMode]}
color={buttonColor}
onPress={this.changeDisplayMode}
/>
<IconButton
icon="clock-in"
color={buttonColor}
style={{marginLeft: 5}}
onPress={(): void => props.onPress('today')}
/>
</View>
<View style={{flexDirection: 'row'}}>
<IconButton
icon="chevron-left"
color={buttonColor}
onPress={(): void => props.onPress('prev')}
/>
<IconButton
icon="chevron-right"
color={buttonColor}
style={{marginLeft: 5}}
onPress={(): void => props.onPress('next')}
/>
</View>
</Surface>
</Animatable.View>
);
}
}
export default withTheme(AnimatedBottomBar);

View file

@ -1,66 +1,63 @@
// @flow
import * as React from 'react';
import {StyleSheet} from "react-native";
import {FAB} from "react-native-paper";
import AutoHideHandler from "../../utils/AutoHideHandler";
import {StyleSheet} from 'react-native';
import {FAB} from 'react-native-paper';
import * as Animatable from 'react-native-animatable';
import CustomTabBar from "../Tabbar/CustomTabBar";
import {StackNavigationProp} from "@react-navigation/stack";
import AutoHideHandler from '../../utils/AutoHideHandler';
import CustomTabBar from '../Tabbar/CustomTabBar';
type Props = {
navigation: StackNavigationProp,
icon: string,
onPress: () => void,
}
type PropsType = {
icon: string,
onPress: () => void,
};
const AnimatedFab = Animatable.createAnimatableComponent(FAB);
export default class AnimatedFAB extends React.Component<Props> {
ref: { current: null | Animatable.View };
hideHandler: AutoHideHandler;
constructor() {
super();
this.ref = React.createRef();
this.hideHandler = new AutoHideHandler(false);
this.hideHandler.addListener(this.onHideChange);
}
onScroll = (event: SyntheticEvent<EventTarget>) => {
this.hideHandler.onScroll(event);
};
onHideChange = (shouldHide: boolean) => {
if (this.ref.current != null) {
if (shouldHide)
this.ref.current.bounceOutDown(1000);
else
this.ref.current.bounceInUp(1000);
}
}
render() {
return (
<AnimatedFab
ref={this.ref}
useNativeDriver
icon={this.props.icon}
onPress={this.props.onPress}
style={{
...styles.fab,
bottom: CustomTabBar.TAB_BAR_HEIGHT
}}
/>
);
}
}
const styles = StyleSheet.create({
fab: {
position: 'absolute',
margin: 16,
right: 0,
},
fab: {
position: 'absolute',
margin: 16,
right: 0,
},
});
export default class AnimatedFAB extends React.Component<PropsType> {
ref: {current: null | Animatable.View};
hideHandler: AutoHideHandler;
constructor() {
super();
this.ref = React.createRef();
this.hideHandler = new AutoHideHandler(false);
this.hideHandler.addListener(this.onHideChange);
}
onScroll = (event: SyntheticEvent<EventTarget>) => {
this.hideHandler.onScroll(event);
};
onHideChange = (shouldHide: boolean) => {
if (this.ref.current != null) {
if (shouldHide) this.ref.current.bounceOutDown(1000);
else this.ref.current.bounceInUp(1000);
}
};
render(): React.Node {
const {props} = this;
return (
<AnimatedFab
ref={this.ref}
useNativeDriver
icon={props.icon}
onPress={props.onPress}
style={{
...styles.fab,
bottom: CustomTabBar.TAB_BAR_HEIGHT,
}}
/>
);
}
}