diff --git a/src/components/Tabbar/CustomTabBar.js b/src/components/Tabbar/CustomTabBar.js index ccd0263..5082bdd 100644 --- a/src/components/Tabbar/CustomTabBar.js +++ b/src/components/Tabbar/CustomTabBar.js @@ -2,179 +2,216 @@ import * as React from 'react'; import {withTheme} from 'react-native-paper'; -import TabIcon from "./TabIcon"; -import TabHomeIcon from "./TabHomeIcon"; -import {Animated} from 'react-native'; -import {Collapsible} from "react-navigation-collapsible"; +import Animated from 'react-native-reanimated'; +import {Collapsible} from 'react-navigation-collapsible'; +import {StackNavigationProp} from '@react-navigation/stack'; +import TabIcon from './TabIcon'; +import TabHomeIcon from './TabHomeIcon'; +import type {CustomTheme} from '../../managers/ThemeManager'; -type Props = { - state: Object, - descriptors: Object, - navigation: Object, - theme: Object, - collapsibleStack: Object, -} - -type State = { - translateY: AnimatedValue, - barSynced: boolean, -} - -const TAB_ICONS = { - proxiwash: 'tshirt-crew', - services: 'account-circle', - planning: 'calendar-range', - planex: 'clock', +type RouteType = { + name: string, + key: string, + params: {collapsible: Collapsible}, + state: { + index: number, + routes: Array, + }, }; -class CustomTabBar extends React.Component { +type PropsType = { + state: { + index: number, + routes: Array, + }, + descriptors: { + [key: string]: { + options: { + tabBarLabel: string, + title: string, + }, + }, + }, + navigation: StackNavigationProp, + theme: CustomTheme, +}; - static TAB_BAR_HEIGHT = 48; +type StateType = { + // eslint-disable-next-line flowtype/no-weak-types + translateY: any, +}; - state = { - translateY: new Animated.Value(0), - } +const TAB_ICONS = { + proxiwash: 'tshirt-crew', + services: 'account-circle', + planning: 'calendar-range', + planex: 'clock', +}; - syncTabBar = (route, index) => { - const state = this.props.state; - const isFocused = state.index === index; - if (isFocused) { - const stackState = route.state; - const stackRoute = stackState ? stackState.routes[stackState.index] : undefined; - const params: { collapsible: Collapsible } = stackRoute ? stackRoute.params : undefined; - const collapsible = params ? params.collapsible : undefined; - if (collapsible) { - this.setState({ - translateY: Animated.multiply(-1.5, collapsible.translateY), // Hide tab bar faster than header bar - }); - } - } +class CustomTabBar extends React.Component { + static TAB_BAR_HEIGHT = 48; + + constructor() { + super(); + this.state = { + translateY: new Animated.Value(0), }; + } - /** - * Navigates to the given route if it is different from the current one - * - * @param route Destination route - * @param currentIndex The current route index - * @param destIndex The destination route index - */ - onItemPress(route: Object, currentIndex: number, destIndex: number) { - const event = this.props.navigation.emit({ - type: 'tabPress', - target: route.key, - canPreventDefault: true, + /** + * Navigates to the given route if it is different from the current one + * + * @param route Destination route + * @param currentIndex The current route index + * @param destIndex The destination route index + */ + onItemPress(route: RouteType, currentIndex: number, destIndex: number) { + const {navigation} = this.props; + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + }); + if (currentIndex !== destIndex && !event.defaultPrevented) + navigation.navigate(route.name); + } + + /** + * Navigates to tetris screen on home button long press + * + * @param route + */ + onItemLongPress(route: RouteType) { + const {navigation} = this.props; + const event = navigation.emit({ + type: 'tabLongPress', + target: route.key, + canPreventDefault: true, + }); + if (route.name === 'home' && !event.defaultPrevented) + navigation.navigate('game-start'); + } + + /** + * Finds the active route and syncs the tab bar animation with the header bar + */ + onRouteChange = () => { + const {props} = this; + props.state.routes.map(this.syncTabBar); + }; + + /** + * Gets an icon for the given route if it is not the home one as it uses a custom button + * + * @param route + * @param focused + * @returns {null} + */ + getTabBarIcon = (route: RouteType, focused: boolean): React.Node => { + let icon = TAB_ICONS[route.name]; + icon = focused ? icon : `${icon}-outline`; + if (route.name !== 'home') return icon; + return null; + }; + + /** + * Gets a tab icon render. + * If the given route is focused, it syncs the tab bar and header bar animations together + * + * @param route The route for the icon + * @param index The index of the current route + * @returns {*} + */ + getRenderIcon = (route: RouteType, index: number): React.Node => { + const {props} = this; + const {state} = props; + const {options} = props.descriptors[route.key]; + let label; + if (options.tabBarLabel != null) label = options.tabBarLabel; + else if (options.title != null) label = options.title; + else label = route.name; + + const onPress = () => { + this.onItemPress(route, state.index, index); + }; + const onLongPress = () => { + this.onItemLongPress(route); + }; + const isFocused = state.index === index; + + const color = isFocused + ? props.theme.colors.primary + : props.theme.colors.tabIcon; + if (route.name !== 'home') { + return ( + index} + key={route.key} + /> + ); + } + return ( + + ); + }; + + getIcons(): React.Node { + const {props} = this; + return props.state.routes.map(this.getRenderIcon); + } + + syncTabBar = (route: RouteType, index: number) => { + const {state} = this.props; + const isFocused = state.index === index; + if (isFocused) { + const stackState = route.state; + const stackRoute = + stackState != null ? stackState.routes[stackState.index] : null; + const params: {collapsible: Collapsible} | null = + stackRoute != null ? stackRoute.params : null; + const collapsible = params != null ? params.collapsible : null; + if (collapsible != null) { + this.setState({ + translateY: Animated.multiply(-1.5, collapsible.translateY), // Hide tab bar faster than header bar }); - if (currentIndex !== destIndex && !event.defaultPrevented) - this.props.navigation.navigate(route.name); + } } + }; - /** - * Navigates to tetris screen on home button long press - * - * @param route - */ - onItemLongPress(route: Object) { - const event = this.props.navigation.emit({ - type: 'tabLongPress', - target: route.key, - canPreventDefault: true, - }); - if (route.name === "home" && !event.defaultPrevented) - this.props.navigation.navigate('game-start'); - } - - /** - * Gets an icon for the given route if it is not the home one as it uses a custom button - * - * @param route - * @param focused - * @returns {null} - */ - tabBarIcon = (route, focused) => { - let icon = TAB_ICONS[route.name]; - icon = focused ? icon : icon + ('-outline'); - if (route.name !== "home") - return icon; - else - return null; - }; - - /** - * Finds the active route and syncs the tab bar animation with the header bar - */ - onRouteChange = () => { - this.props.state.routes.map(this.syncTabBar) - } - - /** - * Gets a tab icon render. - * If the given route is focused, it syncs the tab bar and header bar animations together - * - * @param route The route for the icon - * @param index The index of the current route - * @returns {*} - */ - renderIcon = (route, index) => { - const state = this.props.state; - const {options} = this.props.descriptors[route.key]; - const label = - options.tabBarLabel != null - ? options.tabBarLabel - : options.title != null - ? options.title - : route.name; - - const onPress = () => this.onItemPress(route, state.index, index); - const onLongPress = () => this.onItemLongPress(route); - const isFocused = state.index === index; - - const color = isFocused ? this.props.theme.colors.primary : this.props.theme.colors.tabIcon; - if (route.name !== "home") { - return index} - key={route.key} - /> - } else - return - }; - - getIcons() { - return this.props.state.routes.map(this.renderIcon); - } - - render() { - this.props.navigation.addListener('state', this.onRouteChange); - const icons = this.getIcons(); - return ( - - {icons} - - ); - } + render(): React.Node { + const {props, state} = this; + props.navigation.addListener('state', this.onRouteChange); + const icons = this.getIcons(); + // $FlowFixMe + return ( + + {icons} + + ); + } } export default withTheme(CustomTabBar); diff --git a/src/components/Tabbar/TabHomeIcon.js b/src/components/Tabbar/TabHomeIcon.js index 608a66e..806d689 100644 --- a/src/components/Tabbar/TabHomeIcon.js +++ b/src/components/Tabbar/TabHomeIcon.js @@ -1,106 +1,133 @@ // @flow import * as React from 'react'; -import {Image, Platform, View} from "react-native"; +import {Image, Platform, View} from 'react-native'; import {FAB, TouchableRipple, withTheme} from 'react-native-paper'; -import * as Animatable from "react-native-animatable"; +import * as Animatable from 'react-native-animatable'; +import FOCUSED_ICON from '../../../assets/tab-icon.png'; +import UNFOCUSED_ICON from '../../../assets/tab-icon-outline.png'; +import type {CustomTheme} from '../../managers/ThemeManager'; -type Props = { - focused: boolean, - onPress: Function, - onLongPress: Function, - theme: Object, - tabBarHeight: number, -} +type PropsType = { + focused: boolean, + onPress: () => void, + onLongPress: () => void, + theme: CustomTheme, + tabBarHeight: number, +}; const AnimatedFAB = Animatable.createAnimatableComponent(FAB); /** * Abstraction layer for Agenda component, using custom configuration */ -class TabHomeIcon extends React.Component { +class TabHomeIcon extends React.Component { + constructor(props: PropsType) { + super(props); + Animatable.initializeRegistryWithDefinitions({ + fabFocusIn: { + '0': { + scale: 1, + translateY: 0, + }, + '0.9': { + scale: 1.2, + translateY: -9, + }, + '1': { + scale: 1.1, + translateY: -7, + }, + }, + fabFocusOut: { + '0': { + scale: 1.1, + translateY: -6, + }, + '1': { + scale: 1, + translateY: 0, + }, + }, + }); + } - focusedIcon = require('../../../assets/tab-icon.png'); - unFocusedIcon = require('../../../assets/tab-icon-outline.png'); + shouldComponentUpdate(nextProps: PropsType): boolean { + const {focused} = this.props; + return nextProps.focused !== focused; + } - constructor(props) { - super(props); - Animatable.initializeRegistryWithDefinitions({ - fabFocusIn: { - "0": { - scale: 1, translateY: 0 - }, - "0.9": { - scale: 1.2, translateY: -9 - }, - "1": { - scale: 1.1, translateY: -7 - }, - }, - fabFocusOut: { - "0": { - scale: 1.1, translateY: -6 - }, - "1": { - scale: 1, translateY: 0 - }, - } - }); - } - - iconRender = ({size, color}) => - this.props.focused - ? - : ; - - shouldComponentUpdate(nextProps: Props): boolean { - return (nextProps.focused !== this.props.focused); - } - - render(): React$Node { - const props = this.props; - return ( - - - - - - - ); - } + getIconRender = ({ + size, + color, + }: { + size: number, + color: string, + }): React.Node => { + const {focused} = this.props; + if (focused) + return ( + + ); + return ( + + ); + }; + render(): React.Node { + const {props} = this; + return ( + + + + + + ); + } } -export default withTheme(TabHomeIcon); \ No newline at end of file +export default withTheme(TabHomeIcon); diff --git a/src/components/Tabbar/TabIcon.js b/src/components/Tabbar/TabIcon.js index 923fd86..effafaf 100644 --- a/src/components/Tabbar/TabIcon.js +++ b/src/components/Tabbar/TabIcon.js @@ -1,114 +1,117 @@ // @flow import * as React from 'react'; -import {View} from "react-native"; +import {View} from 'react-native'; import {TouchableRipple, withTheme} from 'react-native-paper'; -import type {MaterialCommunityIconsGlyphs} from "react-native-vector-icons/MaterialCommunityIcons"; -import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons"; -import * as Animatable from "react-native-animatable"; - -type Props = { - focused: boolean, - color: string, - label: string, - icon: MaterialCommunityIconsGlyphs, - onPress: Function, - onLongPress: Function, - theme: Object, - extraData: any, -} +import type {MaterialCommunityIconsGlyphs} from 'react-native-vector-icons/MaterialCommunityIcons'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import * as Animatable from 'react-native-animatable'; +import type {CustomTheme} from '../../managers/ThemeManager'; +type PropsType = { + focused: boolean, + color: string, + label: string, + icon: MaterialCommunityIconsGlyphs, + onPress: () => void, + onLongPress: () => void, + theme: CustomTheme, + extraData: null | boolean | number | string, +}; /** * Abstraction layer for Agenda component, using custom configuration */ -class TabIcon extends React.Component { +class TabIcon extends React.Component { + firstRender: boolean; - firstRender: boolean; + constructor(props: PropsType) { + super(props); + Animatable.initializeRegistryWithDefinitions({ + focusIn: { + '0': { + scale: 1, + translateY: 0, + }, + '0.9': { + scale: 1.3, + translateY: 7, + }, + '1': { + scale: 1.2, + translateY: 6, + }, + }, + focusOut: { + '0': { + scale: 1.2, + translateY: 6, + }, + '1': { + scale: 1, + translateY: 0, + }, + }, + }); + this.firstRender = true; + } - constructor(props) { - super(props); - Animatable.initializeRegistryWithDefinitions({ - focusIn: { - "0": { - scale: 1, translateY: 0 - }, - "0.9": { - scale: 1.3, translateY: 7 - }, - "1": { - scale: 1.2, translateY: 6 - }, - }, - focusOut: { - "0": { - scale: 1.2, translateY: 6 - }, - "1": { - scale: 1, translateY: 0 - }, - } - }); - this.firstRender = true; - } + componentDidMount() { + this.firstRender = false; + } - componentDidMount() { - this.firstRender = false; - } + shouldComponentUpdate(nextProps: PropsType): boolean { + const {props} = this; + return ( + nextProps.focused !== props.focused || + nextProps.theme.dark !== props.theme.dark || + nextProps.extraData !== props.extraData + ); + } - shouldComponentUpdate(nextProps: Props): boolean { - return (nextProps.focused !== this.props.focused) - || (nextProps.theme.dark !== this.props.theme.dark) - || (nextProps.extraData !== this.props.extraData); - } - - render(): React$Node { - const props = this.props; - return ( - - - - - - - {props.label} - - - - ); - } + render(): React.Node { + const {props} = this; + return ( + + + + + + + {props.label} + + + + ); + } } export default withTheme(TabIcon);