Skip to content

Instantly share code, notes, and snippets.

@andrerfneves
Last active August 31, 2017 19:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrerfneves/ea22d7da76972448255c84d51d1f2e1e to your computer and use it in GitHub Desktop.
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.
// @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