Skip to content

Commit

Permalink
add pokemon list
Browse files Browse the repository at this point in the history
  • Loading branch information
shawwal committed Nov 20, 2023
1 parent 09577c6 commit 8b3190a
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 28 deletions.
4 changes: 2 additions & 2 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
"backgroundColor": "#000000"
},
"assetBundlePatterns": [
"**/*"
Expand All @@ -21,7 +21,7 @@
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
"backgroundColor": "#000000"
}
},
"web": {
Expand Down
12 changes: 6 additions & 6 deletions app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ export default function TabLayout() {
}}
/>
<Tabs.Screen
name="two"
name="favorite"
options={{
title: 'Tab Two',
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
title: 'Favorite',
tabBarIcon: ({ color }) => <TabBarIcon name="heart" color={color} />,
}}
/>
<Tabs.Screen
name="three"
name="settings"
options={{
title: 'Tab Three',
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
title: 'Settings',
tabBarIcon: ({ color }) => <TabBarIcon name="cogs" color={color} />,
}}
/>
</Tabs>
Expand Down
6 changes: 3 additions & 3 deletions app/(tabs)/three.tsx → app/(tabs)/favorite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { StyleSheet } from 'react-native';
import EditScreenInfo from '../../components/EditScreenInfo';
import { Text, View } from '../../components/Themed';

export default function TabThreeScreen() {
export default function TabFavoriteScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Tab Three</Text>
<Text style={styles.title}>Tab favorite</Text>
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
<EditScreenInfo path="app/(tabs)/three.tsx" />
<EditScreenInfo path="app/(tabs)/favorite.tsx" />
</View>
);
}
Expand Down
30 changes: 16 additions & 14 deletions app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { View } from '../../components/Themed';
import React, { useState, useEffect } from 'react';
import { FlatList, StyleSheet, TouchableOpacity, Image, Dimensions } from 'react-native';
import axios from 'axios';
import PokemonRegions from 'components/PokemonRegions';
import PokemonRegions from 'constants/PokemonRegions';
import { useRouter } from 'expo-router';

const Home = ({ navigation }) => {
const Home = () => {
const [regions, setRegions] = useState([]);
const { width } = Dimensions.get('window');
const router = useRouter();
useEffect(() => {
const fetchRegions = async () => {
try {
Expand All @@ -21,22 +23,22 @@ const Home = ({ navigation }) => {
fetchRegions();
}, []);

const handleNavigate = (item: any) => (
console.log('item', item)
// navigation.navigate('PokemonList', { region: item })
)
const handleNavigate = (item: any, index: number) => {
router.push({ pathname: "/pokemonList", params: { name: item.name, url: item.url, number: index + 1 } });
}

const numColumns = width >= 600 ? 3 : 2; // 3 columns on tablet and desktop, 2 columns on mobile
const posterWidth = width >= 600 ? 200 : 180;

const renderGenerationItem = ({ item }) => (
<TouchableOpacity
style={styles.generationItem}
onPress={() => handleNavigate(item)}
>
{/* @ts-ignore */}
<Image source={PokemonRegions[item.name]} style={{...styles.generationImage, maxWidth: posterWidth}} />
</TouchableOpacity>
const renderGenerationItem = ({ item, index }) => (
item.name === 'hisui' ? <View /> :
<TouchableOpacity
style={styles.generationItem}
onPress={() => handleNavigate(item, index)}
>
{/* @ts-ignore */}
<Image source={PokemonRegions[item.name]} style={{ ...styles.generationImage, maxWidth: posterWidth }} />
</TouchableOpacity>
);

return (
Expand Down
6 changes: 3 additions & 3 deletions app/(tabs)/two.tsx → app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { StyleSheet } from 'react-native';
import EditScreenInfo from '../../components/EditScreenInfo';
import { Text, View } from '../../components/Themed';

export default function TabTwoScreen() {
export default function TabSettingsScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Tab Two</Text>
<Text style={styles.title}>Tab settings</Text>
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
<EditScreenInfo path="app/(tabs)/two.tsx" />
<EditScreenInfo path="app/(tabs)/settings.tsx" />
</View>
);
}
Expand Down
1 change: 1 addition & 0 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function RootLayoutNav() {
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="pokemonList"/>
<Stack.Screen name="modal" options={{ headerTitle: 'Information', presentation: 'modal' }} />
</Stack>
</ThemeProvider>
Expand Down
134 changes: 134 additions & 0 deletions app/pokemonList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React, { useEffect, useState, memo } from 'react';
import { StyleSheet, Alert, Image, TouchableOpacity, Dimensions, FlatList } from 'react-native';
import { Text, View } from '../components/Themed';
import Loading from 'components/Loading';
import axios from 'axios';
import { getPokemonByRegion } from 'utils/getPokemonByRegion';
import { capitalizeFirstLetter } from 'utils/commonUtils';
import { useLocalSearchParams, useRouter, Stack } from 'expo-router';
var { width } = Dimensions.get('window');

function PokemonListScreen() {
const [pokemonList, setPokemonList] = useState([]);
const [nextApi, setNextApi] = useState('');
const params = useLocalSearchParams();
const router = useRouter();
const { name } = params;
const [loading, setLoading] = useState(true);
const pokemonWidth = width >= 600 ? 300 : width * 0.3;
const wrapperWidth = width >= 600 ? 200 : width * 0.3;

useEffect(() => {
const fetchPokemon = async () => {
try {
const data = await getPokemonByRegion(name);
setPokemonList(data?.results);
setNextApi(data?.next);
setLoading(false)
} catch (error) {
console.error('Error fetching Pokémon:', error);
setLoading(false);
}
};

fetchPokemon();
}, []);

function handlePokemonDetails(link: any, id: any) {
// @ts-ignore
// router.push('/pokemonDetailsScreen', {
// endPoint: link,
// imgId: id,
// });
}

function handleNextPokemon() {
axios({
url: nextApi,
method: 'GET',
}).then((response) => {
if (response.status === 200) {
if (response.data.next != null) {
const nextResult = [...pokemonList, ...response.data.results] as any;
setPokemonList(nextResult);
setNextApi(response.data.next);
} else {
Alert.alert('No more Pokemon available at the moment');
}
}
}).catch((error) => {
Alert.alert('Something went wrong', error);
})
}

return (
<>
<Stack.Screen
options={{
// @ts-ignore
headerTitle: capitalizeFirstLetter(params.name),
}}
/>
<View style={styles.container}>
{pokemonList.length === 0 || loading ? <Loading /> :
<FlatList
data={pokemonList}
numColumns={3}
showsVerticalScrollIndicator={false}
keyExtractor={(_, index) => index.toString()}
onEndReached={handleNextPokemon}
onEndReachedThreshold={0.3}
contentContainerStyle={styles.innerContainer}
maxToRenderPerBatch={10}
removeClippedSubviews={true}
renderItem={({ item }) => {
const urlString = String(item.url);
const pokemonId = urlString.slice(-7).replace(/\D|\//g, "");
const pokemonImage = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/' + pokemonId + '.png';
return (
<TouchableOpacity
onPress={() => handlePokemonDetails(item.url, pokemonId)}
style={{...styles.pokemonWrapper, width: wrapperWidth}}
key={item.name}
>
<Image
style={{...styles.pokemonImg, width: pokemonWidth}}
source={{ uri: pokemonImage }}
/>
<Text style={styles.titleCase} ellipsizeMode='tail' numberOfLines={1}>{item.name}</Text>
</TouchableOpacity>
)
}}
/>
}
</View>
</>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
innerContainer: {
width: '100%',
maxWidth: 900, // Maximum width for desktop
},
titleCase: {
textTransform: 'capitalize'
},
pokemonWrapper: {
margin: 3,
borderRadius: 8,
overflow: 'hidden',
alignItems: 'center',
},
pokemonImg: {
height: 110,
resizeMode: 'contain',
},
});

export default memo(PokemonListScreen)
19 changes: 19 additions & 0 deletions components/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { ActivityIndicator, StyleSheet } from 'react-native';
import { View } from '../components/Themed';

const Loading = () => (
<View style={styles.loadingContainer}>
<ActivityIndicator />
</View>
);

const styles = StyleSheet.create({
loadingContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
})

export default Loading
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions utils/commonUtils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const capitalizeFirstLetter = (str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1);
}
60 changes: 60 additions & 0 deletions utils/getPokemonByRegion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// api.js
import axios from 'axios';

const POKEMON_API_BASE_URL = 'https://pokeapi.co/api/v2';

// Function to get Pokémon by custom offset for each region
export const getPokemonByRegion = async (region) => {
try {
let offset = 0;

switch (region.toLowerCase()) {
case 'kanto':
offset = 0;
break;
case 'johto':
offset = 151;
break;
case 'hoenn':
offset = 251;
break;
case 'sinnoh':
offset = 386;
break;
case 'unova':
offset = 493;
break;
case 'kalos':
offset = 649;
break;
case 'alola':
offset = 721;
break;
case 'galar':
offset = 809;
break;
case 'paldea':
offset = 905;
break;
// Add more cases for additional regions if needed
default:
return [];
}

const response = await axios.get(`${POKEMON_API_BASE_URL}/pokemon`, {
params: {
offset,
limit: 20, // Adjust the limit as needed
},
});
const pokemonData = response.data;

// Additional logic if you need to fetch more details for each Pokémon
// e.g., fetch details for each Pokémon using their URLs

return pokemonData;
} catch (error) {
console.error('Error fetching Pokémon:', error);
throw error;
}
};

0 comments on commit 8b3190a

Please sign in to comment.