Created
November 15, 2017 03:55
-
-
Save kkemple/4c60d1c7030d6b9825b635ce8042d96b to your computer and use it in GitHub Desktop.
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 | |
// On android we fall back to scroll view due to overflow 'visible' not being supported | |
// https://github.com/facebook/react-native/issues/6802 | |
import React, { Component } from 'react'; | |
import { | |
Animated, | |
Dimensions, | |
PanResponder, | |
Platform, | |
ScrollView, | |
StyleSheet, | |
Text, | |
View, | |
} from 'react-native'; | |
import { withTheme, GoalCard } from '@mls-digital/react-components'; | |
import propTypes from 'prop-types'; | |
import type { GoalsCarouselItem } from '@utils/reducers'; | |
type Props = { | |
theme: Theme, | |
goals: Array<GoalsCarouselItem>, | |
onGoalCardSelected: (itemId: number) => void, | |
style: StyleSheet.styles, | |
}; | |
type State = { | |
position: Animated.Value, | |
}; | |
export class GoalsCarousel extends Component<Props, State> { | |
currentIndex: number = 0; | |
direction: string; | |
static propTypes = { | |
theme: propTypes.object.isRequired, | |
goals: propTypes.arrayOf(propTypes.object).isRequired, | |
onGoalCardSelected: propTypes.func, | |
}; | |
state = { | |
position: new Animated.Value(0), | |
}; | |
componentWillMount = (): void => { | |
this.panResponder = PanResponder.create({ | |
onMoveShouldSetPanResponder: this.handleGestureCapture, | |
onPanResponderMove: this.handleGestureMove, | |
onPanResponderRelease: this.handleGestureRelease, | |
onPanResponderTerminationRequest: () => true, | |
}); | |
}; | |
render = (): JSX.Element => { | |
const { style, theme, goals, onGoalCardSelected = () => {} } = this.props; | |
const { width } = Dimensions.get('window'); | |
const computedStyles = styles(theme, width); | |
return ( | |
<View style={[computedStyles.container, style]}> | |
<Text style={computedStyles.title}>SCORING</Text> | |
<View | |
{...(Platform.OS === 'ios' ? this.panResponder.panHandlers : {})} | |
style={computedStyles.carousel} | |
> | |
{Platform.OS === 'ios' ? ( | |
<Animated.View | |
style={[ | |
computedStyles.itemsContainer, | |
{ | |
transform: | |
goals.length < 2 | |
? [] | |
: [this.translateXForContainer(goals)], | |
}, | |
]} | |
> | |
{goals.map((goal, index) => { | |
return ( | |
<Animated.View | |
key={goal.id} | |
style={[ | |
this.getMarginForCarouselItem(index, goals, width), | |
this.getStylesForCarouselItem(index, goals), | |
]} | |
> | |
<GoalCard | |
onPress={() => onGoalCardSelected(goal.id)} | |
highlightImage={ | |
goal.videoImage | |
? { | |
uri: goal.videoImage, | |
} | |
: undefined | |
} | |
playerImage={{ uri: goal.scoringPlayerImage }} | |
subtitle={ | |
goal.assistPlayerName | |
? `Assisted By ${goal.assistPlayerName}` | |
: undefined | |
} | |
club={goal.club} | |
title={goal.title} | |
/> | |
</Animated.View> | |
); | |
})} | |
</Animated.View> | |
) : ( | |
<ScrollView | |
horizontal | |
showsHorizontalScrollIndicator={false} | |
contentContainerStyle={computedStyles.itemsContainer} | |
> | |
{goals.map((goal, index) => { | |
return ( | |
<GoalCard | |
key={goal.id} | |
style={this.getMarginForCarouselItem(index, goals, width)} | |
onPress={() => onGoalCardSelected(goal.id)} | |
highlightImage={ | |
goal.videoImage | |
? { | |
uri: goal.videoImage, | |
} | |
: undefined | |
} | |
playerImage={{ uri: goal.scoringPlayerImage }} | |
subtitle={ | |
goal.assistPlayerName | |
? `Assisted By ${goal.assistPlayerName}` | |
: undefined | |
} | |
club={goal.club} | |
title={goal.title} | |
/> | |
); | |
})} | |
</ScrollView> | |
)} | |
</View> | |
</View> | |
); | |
}; | |
handleGestureCapture = (e: GestureEvent, { dx }: GestureState): boolean => | |
Math.abs(dx) > 10; | |
handleGestureMove = (e: GestureEvent, { dx }: GestureState): void => { | |
const THRESHOLD = 230; | |
const { goals } = this.props; | |
this.direction = dx > 0 ? 'right' : 'left'; | |
if (this.direction === 'right' && this.currentIndex === 0) return true; | |
if (this.direction === 'left' && this.currentIndex === goals.length - 1) | |
return true; | |
const position = this.currentIndex - dx / THRESHOLD; | |
this.state.position.setValue(position); | |
return true; | |
}; | |
handleGestureRelease = (e: GestureEvent, { dx }: GestureState): void => { | |
const THRESHOLD = 230; | |
const { goals } = this.props; | |
this.direction = dx > 0 ? 'right' : 'left'; | |
if (this.direction === 'right' && this.currentIndex === 0) return true; | |
if (this.direction === 'left' && this.currentIndex === goals.length - 1) | |
return true; | |
if (Math.abs(dx) < THRESHOLD / 4) { | |
Animated.timing(this.state.position, { | |
duration: 150, | |
toValue: this.currentIndex, | |
}).start(); | |
} else { | |
Animated.timing(this.state.position, { | |
duration: 150, | |
toValue: | |
this.direction === 'left' ? ++this.currentIndex : --this.currentIndex, | |
}).start(); | |
} | |
return true; | |
}; | |
translateXForContainer = ( | |
goals: Array<GoalsCarouselItem>, | |
): { translateX: Animated.Value } => { | |
return { | |
translateX: this.state.position.interpolate({ | |
extrapolate: 'clamp', | |
inputRange: goals.map((_, index) => index), | |
outputRange: goals.map( | |
(_, index) => (index === 0 ? 0 : -(index * 236)), | |
), | |
useNativeDriver: true, | |
}), | |
}; | |
}; | |
getMarginForCarouselItem = ( | |
currentIndex: number, | |
goals: Array<GoalsCarouselItem>, | |
width: number, | |
): { marginLeft: number, marginRight?: number } => { | |
return currentIndex === 0 | |
? { marginLeft: width / 2 - 230 / 2 } | |
: currentIndex === goals.length - 1 | |
? { | |
marginLeft: 8, | |
marginRight: width / 2 - 230 / 2, | |
} | |
: { marginLeft: 8 }; | |
}; | |
getStylesForCarouselItem = ( | |
currentIndex: number, | |
goals: Array<GoalsCarouselItem>, | |
): { opacity: Animated.Value | 1 } => { | |
return { | |
opacity: | |
goals.length === 1 | |
? 1 | |
: this.state.position.interpolate({ | |
useNativeDriver: true, | |
extrapolate: 'clamp', | |
inputRange: | |
currentIndex === 0 | |
? [currentIndex, currentIndex + 1] | |
: currentIndex === goals.length - 1 | |
? [currentIndex - 1, currentIndex] | |
: [currentIndex - 1, currentIndex, currentIndex + 1], | |
outputRange: | |
currentIndex === 0 | |
? [1, 0.2] | |
: currentIndex === goals.length - 1 | |
? [0.2, 1] | |
: [0.2, 1, 0.2], | |
}), | |
}; | |
}; | |
} | |
export default withTheme(GoalsCarousel); | |
const styles: StyleSheetFactory = (theme: Theme, width) => | |
StyleSheet.create({ | |
carousel: { | |
width: '100%', | |
}, | |
container: { | |
alignItems: 'center', | |
justifyContent: 'center', | |
opacity: 1, | |
position: 'relative', | |
width: width, | |
}, | |
itemsContainer: { | |
alignItems: 'center', | |
flexDirection: 'row', | |
justifyContent: 'flex-start', | |
}, | |
title: { | |
backgroundColor: 'transparent', | |
color: theme.colors.grey, | |
fontFamily: theme.fontFamilies.regular, | |
fontSize: 10, | |
letterSpacing: 2, | |
lineHeight: 14, | |
marginBottom: 8, | |
opacity: 0.7, | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment