Last active
August 31, 2017 19:38
-
-
Save andrerfneves/ea22d7da76972448255c84d51d1f2e1e to your computer and use it in GitHub Desktop.
MainTileView: Component responsible for loading top level pages of a React Native application. It deals with loading dynamic JSON data from an API and follows an internal parsing algorithm to decide which components are rendered to the View. Some complex animation is also handled on the `onScroll` prop of the main ScrollView.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
import React, { Component } from 'react'; | |
import { View, FlatList, RefreshControl, Animated } from 'react-native'; | |
import LinearGradient from 'react-native-linear-gradient'; | |
import Loader from '../../components/loader/loader'; | |
import Navbar from '../../components/navbar/navbar'; | |
import { parseMetadataIntoComponents } from '../../services/parse-metadata'; | |
import { findPageDataByPath, findNavDataByPath, findNavIndexByPath } from '../../utils/array'; | |
import { uuid } from '../../utils/uuid'; | |
import styles, { | |
bottomBarGradient, | |
loaderBGColor, | |
loaderColor, | |
} from './styles'; | |
import { | |
clampedScrollAnim, | |
navbarTranslateAnim, | |
navbarBGAnim, | |
menuCircleBGAnim, | |
menuCircleOpacityAnim, | |
} from './animations'; | |
type Props = { | |
// Props | |
nav: Array<*>, | |
navigator: Object, | |
pageData: Array<*>, | |
primaryNavigation: string, | |
secondaryNavigation: Object, | |
// Actions | |
fetchPageData: Function, | |
navigateToRoute: Function, | |
showModal: Function, | |
toggleDrawer: Function, | |
}; | |
type State = { | |
clampedScroll: any, | |
loading: boolean, | |
offsetAnim: any, | |
refreshing: boolean, | |
scrollAnim: any, | |
}; | |
export default class MainTile extends Component { | |
props: Props; | |
state: State; | |
_tileData: Array<*>; | |
constructor(props: Props) { | |
super(props); | |
// Scroll Animated Values | |
const scrollAnim = new Animated.Value(0); | |
const offsetAnim = new Animated.Value(0); | |
// Tile Data | |
this._tileData = []; | |
// Component State | |
this.state = { | |
clampedScroll: clampedScrollAnim(scrollAnim, offsetAnim), | |
loading: true, | |
offsetAnim, | |
refreshing: false, | |
scrollAnim, | |
}; | |
} | |
static navigatorStyle = { | |
navBarHidden: true, | |
navBarTransparent: true, | |
}; | |
componentWillMount() { | |
const { pageData, primaryNavigation, secondaryNavigation, nav } = this.props; | |
const routeIndex = findNavIndexByPath(nav, primaryNavigation); | |
this._fetchCurrentPageData(pageData, secondaryNavigation[routeIndex]); | |
} | |
_fetchCurrentPageData = (pageData, pagePath) => { | |
const { fetchPageData, navigator } = this.props; | |
const data = findPageDataByPath(pageData, pagePath); | |
if (!data) fetchPageData(pagePath); | |
if (data && data.components) { | |
this.setState({ loading: false }); | |
this._tileData = parseMetadataIntoComponents( | |
data.components, | |
navigator, | |
); | |
} | |
} | |
componentWillReceiveProps(nextProps: Props) { | |
const { primaryNavigation, nav } = this.props; | |
const { pageData, secondaryNavigation } = nextProps; | |
const routeIndex = findNavIndexByPath(nav, primaryNavigation); | |
this._fetchCurrentPageData(pageData, secondaryNavigation[routeIndex]); | |
} | |
_getBottomBar = () => ( | |
<LinearGradient | |
start={{ x: 0, y: 1 }} | |
end={{ x: 1, y: 0 }} | |
colors={bottomBarGradient} | |
style={styles.bottomBarGradient} | |
/> | |
); | |
_getAnimatedNavbar = () => { | |
const { navigator, showModal, toggleDrawer, nav, | |
primaryNavigation, pageData, secondaryNavigation } = this.props; | |
const { clampedScroll, scrollAnim } = this.state; | |
// Icon Handlers | |
const leftIconOnPress = () => toggleDrawer(navigator); | |
const rightIconOnPress = () => showModal( | |
{ screen: '/search' }, | |
navigator, | |
); | |
// Title, Subtitle & Dropdown | |
const routeData = findNavDataByPath(nav, primaryNavigation); | |
const routeIndex = findNavIndexByPath(nav, primaryNavigation); | |
const navbarTitle = routeData && routeData.title; | |
const hasDropdown = routeData && | |
routeData.children && | |
routeData.children.length > 0; | |
const subtitleData = findPageDataByPath(pageData, secondaryNavigation[routeIndex]); | |
let subtitle; | |
if (subtitleData && routeData && (subtitleData.path === routeData.path)) { | |
subtitle = 'HOME'; | |
} else { | |
subtitle = subtitleData && subtitleData.title; | |
} | |
// Dropdown Handler | |
const setLoadingState = () => this.setState({ loading: true }); | |
const handleTitlePress = () => showModal( | |
{ | |
screen: '/secondaryMenu', | |
passProps: { | |
primaryNavigation, | |
primaryIndex: routeIndex, | |
setLoadingState, | |
}, | |
}, | |
navigator, | |
); | |
// Animated Values | |
const navbarTranslateParam = navbarTranslateAnim(clampedScroll); | |
const navbarBGParam = navbarBGAnim(scrollAnim); | |
const menuCircleBGParam = menuCircleBGAnim(scrollAnim); | |
const menuCircleOpacityParam = menuCircleOpacityAnim(scrollAnim); | |
// Styles | |
const navbarContainerStyles = { | |
transform: [{ translateY: navbarTranslateParam || 0 }], | |
backgroundColor: navbarBGParam, | |
}; | |
return ( | |
<View style={styles.navbarWrapper}> | |
<Navbar | |
containerStyles={navbarContainerStyles} | |
hasShadow={hasDropdown} | |
hasSubtitle={hasDropdown} | |
isOnTransparentBackground | |
leftIcon='menu' | |
leftIconIsMenu | |
leftIconMenuCircleColor={menuCircleBGParam} | |
leftIconMenuCircleOpacity={menuCircleOpacityParam} | |
leftOnPress={leftIconOnPress} | |
rightIcon='search' | |
rightOnPress={rightIconOnPress} | |
title={navbarTitle} | |
titleHasDropdown={hasDropdown} | |
titleOnPress={handleTitlePress} | |
subtitle={subtitle} | |
isExpandedNavbar | |
/> | |
</View> | |
); | |
} | |
_keyExtractor = () => uuid(); | |
_renderItem = ({ item }) => item; | |
_getRefreshControl = () => { | |
const { refreshing } = this.state; | |
return ( | |
<RefreshControl | |
colors={[loaderColor]} | |
onRefresh={() => { console.log('Pull to refresh TBI'); }} | |
progressBackgroundColor={loaderBGColor} | |
refreshing={refreshing} | |
size={RefreshControl.SIZE.DEFAULT} | |
/> | |
); | |
} | |
_getTile = () => { | |
const { scrollAnim, loading } = this.state; | |
// Return Loader Component if `loading` prop is true | |
if (loading) return <Loader color='dark' />; | |
// Animation Scroll Hook | |
const onScroll = Animated.event([ | |
{ nativeEvent: { contentOffset: { y: scrollAnim } } }, | |
]); | |
return ( | |
<FlatList | |
contentContainerStyle={styles.listContent} | |
data={this._tileData} | |
keyExtractor={this._keyExtractor} | |
onScroll={onScroll} | |
refreshControl={this._getRefreshControl()} | |
renderItem={this._renderItem} | |
removeClippedSubviews | |
/> | |
); | |
} | |
render() { | |
return ( | |
<View style={styles.view}> | |
{this._getTile()} | |
{this._getAnimatedNavbar()} | |
{this._getBottomBar()} | |
</View> | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment