Last active
August 13, 2019 19:23
-
-
Save catalinmiron/f1a115a59bfb9ebba23cc3d152d3cf6e to your computer and use it in GitHub Desktop.
Mars planet expo implementation
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
// Inspiration: https://dribbble.com/shots/6520262-Mars | |
// Planet image: https://pngimg.com/download/61156 | |
import React from 'react'; | |
import { | |
StatusBar, | |
ScrollView, | |
TouchableWithoutFeedback, | |
Dimensions, | |
Image, | |
View, | |
TouchableOpacity, | |
Text | |
} from 'react-native'; | |
import { createAppContainer } from 'react-navigation'; | |
import * as Animatable from 'react-native-animatable'; | |
import { AppLoading } from 'expo'; | |
import { Asset } from 'expo-asset'; | |
import { Ionicons } from '@expo/vector-icons'; | |
import { createFluidNavigator, Transition } from 'react-navigation-fluid-transitions'; | |
const { width, height } = Dimensions.get('screen'); | |
function cacheImages(images) { | |
return images.map(image => { | |
if (typeof image === 'string') { | |
return Image.prefetch(image); | |
} else { | |
return Asset.fromModule(image).downloadAsync(); | |
} | |
}); | |
} | |
myCustomTransitionFunction = transitionInfo => { | |
const { progress, start, end } = transitionInfo; | |
const opacity = progress.interpolate({ | |
inputRange: [0, start, end, 1], | |
outputRange: [1, 1, 0, 0] | |
}); | |
const translateX = progress.interpolate({ | |
inputRange: [0, start, end, 1], | |
outputRange: [0, 0, 100, 100] | |
}); | |
return { opacity, transform: [{ translateX }] }; | |
}; | |
myCustomTransitionFunction2 = transitionInfo => { | |
const { progress, start, end } = transitionInfo; | |
const opacity = progress.interpolate({ | |
inputRange: [0, start, end, 1], | |
outputRange: [1, 1, 0, 0] | |
}); | |
const translateX = progress.interpolate({ | |
inputRange: [0, start, end, 1], | |
outputRange: [0, 0, -100, -100] | |
}); | |
return { opacity, transform: [{ translateX }] }; | |
}; | |
const smallMars = 300; | |
const bigMars = 900; | |
const ratio = bigMars / smallMars; | |
const rotation = '60deg'; | |
const dotSize = 14; | |
const coordinates = [...Array(10).keys()].map(() => { | |
const a = Math.random() * 2 * Math.PI; | |
const maxRadius = smallMars / 2 - dotSize * 2; | |
const r = maxRadius * Math.sqrt(Math.random()); | |
// If you need it in Cartesian coordinates | |
const x = r * Math.cos(a) + smallMars / 2; | |
const y = r * Math.sin(a) + smallMars / 2; | |
return { | |
x, | |
y | |
}; | |
}); | |
const minutes = [...Array(10).keys()].map(i => Math.round(Math.random() * 40) + 10); | |
class Screen1 extends React.Component { | |
render() { | |
return ( | |
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> | |
<Animatable.View | |
useNativeDriver | |
animation="fadeInUp" | |
delay={500} | |
duration={1500} | |
style={{ position: 'absolute', alignItems: 'center', justifyContent: 'center' }} | |
> | |
<Transition shared="image"> | |
<Image | |
source={require('./assets/mars_planet.png')} | |
style={{ | |
width: height, | |
height: height, | |
resizeMode: 'contain', | |
transform: [{ rotate: '0deg' }], | |
position: 'absolute', | |
bottom: -height | |
}} | |
/> | |
</Transition> | |
</Animatable.View> | |
<View | |
style={{ | |
position: 'absolute', | |
bottom: 50, | |
left: 0, | |
right: 0, | |
alignItems: 'center', | |
justifyContent: 'center' | |
}} | |
> | |
<Transition delay appear="left" disappear={myCustomTransitionFunction}> | |
<TouchableOpacity onPress={() => this.props.navigation.navigate('Screen2')}> | |
<Ionicons name="ios-arrow-round-forward" size={72} color="white" /> | |
</TouchableOpacity> | |
</Transition> | |
</View> | |
</View> | |
); | |
} | |
} | |
class Screen2 extends React.Component { | |
willFocusSubscription = null; | |
backButton = null; | |
componentDidMount() { | |
this.willFocusSubscription = this.props.navigation.addListener('willFocus', payload => { | |
if (!this.backButton) { | |
return; | |
} | |
this.backButton.fadeInRight(500, 1000); | |
// this.backButton.transitionTo({ opacity: 1 }, 1000, Easing.inOut, 2000); | |
}); | |
} | |
render() { | |
return ( | |
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> | |
<View style={{ position: 'relative' }}> | |
<Transition shared="image"> | |
<Image | |
source={require('./assets/mars_planet.png')} | |
style={{ | |
width: smallMars, | |
height: smallMars, | |
resizeMode: 'contain', | |
transform: [{ rotate: rotation }] | |
}} | |
onLayout={e => {}} | |
/> | |
</Transition> | |
{coordinates.map(({ x, y }, i) => { | |
return ( | |
<TouchableWithoutFeedback | |
onPress={async () => { | |
await this.backButton.fadeOutLeft(400); | |
this.props.navigation.navigate('Screen3', { coordinates: { x, y } }); | |
}} | |
hitSlop={{ top: 20, left: 20, bottom: 20, right: 20 }} | |
style={{ position: 'absolute' }} | |
key={i} | |
> | |
<Animatable.View | |
animation="fadeIn" | |
duration={1000} | |
useNativeDriver | |
delay={1000 + i * 150} | |
style={{ | |
position: 'absolute', | |
width: dotSize, | |
height: dotSize, | |
borderRadius: dotSize / 2, | |
backgroundColor: '#fff', | |
transform: [ | |
{ | |
translateX: x - dotSize / 2 | |
}, | |
{ | |
translateY: y - dotSize / 2 | |
} | |
] | |
}} | |
/> | |
</TouchableWithoutFeedback> | |
); | |
})} | |
</View> | |
<Animatable.View | |
useNativeDriver | |
ref={ref => (this.backButton = ref)} | |
animation="fadeInRight" | |
style={{ | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
right: 0, | |
alignItems: 'flex-start', | |
padding: 20, | |
justifyContent: 'center' | |
}} | |
> | |
<TouchableOpacity | |
onPress={async () => { | |
await this.backButton.fadeOutLeft(400); | |
this.props.navigation.navigate('Screen1'); | |
}} | |
> | |
<Ionicons name="ios-arrow-round-back" size={52} color="white" /> | |
</TouchableOpacity> | |
{/* <TouchableOpacity onPress={async () => {await this.backButton.fadeOutDown(400); this.props.navigation.navigate("Screen3", {coordinates})}}> | |
<Ionicons name="ios-arrow-round-forward" size={52} color="white"/> | |
</TouchableOpacity> */} | |
</Animatable.View> | |
<ScrollView horizontal style={{ position: 'absolute', bottom: 0, left: 0 }}> | |
{minutes.map((time, i) => { | |
return ( | |
<Animatable.View | |
key={i} | |
animation="fadeInRight" | |
duration={600} | |
useNativeDriver | |
delay={2000 + i * 150} | |
style={{ | |
marginRight: 20, | |
borderWidth: 1, | |
borderColor: '#fff', | |
borderRadius: 10, | |
padding: 10, | |
height: 120, | |
width: 110, | |
alignItems: 'flex-end', | |
justifyContent: 'flex-end' | |
}} | |
> | |
<Text style={{ fontSize: 24, color: 'white', fontFamily: 'monospace', fontWeight: 'bold' }}>{time}m</Text> | |
</Animatable.View> | |
); | |
})} | |
</ScrollView> | |
</View> | |
); | |
} | |
} | |
class Screen3 extends React.Component { | |
render() { | |
const { x, y } = this.props.navigation.state.params.coordinates; | |
return ( | |
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> | |
<View | |
style={{ | |
position: 'relative', | |
transform: [{ translateX: -x * ratio + bigMars / 2 }, { translateY: -y * ratio + bigMars / 2 }] | |
}} | |
> | |
<Transition shared="image" delay> | |
<Image | |
source={require('./assets/mars_planet.png')} | |
style={{ | |
resizeMode: 'contain', | |
width: bigMars, | |
height: bigMars, | |
transform: [{ rotate: rotation }] | |
}} | |
/> | |
</Transition> | |
</View> | |
<Animatable.View | |
useNativeDriver | |
animation="fadeIn" | |
delay={1000} | |
duration={500} | |
style={{ | |
position: 'absolute', | |
left: width / 2, | |
top: 300, | |
width: dotSize, | |
height: dotSize, | |
borderRadius: dotSize / 2, | |
backgroundColor: '#fff' | |
}} | |
/> | |
<View | |
style={{ | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
right: 0, | |
alignItems: 'flex-start', | |
justifyContent: 'center', | |
padding: 20 | |
}} | |
> | |
<Transition delay appear="left" disappear={myCustomTransitionFunction}> | |
<TouchableOpacity onPress={() => this.props.navigation.navigate('Screen2')}> | |
<Ionicons name="ios-arrow-round-back" size={52} color="white" /> | |
</TouchableOpacity> | |
</Transition> | |
</View> | |
</View> | |
); | |
} | |
} | |
const AppContainer = createAppContainer( | |
createFluidNavigator( | |
{ | |
Screen1: { | |
screen: Screen1 | |
}, | |
Screen2: { | |
screen: Screen2 | |
}, | |
Screen3: { | |
screen: Screen3 | |
} | |
}, | |
{ | |
headerMode: 'none', | |
initialRouteName: 'Screen1' | |
} | |
) | |
); | |
export default class App extends React.Component { | |
state = { | |
isReady: false | |
}; | |
async _loadAssetsAsync() { | |
const imageAssets = cacheImages([require('./assets/mars_planet.png')]); | |
await Promise.all([...imageAssets]); | |
} | |
static router = AppContainer.router; | |
render() { | |
if (!this.state.isReady) { | |
return ( | |
<AppLoading | |
startAsync={this._loadAssetsAsync} | |
onFinish={() => this.setState({ isReady: true })} | |
onError={console.warn} | |
/> | |
); | |
} | |
return ( | |
<View style={{ backgroundColor: '#000', flex: 1 }}> | |
<StatusBar hidden={true} /> | |
<AppContainer /> | |
</View> | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment