Themes
documentacion oficial en react-navigation Vamos a hacer otra implementacion para que nos sirva independientemente de si estamos usando reactnavigation o cualquier otra alternativa.
usaremos el context API
nativo.
En src/config/theme/theme.ts
import { StyleSheet } from 'react-native';
export interface ThemeColors {
primary: string;
text: string;
background: string;
cardBackground: string;
buttonTextColor: string;
}
export const colors: ThemeColors = {
primary: '#5856D6',
text: 'black',
background: '#F3F2F7',
cardBackground: 'white',
buttonTextColor: 'white',
};
export const lightColors: ThemeColors = {
primary: '#5856D6',
text: 'black',
background: '#F3F2F7',
cardBackground: 'white',
buttonTextColor: 'white',
};
export const darkColors: ThemeColors = {
primary: '#5856D6',
text: 'white',
background: '#090909',
cardBackground: '#2d2d2d',
buttonTextColor: 'white',
};
export const globalStyles = StyleSheet.create({
title: {
fontSize: 30,
fontWeight: 'bold',
color: colors.text,
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
borderColor: 'rgba(0,0,0,.3)',
borderRadius: 10,
color: colors.text,
},
subTitle: {
fontSize: 20,
fontWeight: 'bold',
color: colors.text,
},
mainContainer: {
flex: 1,
backgroundColor: colors.background,
},
globalMargin: {
paddingHorizontal: 20,
flex: 1,
},
btnPrimary: {
backgroundColor: colors.primary,
borderRadius: 10,
padding: 10,
alignItems: 'center',
},
btnPrimaryText: {
color: colors.text,
fontSize: 16,
},
});
ThemeContext
Creamos dentro de presentation/context/ThemeContext.tsx
import React from 'react';
import { PropsWithChildren, createContext } from 'react';
import { ThemeColors, lightColors } from '../../config/theme/theme';
type ThemeColor = 'light' | 'dark'
interface ThemeContextProps {
currentTheme: ThemeColor;
colors: ThemeColors;
setTheme: (theme: ThemeColor) => void
}
export const ThemeContext = createContext({} as ThemeContextProps);
export const ThemeProvider = ({ children }: PropsWithChildren) => {
const setTheme = (theme: ThemeColor) => {
console.log({ theme });
};
return (
<ThemeContext.Provider value={{
currentTheme: 'light',
colors: lightColors,
setTheme: setTheme,
}
}>
{children}
</ThemeContext.Provider >
);
};
En App.tsx
import { NavigationContainer } from '@react-navigation/native';
import React, { PropsWithChildren } from 'react';
import { StackNav } from './presentation/navigation/StackNav';
import { ThemeProvider } from './presentation/context/ThemeContext';
const AppState = ({ children }: PropsWithChildren) => {
return (
<NavigationContainer>
<ThemeProvider>
{children}
</ThemeProvider>
</NavigationContainer>
);
};
export const App = () => {
return (
<AppState>
<StackNav />
</AppState>
);
};
Creamos ChangeThemeScreen
:
import React, { useContext } from 'react';
import CustomView from '../../ui/CustomView';
import { Title } from '../../ui/Title';
import Button from '../../ui/Button';
import { ThemeContext } from '../../context/ThemeContext';
import { Text } from 'react-native';
const ChangeThemeScreen = () => {
const { setTheme, currentTheme, colors } = useContext(ThemeContext);
return (
<CustomView margin>
<Title text={`cambiar tema : ${currentTheme}`} />
<Button
text="light"
onPress={() => setTheme('light')}
/>
<Button
text="dark"
onPress={() => setTheme('dark')}
/>
<Text style={{ color:colors.text}}>
{
JSON.stringify(colors, null, 2)
}
</Text>
</CustomView>
);
};
export default ChangeThemeScreen;
Acordate de agregar la ruta a StackNav
<Stack.Screen name="ChangeThemeScreen" component={ChangeThemeScreen} />
Hagamos que ThemeContext
cambie los valores:
import React, { useState } from 'react';
import { PropsWithChildren, createContext } from 'react';
import { ThemeColors, darkColors, lightColors } from '../../config/theme/theme';
type ThemeColor = 'light' | 'dark'
interface ThemeContextProps {
currentTheme: ThemeColor;
colors: ThemeColors;
setTheme: (theme: ThemeColor) => void
}
export const ThemeContext = createContext({} as ThemeContextProps);
export const ThemeProvider = ({ children }: PropsWithChildren) => {
const [currentTheme, setCurrentTheme] = useState<ThemeColor>('light');
const setTheme = (theme: ThemeColor) => {
setCurrentTheme(theme);
};
return (
<ThemeContext.Provider value={{
currentTheme: currentTheme,
colors: (currentTheme === 'light' ? lightColors : darkColors),
setTheme: setTheme,
}
}>
{children}
</ThemeContext.Provider >
);
};```
Ahora los botones deben cambiar el title de `ChangeThemeScreen`
Ahora hay q usar el `colors` en todos lados , por ejemplo en `CustomView` lo usamos asi :
```tsx
import { StyleProp, View, ViewStyle } from 'react-native';
import React, { ReactNode, useContext } from 'react';
import { globalStyles } from '../../config/theme/theme';
import { ThemeContext } from '../context/ThemeContext';
interface Props {
style?: StyleProp<ViewStyle>
children?: ReactNode;
margin?: boolean;
}
const CustomView = ({ children, style, margin = false }: Props) => {
const { colors } = useContext(ThemeContext);
return (
<View style={[
globalStyles.mainContainer,
{ backgroundColor: colors.background },
style,
margin ? { margin: 10 } : undefined,
]}>
{children}
</View>
);
};
export default CustomView;
Si estamos usando react nativation , es problable que al cambiar de pantallas
veamos un destello. Para arreglarlo modificamos App.tsx
import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
import React, { PropsWithChildren, useContext } from 'react';
import { StackNav } from './presentation/navigation/StackNav';
import { ThemeContext, ThemeProvider } from './presentation/context/ThemeContext';
const AppNavigation = ({ children }: PropsWithChildren) => {
const { isDark } = useContext(ThemeContext);
return (
<NavigationContainer theme={isDark ? DarkTheme : DefaultTheme}>
{children}
</NavigationContainer>
);
};
const AppTheme = ({ children }: PropsWithChildren) => {
return (
<ThemeProvider>
<AppNavigation>
{children}
</AppNavigation>
</ThemeProvider>
);
};
export const App = () => {
return (
<AppTheme>
<StackNav />
</AppTheme>
);
};
y Agregamos isDark
al ThemeContext
por ahora ignora lo del useEffect
y el useColorScheme
.
import React, { useEffect, useState } from 'react';
import { PropsWithChildren, createContext } from 'react';
import { ThemeColors, darkColors, lightColors } from '../../config/theme/theme';
import { useColorScheme } from 'react-native';
type ThemeColor = 'light' | 'dark'
interface ThemeContextProps {
currentTheme: ThemeColor;
colors: ThemeColors;
isDark: boolean;
setTheme: (theme: ThemeColor) => void
}
export const ThemeContext = createContext({} as ThemeContextProps);
export const ThemeProvider = ({ children }: PropsWithChildren) => {
const colorScheme = useColorScheme();
const [currentTheme, setCurrentTheme] = useState<ThemeColor>('light');
const isDark = currentTheme === 'dark';
const colors = isDark ? darkColors : lightColors;
const setTheme = (theme: ThemeColor) => {
setCurrentTheme(theme);
};
useEffect(() => {
if (colorScheme === 'dark') {
setCurrentTheme('dark');
} else {
setCurrentTheme('light');
}
}, [colorScheme]);
return (
<ThemeContext.Provider value={{
currentTheme: currentTheme,
isDark: isDark,
colors: colors,
setTheme: setTheme,
}
}>
{children}
</ThemeContext.Provider >
);
};
Tema basado en sistema operativo
en ThemeContext
usamos
const colorScheme = useColorScheme();
useEffect(() => {
if (colorScheme === 'dark') {
setCurrentTheme('dark');
} else {
setCurrentTheme('light');
}
}, [colorScheme]);
App state
Esto nos permite saber si la app esta en primer o segundo plano y detectar el cambio:
Si vien appstate nos sirve para muchas cosas como detectar en un chat el estado activo o inactivo podemos usarlo como alternativa si no nos funiona bien la deteccion de color del sistema.
import React, { useEffect, useState } from 'react';
import { PropsWithChildren, createContext } from 'react';
import { ThemeColors, darkColors, lightColors } from '../../config/theme/theme';
import { AppState, Appearance, useColorScheme } from 'react-native';
type ThemeColor = 'light' | 'dark'
interface ThemeContextProps {
currentTheme: ThemeColor;
colors: ThemeColors;
isDark: boolean;
setTheme: (theme: ThemeColor) => void
}
export const ThemeContext = createContext({} as ThemeContextProps);
export const ThemeProvider = ({ children }: PropsWithChildren) => {
// const colorScheme = useColorScheme();
const [currentTheme, setCurrentTheme] = useState<ThemeColor>('light');
const isDark = currentTheme === 'dark';
const colors = isDark ? darkColors : lightColors;
const setTheme = (theme: ThemeColor) => {
setCurrentTheme(theme);
};
// useEffect(() => {
// if (colorScheme === 'dark') {
// setCurrentTheme('dark');
// } else {
// setCurrentTheme('light');
// }
// }, [colorScheme]);
useEffect(() => {
const subscription = AppState.addEventListener('change', nextAppState => {
const colorScheme = Appearance.getColorScheme();
setCurrentTheme(colorScheme === 'dark' ? 'dark' : 'light');
});
return () => {
subscription.remove();
};
}, []);
return (
<ThemeContext.Provider value={{
currentTheme: currentTheme,
isDark: isDark,
colors: colors,
setTheme: setTheme,
}
}>
{children}
</ThemeContext.Provider >
);
};
Hasta aca estamos, si queres podes simplificar app.tsx
asi :
import React from 'react';
import { StackNav } from './presentation/navigation/StackNav';
import { ThemeProvider } from './presentation/context/ThemeContext';
export const App = () => {
return (
<ThemeProvider>
<StackNav />
</ThemeProvider>
);
};
import React, { useEffect, useState } from 'react';
import { PropsWithChildren, createContext } from 'react';
import { ThemeColors, darkColors, lightColors } from '../../config/theme/theme';
import { AppState, Appearance, useColorScheme } from 'react-native';
import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
type ThemeColor = 'light' | 'dark'
interface ThemeContextProps {
currentTheme: ThemeColor;
colors: ThemeColors;
isDark: boolean;
setTheme: (theme: ThemeColor) => void
}
export const ThemeContext = createContext({} as ThemeContextProps);
export const ThemeProvider = ({ children }: PropsWithChildren) => {
// const colorScheme = useColorScheme();
const [currentTheme, setCurrentTheme] = useState<ThemeColor>('light');
const isDark = currentTheme === 'dark';
const colors = isDark ? darkColors : lightColors;
const setTheme = (theme: ThemeColor) => {
setCurrentTheme(theme);
};
// useEffect(() => {
// if (colorScheme === 'dark') {
// setCurrentTheme('dark');
// } else {
// setCurrentTheme('light');
// }
// }, [colorScheme]);
useEffect(() => {
const subscription = AppState.addEventListener('change', nextAppState => {
const colorScheme = Appearance.getColorScheme();
setCurrentTheme(colorScheme === 'dark' ? 'dark' : 'light');
});
return () => {
subscription.remove();
};
}, []);
return (
<NavigationContainer theme={isDark ? DarkTheme : DefaultTheme}>
<ThemeContext.Provider value={{
currentTheme: currentTheme,
isDark: isDark,
colors: colors,
setTheme: setTheme,
}
}>
{children}
</ThemeContext.Provider >
</NavigationContainer>
);
};
tanstack query peticiones y cache
npm i @tanstack/react-query
- En
App
import React from 'react';
import { StackNav } from './presentation/navigation/Navigator';
import { ThemeContextProvider } from './presentation/context/ThemeContext';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
const App = () => {
return (
<QueryClientProvider client={queryClient}>
<ThemeContextProvider>
<StackNav />
</ThemeContextProvider>
</QueryClientProvider>
);
};
export default App;
Vamos a HomeScreen
import { ActivityIndicator, View } from 'react-native';
import React from 'react';
import { Button, Text } from 'react-native-paper';
import { getPokemons } from '../../../actions/pokemons';
import { useQuery } from '@tanstack/react-query';
const HomeScreen = () => {
const { isLoading, data } = useQuery({
queryKey: ['pokemons'],
queryFn: () => getPokemons(),
staleTime: 1000 * 60 * 60, //60 min
});
return (
<View>
{isLoading ? <ActivityIndicator /> : (
<Button icon="camera" mode="contained" onPress={() => console.log('Pressed')}>
Press me
</Button>
)}
</View>
);
};
export default HomeScreen;
1. useQuery
Hook
const { isLoading, data } = useQuery({
queryKey: ['pokemons'],
queryFn: () => getPokemons(),
staleTime: 1000 * 60 * 60, // 60 min
});
El hook useQuery
se usa para realizar solicitudes de datos y manejar automáticamente su estado (como cargando, éxito, error, etc.). Aquí, se configura para obtener una lista de Pokémon.
Parámetros de useQuery
-
queryKey
:- Una clave única que identifica esta consulta.
- Se utiliza internamente para almacenar en caché los datos y reconocer cuándo invalidarlos.
- En este caso, la clave es
['pokemons']
.
-
queryFn
:- Una función que se ejecuta para obtener los datos.
- Aquí, se define como una función anónima que llama a
getPokemons()
. getPokemons()
es probablemente una función que realiza la solicitud HTTP para obtener los datos (por ejemplo, usandofetch
oaxios
).
-
staleTime
:- Define cuánto tiempo los datos se consideran “frescos” antes de volverse “obsoletos”.
- Durante este tiempo, TanStack Query no volverá a ejecutar la función de consulta (
queryFn
) si los datos ya están en caché. - Aquí, se establece en 60 minutos (1000 ms * 60 s * 60 min).
2. Propiedades que retorna useQuery
El hook retorna un objeto con múltiples propiedades para manejar el estado y los datos de la consulta. En este caso, se usan:
-
isLoading
:- Indica si la consulta está en proceso de cargar los datos.
- Es
true
cuando la consulta se está ejecutando y no hay datos en caché disponibles.
-
data
:- Contiene los datos obtenidos de la función
queryFn
(getPokemons
). - Si la consulta falla, este valor será
undefined
.
- Contiene los datos obtenidos de la función
3. Flujo de trabajo
-
Primera ejecución:
- Cuando
useQuery
se ejecuta por primera vez, verifica si ya hay datos en caché asociados con la clave['pokemons']
. - Si no hay datos (o son obsoletos), ejecuta la función
queryFn
(getPokemons
) para obtenerlos.
- Cuando
-
Estado de carga:
- Mientras los datos se están obteniendo,
isLoading
estrue
. - Una vez que la consulta se completa,
isLoading
cambia afalse
.
- Mientras los datos se están obteniendo,
-
Caché y
staleTime
:- Los datos obtenidos se almacenan en caché bajo la clave
['pokemons']
. - Durante el tiempo definido en
staleTime
(60 minutos), los datos se consideran frescos. - Si vuelves a usar
useQuery
con la misma clave dentro de este período, no ejecutará la consulta nuevamente; en su lugar, usará los datos en caché.
- Los datos obtenidos se almacenan en caché bajo la clave
-
Actualización automática:
- Cuando los datos se vuelven “obsoletos” después de 60 minutos,
useQuery
automáticamente ejecutaráqueryFn
nuevamente para actualizar los datos.
- Cuando los datos se vuelven “obsoletos” después de 60 minutos,
Ventajas de esta configuración
-
Caché eficiente:
- TanStack Query almacena los datos para evitar llamadas repetidas a la API si no es necesario.
-
Control de frescura:
- El parámetro
staleTime
permite mantener datos frescos por un tiempo específico, reduciendo la carga en el servidor.
- El parámetro
-
Estado automático:
useQuery
maneja automáticamente estados comoisLoading
ydata
, eliminando la necesidad de escribir lógica adicional para manejar solicitudes de datos.
-
Sincronización y reintentos:
- TanStack Query incluye reintentos automáticos en caso de error, así como sincronización cuando se detectan cambios en la red o en el enfoque de la aplicación.
Interfaces de PokeApi
En infraestructure/interfaces/
creamos pokeApi.interfaces.ts
export interface PokeAPIPaginatedResponse {
count: number;
next: string;
previous: string;
results: Result[];
}
export interface Result {
name: string;
url: string;
}
export interface PokeAPIPokemon {
abilities: Ability[];
base_experience: number;
forms: Species[];
game_indices: GameIndex[];
height: number;
held_items: any[];
id: number;
is_default: boolean;
location_area_encounters: string;
moves: Move[];
name: string;
order: number;
past_abilities: any[];
past_types: any[];
species: Species;
sprites: Sprites;
stats: Stat[];
types: Type[];
weight: number;
}
export interface Ability {
ability: Species;
is_hidden: boolean;
slot: number;
}
export interface Species {
name: string;
url: string;
}
export interface GameIndex {
game_index: number;
version: Species;
}
export interface Move {
move: Species;
version_group_details: VersionGroupDetail[];
}
export interface VersionGroupDetail {
level_learned_at: number;
move_learn_method: Species;
version_group: Species;
}
export interface GenerationV {
'black-white': Sprites;
}
export interface GenerationIv {
'diamond-pearl': Sprites;
'heartgold-soulsilver': Sprites;
platinum: Sprites;
}
export interface Versions {
'generation-i': GenerationI;
'generation-ii': GenerationIi;
'generation-iii': GenerationIii;
'generation-iv': GenerationIv;
'generation-v': GenerationV;
'generation-vi': { [key: string]: Home };
'generation-vii': GenerationVii;
'generation-viii': GenerationViii;
}
export interface Other {
dream_world: DreamWorld;
home: Home;
'official-artwork': OfficialArtwork;
showdown: Sprites;
}
export interface Sprites {
back_default: string;
back_female: null;
back_shiny: string;
back_shiny_female: null;
front_default: string;
front_female: null;
front_shiny: string;
front_shiny_female: null;
other?: Other;
versions?: Versions;
animated?: Sprites;
}
export interface GenerationI {
'red-blue': RedBlue;
yellow: RedBlue;
}
export interface RedBlue {
back_default: string;
back_gray: string;
back_transparent: string;
front_default: string;
front_gray: string;
front_transparent: string;
}
export interface GenerationIi {
crystal: Crystal;
gold: Gold;
silver: Gold;
}
export interface Crystal {
back_default: string;
back_shiny: string;
back_shiny_transparent: string;
back_transparent: string;
front_default: string;
front_shiny: string;
front_shiny_transparent: string;
front_transparent: string;
}
export interface Gold {
back_default: string;
back_shiny: string;
front_default: string;
front_shiny: string;
front_transparent?: string;
}
export interface GenerationIii {
emerald: OfficialArtwork;
'firered-leafgreen': Gold;
'ruby-sapphire': Gold;
}
export interface OfficialArtwork {
front_default: string;
front_shiny: string;
}
export interface Home {
front_default: string;
front_female: null;
front_shiny: string;
front_shiny_female: null;
}
export interface GenerationVii {
icons: DreamWorld;
'ultra-sun-ultra-moon': Home;
}
export interface DreamWorld {
front_default: string;
front_female: null;
}
export interface GenerationViii {
icons: DreamWorld;
}
export interface Stat {
base_stat: number;
effort: number;
stat: Species;
}
export interface Type {
slot: number;
type: Species;
}
Y modificamos actions/get-pokemons
import { pokeApi } from '../../config/api/pokeApi';
import { Pokemon } from '../../domain/entities/pokemon';
import type { PokeAPIPaginatedResponse, PokeAPIPokemon } from '../../infraestructure/interfaces/pokeApi.interfaces';
export const getPokemons = async (page: number, limit: number = 20): Promise<Pokemon[]> => {
try {
const url = `/pokemon?offset=${page * 10}&limit=${limit}`;
const { data } = await pokeApi.get<PokeAPIPaginatedResponse>(url);
const pokemonPromises = data.results.map((info) => {
return pokeApi.get<PokeAPIPokemon>(info.url);
});
const pokeApiPokemons = await Promise.all(pokemonPromises);
console.log(pokeApiPokemons);
return [];
} catch (error) {
throw new Error('Error getting pokemons');
}
};
Mapeo de PokeApi a entidad.
Creamos en /infraestructure/mappers
al pokemon.mapper.ts
import { Pokemon } from '../../domain/entities/pokemon';
import { PokeAPIPokemon } from '../interfaces/pokeApi.interfaces';
export class PokemonMapper {
static pokeApiPokemonToEntity(data: PokeAPIPokemon): Pokemon {
const sprites = PokemonMapper.getSprites(data);
const avatar = `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${data.id}.png`;
return {
id: data.id,
name: data.name,
avatar: avatar,
sprites: sprites,
types: data.types.map(type => type.type.name),
};
}
static getSprites(data: PokeAPIPokemon): string[] {
const sprites: string[] = [
data.sprites.front_default,
data.sprites.back_default,
data.sprites.front_shiny,
data.sprites.back_shiny,
];
if (data.sprites.other?.home.front_default) { sprites.push(data.sprites.other?.home.front_default); }
if (data.sprites.other?.['official-artwork'].front_default) { sprites.push(data.sprites.other?.['official-artwork'].front_default); }
if (data.sprites.other?.['official-artwork'].front_shiny) { sprites.push(data.sprites.other?.['official-artwork'].front_shiny); }
if (data.sprites.other?.showdown.front_default) { sprites.push(data.sprites.other?.showdown.front_default); }
if (data.sprites.other?.showdown.back_default) { sprites.push(data.sprites.other?.showdown.back_default); }
return sprites;
}
}
En get-pokemons
import { pokeApi } from '../../config/api/pokeApi';
import { Pokemon } from '../../domain/entities/pokemon';
import type { PokeAPIPaginatedResponse, PokeAPIPokemon } from '../../infraestructure/interfaces/pokeApi.interfaces';
import { PokemonMapper } from '../../infraestructure/mappers/pokemon.mapper';
export const getPokemons = async (page: number, limit: number = 20): Promise<Pokemon[]> => {
try {
const url = `/pokemon?offset=${page * 10}&limit=${limit}`;
const { data } = await pokeApi.get<PokeAPIPaginatedResponse>(url);
const pokemonPromises = data.results.map((info) => {
return pokeApi.get<PokeAPIPokemon>(info.url);
});
const pokeApiPokemons = await Promise.all(pokemonPromises);
const pokemons = pokeApiPokemons.map((item) => PokemonMapper.pokeApiPokemonToEntity(item.data));
return pokemons;
} catch (error) {
throw new Error('Error getting pokemons');
}
};
y en HomeScreen
import { ActivityIndicator, Image, ScrollView, View } from 'react-native';
import React from 'react';
import { Button, Text } from 'react-native-paper';
import { getPokemons } from '../../../actions/pokemons';
import { useQuery } from '@tanstack/react-query';
const HomeScreen = () => {
const { isLoading, data = [] } = useQuery({
queryKey: ['pokemons'],
queryFn: () => getPokemons(0), staleTime: 1000 * 60 * 60, //60 min
});
return (
<View>
<Text>HomeScreen</Text>
{isLoading ? <ActivityIndicator /> : (
<Button icon="camera" mode="contained" onPress={() => console.log('Pressed')}>
Press me
</Button>
)}
<ScrollView>
{
data?.map(pokemon => (
<Image src={pokemon.avatar} key={pokemon.id} width={100} height={100} />
))
}
</ScrollView>
</View>
);
};
export default HomeScreen;
Deberias ver los pokimoneee.
Diseno HomeScreen
Parte 1
import { StyleSheet, View } from 'react-native';
import React from 'react';
import { getPokemons } from '../../../actions/pokemons';
import { useQuery } from '@tanstack/react-query';
import PokeballBg from '../../components/ui/PomkeonBg';
const HomeScreen = () => {
const { isLoading, data = [] } = useQuery({
queryKey: ['pokemons'],
queryFn: () => getPokemons(0), staleTime: 1000 * 60 * 60, //60 min
});
return (
<View>
<PokeballBg style={styles.imgPosition} />
</View>
);
};
export default HomeScreen;
const styles = StyleSheet.create({
imgPosition: {
position: 'absolute',
top: -100,
right: -100,
},
});
En presentation/components/ui
creamos : PaokeballBg
import { Image, ImageStyle, StyleProp } from 'react-native';
import React, { useContext } from 'react';
import { ThemeContext } from '../../context/ThemeContext';
interface Props {
style?: StyleProp<ImageStyle>
}
const PokeballBg = ({ style }: Props) => {
const { isDark } = useContext(ThemeContext);
const pokeball = isDark ?
require('../../../assets/pokeball-light.png')
: require('../../../assets/pokeball-dark.png');
return (
<Image
source={pokeball}
style={
[
{ width: 300, height: 300, opacity: 0.3 },
style,
]}
/>
);
};
export default PokeballBg;
Parte 2
import { FlatList, StyleSheet, View } from 'react-native';
import React from 'react';
import { getPokemons } from '../../../actions/pokemons';
import { useQuery } from '@tanstack/react-query';
import PokeballBg from '../../components/ui/PomkeonBg';
import { Text } from 'react-native-paper';
import { globalTheme } from '../../../config/theme/global-theme';
import PokemonCard from '../../components/pokemons/PokemonCard';
const HomeScreen = () => {
const { isLoading, data: pokemons = [] } = useQuery({
queryKey: ['pokemons'],
queryFn: () => getPokemons(0), staleTime: 1000 * 60 * 60, //60 min
});
return (
<View style={globalTheme.globalMargin}>
<PokeballBg style={styles.imgPosition} />
<FlatList
data={pokemons}
keyExtractor={(pokemon, index) => `${pokemon.id}-${index}`}
numColumns={2}
style={{ paddingTop: 16 }}
ListHeaderComponent={() => (
<Text variant="displayMedium" >Pokedex</Text>
)}
renderItem={({ item }) => (
<PokemonCard pokemon={item} />
)}
/>
</View>
);
};
export default HomeScreen;
const styles = StyleSheet.create({
imgPosition: {
position: 'absolute',
top: -100,
right: -100,
},
});
En components/pokemon
creamos PokemonCard.tsx
import { Image, StyleSheet, View } from 'react-native';
import React from 'react';
import { Pokemon } from '../../../domain/entities/pokemon';
import { Card, Text } from 'react-native-paper';
interface Props {
pokemon: Pokemon;
}
const PokemonCard = ({ pokemon }: Props) => {
return (
<Card style={[
styles.cardContainer,
]}>
<Text style={styles.name} variant="bodyLarge" lineBreakMode="middle" >
{pokemon.name}
{'\n#' + pokemon.id}
</Text>
<View style={styles.pokeballContainer}>
<Image source={require('../../../assets/pokeball-light.png')} style={styles.pokeball} />
</View>
<Image source={{ uri: pokemon.avatar }} style={styles.pokemonImage} />
<Text style={[styles.name, { marginTop: 35 }]}>{pokemon.types[0]}</Text>
</Card>
);
};
export default PokemonCard;
const styles = StyleSheet.create({
cardContainer: {
marginHorizontal: 10,
backgroundColor: 'grey',
height: 120,
flex: 0.5,
marginBottom: 25,
borderRadius: 10,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
name: {
color: 'white',
top: 10,
left: 10,
},
pokeball: {
width: 100,
height: 100,
right: -25,
top: -25,
opacity: 0.4,
},
pokemonImage: {
width: 120,
height: 120,
position: 'absolute',
right: -15,
top: -30,
},
pokeballContainer: {
alignItems: 'flex-end',
width: '100%',
position: 'absolute',
overflow: 'hidden',
opacity: 0.5,
},
});