Created
March 7, 2021 21:04
-
-
Save ahmedam55/870abb2e4db06024b2757a7725ed4e0c to your computer and use it in GitHub Desktop.
Pokedex App
This file contains hidden or 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
import { StatusBar } from 'expo-status-bar' | |
import React, { useCallback, useEffect, useState } from 'react' | |
import { | |
Image, | |
Pressable, | |
SafeAreaView, | |
StyleSheet, | |
Text, | |
useWindowDimensions, | |
View, | |
} from 'react-native' | |
import Animated, { | |
runOnJS, | |
useAnimatedReaction, | |
useAnimatedStyle, | |
useSharedValue, | |
withRepeat, | |
withTiming, | |
} from 'react-native-reanimated' | |
import * as Speech from 'expo-speech' | |
const API_BASE = `https://pokeapi.co/api/v2` | |
const fetchPokemon = async id => { | |
const responsePokemon = await fetch(`${API_BASE}/pokemon/${id}`) | |
const pokemon = await responsePokemon.json() | |
const responseDetails = await fetch(`${API_BASE}/pokemon-species/${id}`) | |
const pokemonDetails = await responseDetails.json() | |
const name = pokemonDetails.name | |
const description = pokemonDetails.flavor_text_entries[0].flavor_text | |
const image = pokemon.sprites.other['official-artwork'].front_default | |
return { name, description, image } | |
} | |
const defaultPokemonState = { | |
name: '', | |
description: '', | |
image: null, | |
} | |
const statuses = { | |
initial: 0, | |
loading: 1, | |
success: 2, | |
error: 3, | |
} | |
const noop = () => {} | |
export default function App() { | |
const [pokemon, setPokemon] = useState(defaultPokemonState) | |
const [status, setStatus] = useState(statuses.loading) | |
const dimensions = useWindowDimensions() | |
const translation = useSharedValue(0) | |
const overlayOpacity = useSharedValue(1) | |
const lightOpacity = useSharedValue(1) | |
const coverStyle = useAnimatedStyle(() => { | |
return { | |
transform: [{ translateX: translation.value }], | |
} | |
}) | |
const lightStyle = useAnimatedStyle(() => { | |
return { | |
opacity: lightOpacity.value, | |
} | |
}) | |
const overlayStyle = useAnimatedStyle(() => { | |
return { | |
opacity: overlayOpacity.value, | |
} | |
}) | |
const fetchRandomPokemon = async () => { | |
const pokemonID = Math.floor(Math.random() * 150) + 1 | |
try { | |
setStatus(statuses.loading) | |
const pokemon = await fetchPokemon(pokemonID) | |
setPokemon(pokemon) | |
setStatus(statuses.success) | |
} catch (error) { | |
setStatus(statuses.error) | |
} | |
} | |
const startFlashingLight = () => { | |
lightOpacity.value = withRepeat(withTiming(0.5, { duration: 250 }), -1, true) | |
} | |
const stopFlashingLight = () => { | |
lightOpacity.value = withTiming(1) | |
} | |
const sayPokemonName = () => { | |
Speech.speak(`${pokemon.name}: ${pokemon.description}`, { | |
language: 'en-US', | |
pitch: 0.5, | |
rate: 1.1, | |
onStart: () => { | |
overlayOpacity.value = withTiming(0, { duration: 250 }) | |
startFlashingLight() | |
}, | |
onDone: stopFlashingLight, | |
onStopped: stopFlashingLight, | |
}) | |
} | |
const toggleCover = () => { | |
if (translation.value === 0) { | |
translation.value = withTiming(dimensions.width, { duration: 500 }) | |
} else { | |
translation.value = withTiming(0, { duration: 500 }, () => { | |
overlayOpacity.value = 1 | |
runOnJS(fetchRandomPokemon)() | |
}) | |
Speech.stop() | |
} | |
} | |
useAnimatedReaction( | |
() => { | |
return translation.value === dimensions.width && status === statuses.success | |
}, | |
shouldSpeak => { | |
if (shouldSpeak) { | |
runOnJS(sayPokemonName)() | |
} | |
}, | |
[status, pokemon], | |
) | |
useEffect(() => { | |
fetchRandomPokemon() | |
}, []) | |
return ( | |
<> | |
<StatusBar hidden /> | |
<Animated.View pointerEvents="none" style={[styles.cover, coverStyle]}> | |
<View style={styles.coverHandle} /> | |
<View style={styles.coverBinding} /> | |
</Animated.View> | |
<Pressable onPress={toggleCover} style={styles.pokedexTouchable}> | |
<View style={styles.pokedex}> | |
<SafeAreaView> | |
<View style={styles.lights}> | |
<View style={styles.mainLight}> | |
<Animated.View style={[styles.mainLightInner, lightStyle]} /> | |
</View> | |
<View style={styles.redLight} /> | |
<View style={styles.orangeLight} /> | |
<View style={styles.greenLight} /> | |
</View> | |
<View style={styles.details}> | |
<View style={styles.screen}> | |
<View style={styles.screenHeader}> | |
<View style={styles.screenHeaderHole} /> | |
<View style={styles.screenHeaderHole} /> | |
</View> | |
<View style={styles.screenPokemon}> | |
<Animated.View style={[styles.screenPokemonOverlay, overlayStyle]} /> | |
<Image | |
source={{ | |
uri: | |
status === statuses.success | |
? pokemon.image | |
: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', | |
}} | |
style={[styles.screenPokemonImage]} | |
/> | |
</View> | |
<View style={styles.screenSoundHoles}> | |
<View style={[styles.screenSoundHole, styles.screenSoundHoleLarge]} /> | |
<View style={[styles.screenSoundHole, styles.screenSoundHoleSmall]} /> | |
<View style={[styles.screenSoundHole, styles.screenSoundHoleMedium]} /> | |
<View style={[styles.screenSoundHole, styles.screenSoundHoleLarge]} /> | |
</View> | |
</View> | |
<View style={styles.greenButton} /> | |
</View> | |
</SafeAreaView> | |
</View> | |
</Pressable> | |
</> | |
) | |
} | |
const styles = StyleSheet.create({ | |
cover: { | |
...StyleSheet.absoluteFillObject, | |
top: 150, | |
zIndex: 1, | |
flexDirection: 'row', | |
backgroundColor: 'red', | |
alignItems: 'center', | |
justifyContent: 'space-between', | |
shadowColor: 'black', | |
shadowOffset: { | |
width: 0, | |
height: 0, | |
}, | |
shadowRadius: 10, | |
shadowOpacity: 0.4, | |
}, | |
coverHandle: { | |
height: 40, | |
width: 40, | |
backgroundColor: 'rgb(255,165,0)', | |
borderWidth: 4, | |
borderColor: 'rgba(0,0,0,0.2)', | |
transform: [{ rotate: '45deg' }, { translateX: -30 }], | |
}, | |
coverBinding: { | |
height: '100%', | |
width: 50, | |
backgroundColor: 'rgba(0,0,0,0.2)', | |
borderLeftWidth: 1, | |
borderLeftColor: 'rgb(15,15,15)', | |
}, | |
// | |
pokedexTouchable: { | |
flex: 1, | |
}, | |
pokedex: { | |
flex: 1, | |
backgroundColor: 'red', | |
}, | |
lights: { | |
flexDirection: 'row', | |
borderBottomWidth: 2, | |
borderBottomColor: 'rgb(15, 15, 15)', | |
backgroundColor: 'red', | |
shadowColor: 'black', | |
shadowOffset: { | |
width: 0, | |
height: 20, | |
}, | |
shadowRadius: 10, | |
shadowOpacity: 0.4, | |
paddingBottom: 24, | |
}, | |
mainLight: { | |
marginLeft: 20, | |
height: 80, | |
width: 80, | |
// Light blue | |
backgroundColor: 'rgb(75, 154, 244)', | |
borderRadius: 40, | |
borderWidth: 5, | |
borderColor: 'white', | |
alignItems: 'center', | |
justifyContent: 'center', | |
}, | |
mainLightInner: { | |
height: 40, | |
width: 40, | |
borderRadius: 20, | |
// Dark blue | |
backgroundColor: 'rgb(20, 90, 170)', | |
shadowColor: 'black', | |
shadowOffset: { | |
width: 0, | |
height: 0, | |
}, | |
shadowRadius: 20, | |
shadowOpacity: 0.9, | |
}, | |
redLight: { | |
marginLeft: 15, | |
height: 20, | |
width: 20, | |
borderRadius: 10, | |
// Red | |
backgroundColor: 'rgb(255, 0, 0)', | |
borderWidth: 2, | |
// Dark red | |
borderColor: 'rgb(200, 0, 0)', | |
}, | |
orangeLight: { | |
marginLeft: 10, | |
height: 20, | |
width: 20, | |
borderRadius: 10, | |
// Orange | |
backgroundColor: 'rgb(255, 165, 0)', | |
borderWidth: 2, | |
// Dark orange | |
borderColor: 'rgb(200, 165, 0)', | |
}, | |
greenLight: { | |
marginLeft: 10, | |
height: 20, | |
width: 20, | |
borderRadius: 10, | |
// Green | |
backgroundColor: 'rgb(0, 150, 0)', | |
borderWidth: 2, | |
// Dark green | |
borderColor: 'rgb(0, 100, 0)', | |
}, | |
details: { | |
height: '100%', | |
borderWidth: 10, | |
borderColor: 'rgba(0, 0, 0, 0.15)', | |
}, | |
screen: { | |
backgroundColor: 'white', | |
marginHorizontal: 20, | |
marginVertical: 30, | |
borderRadius: 10, | |
shadowColor: 'black', | |
shadowOffset: { | |
width: 0, | |
height: 0, | |
}, | |
shadowRadius: 10, | |
shadowOpacity: 0.2, | |
}, | |
screenHeader: { | |
flexDirection: 'row', | |
justifyContent: 'center', | |
alignItems: 'center', | |
height: 35, | |
}, | |
screenHeaderHole: { | |
backgroundColor: 'rgb(200, 0, 0)', | |
height: 10, | |
width: 10, | |
borderRadius: 5, | |
borderWidth: 2, | |
borderColor: 'rgba(0, 0, 0, 0.15)', | |
marginHorizontal: 10, | |
}, | |
screenPokemon: { | |
marginHorizontal: 30, | |
borderRadius: 5, | |
justifyContent: 'center', | |
alignItems: 'center', | |
backgroundColor: 'lightblue', | |
padding: 15, | |
borderWidth: 3, | |
borderBottomWidth: 0, | |
borderColor: 'rgba(0, 0, 0, 0.2)', | |
}, | |
screenPokemonOverlay: { | |
...StyleSheet.absoluteFillObject, | |
backgroundColor: 'black', | |
zIndex: 1, | |
}, | |
screenPokemonImage: { | |
height: 150, | |
width: 150, | |
}, | |
screenSoundHoles: { | |
justifyContent: 'center', | |
alignItems: 'flex-end', | |
height: 35, | |
marginRight: 30, | |
}, | |
screenSoundHole: { | |
height: 2, | |
width: 40, | |
backgroundColor: 'rgba(0, 0, 0, 0.4)', | |
marginBottom: 2, | |
}, | |
screenSoundHoleLarge: { | |
width: 40, | |
}, | |
screenSoundHoleMedium: { | |
width: 38, | |
}, | |
screenSoundHoleSmall: { | |
width: 35, | |
}, | |
greenButton: { | |
marginLeft: 20, | |
backgroundColor: 'darkgreen', | |
height: 80, | |
width: 140, | |
borderRadius: 5, | |
borderWidth: 5, | |
borderColor: 'rgba(0, 0, 0, 0.15)', | |
}, | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment