Carousel y posters
Podemos hacerlo con react-native-reanimated-carousel pero lo haremos a mano porque reanimated suele tener problemas y no me quiero volver loco arreglando errores.
Vamos a HomeScreen
y ponemos :
import React from 'react';
import { View, Text, ScrollView } from 'react-native';
import { useMovies } from '../../hooks/useMovies';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { PosterCarousel } from '../../componentes/movies/PosterCarousel';
export const HomeScreen = () => {
const { isLoading, nowPlaying } = useMovies();
const { top } = useSafeAreaInsets();
if (isLoading) {
return (<Text>Cargando</Text>);
}
return (
<ScrollView>
<View style={{ marginTop: top + 20, paddingBottom: 30 }} >
<PosterCarousel movies={nowPlaying} />
</View>
</ScrollView>
);
};
Ahora crearemos 2 componentes PosterCarousel
y MoviePoster
en el folder components
con lo siguiente:
import React from 'react';
import { ScrollView, View } from 'react-native';
import { Movie } from '../../../core/entities/movie.entity';
import { MoviePoster } from './MoviePoster';
interface Props {
movies: Movie[];
height?: number;
}
export const PosterCarousel = ({ height = 440, movies }: Props) => {
return (
<View style={{ height }}>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{
movies.map(movie => <MoviePoster key={movie.id} movie={movie} />)
}
</ScrollView>
</View>
);
};
Usaremos ScrollView
y no FlatList
porque quiero que se rendericen todas las portadas de
una vez, flat list lo haria de modo peresozo.
Ahora MoviePoster
import React from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { Movie } from '../../../core/entities/movie.entity';
interface Props {
movie: Movie;
height?: number;
width?: number;
}
export const MoviePoster = ({ movie, width = 300, height = 420 }: Props) => {
return (
<View style={{ ...styles.imageContainer, width, height }}>
<Image style={styles.image} source={{ uri: movie.poster }} />
</View>
);
};
const styles = StyleSheet.create({
image: {
flex: 1,
borderRadius: 18,
},
imageContainer: {
flex: 1,
borderRadius: 18,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 10,
},
shadowOpacity: 0.24,
shadowRadius: 7,
elevation: 9,
},
});
```
Hasta aca deberiamos poder ver las portadas.
## Navegacion entre pantallas
En `MoviePoster` con `Pressable` y `useNavigation` lograremos
camabiar estre las pantallas de las movies.
```tsx
import React from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { Movie } from '../../../core/entities/movie.entity';
import { Pressable } from 'react-native-gesture-handler';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { RootStackParams } from '../../navigation/StackNav';
interface Props {
movie: Movie;
height?: number;
width?: number;
}
export const MoviePoster = ({ movie, width = 300, height = 420 }: Props) => {
# Typamos para obtener ayuda de las rutas disponibles
const navigation = useNavigation<NavigationProp<RootStackParams>>();
return (
<Pressable
# pasamos movieId a la pantalla details
onPress={() => navigation.navigate('Details', { movieId: movie.id })}
# usamos pressed para generar un efecto
style={
({ pressed }) => ({
width,
height,
opacity: pressed ? 0.9 : 1,
marginHorizontal: 10,
paddingHorizontal: 10,
paddingBottom: 20,
})
}
>
<View style={{ ...styles.imageContainer, width, height }}>
<Image style={styles.image} source={{ uri: movie.poster }} />
</View>
</Pressable>
);
};
#algunos estilos menores
const styles = StyleSheet.create({
image: {
flex: 1,
borderRadius: 18,
},
imageContainer: {
flex: 1,
borderRadius: 18,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 10,
},
shadowOpacity: 0.24,
shadowRadius: 7,
elevation: 9,
},
});
```
## Carousel de peliculas con FlatList
En `HorizontalCarousel` ponemos:
```tsx
import React from 'react';
import { View, Text } from 'react-native';
import { Movie } from '../../../core/entities/movie.entity';
import { FlatList } from 'react-native-gesture-handler';
import { MoviePoster } from './MoviePoster';
interface Props {
movies: Movie[]
title?: string;
}
export const HorizontalCarousel = ({ movies, title }: Props) => {
return (
<View
style={{ height: title ? 260 : 220 }}
>
{
title && (
<Text
style={{
fontSize: 30,
fontWeight: '300',
marginLeft: 10,
marginBottom: 10,
}}
>{title}</Text>
)
}
<FlatList
data={movies}
renderItem={({ item }) => (
<MoviePoster movie={item} width={140} height={200} />
)}
keyExtractor={item => item.id.toString()}
horizontal={true}
showsHorizontalScrollIndicator={false}
/>
</View>
);
};
```
En `HomeScreen`:
```tsx
import React from 'react';
import { View, Text, ScrollView } from 'react-native';
import { useMovies } from '../../hooks/useMovies';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { PosterCarousel } from '../../componentes/movies/PosterCarousel';
import { HorizontalCarousel } from '../../componentes/movies/HorinzontalCarousel';
export const HomeScreen = () => {
const { isLoading, nowPlaying, popular } = useMovies();
const { top } = useSafeAreaInsets();
if (isLoading) {
return (<Text>Cargando</Text>);
}
return (
<ScrollView>
<View style={{ marginTop: top + 20, paddingBottom: 30 }} >
<PosterCarousel movies={nowPlaying} />
<HorizontalCarousel movies={popular} title="Populares" />
</View>
</ScrollView>
);
};
```
Podemos usar `HorizontalCarousel` para las demas peliculas asi :
```tsx
import React from 'react';
import { View, Text, ScrollView } from 'react-native';
import { useMovies } from '../../hooks/useMovies';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { PosterCarousel } from '../../componentes/movies/PosterCarousel';
import { HorizontalCarousel } from '../../componentes/movies/HorinzontalCarousel';
export const HomeScreen = () => {
const { isLoading, nowPlaying, popular, topRated, upcoming } = useMovies();
const { top } = useSafeAreaInsets();
if (isLoading) {
return (<Text>Cargando</Text>);
}
return (
<ScrollView>
<View style={{ marginTop: top + 20, paddingBottom: 30 }} >
<PosterCarousel movies={nowPlaying} />
<HorizontalCarousel movies={popular} title="Populares" />
<HorizontalCarousel movies={topRated} title="Top" />
<HorizontalCarousel movies={upcoming} title="Upcoming" />
</View>
</ScrollView>
);
};
```
## Infinite scroll Horizontal
Primero tenemos que detectar cuando es que llegamos al final
Vamos la fichero `HorizontalCarousel` y usamos la propiedad `onScroll`
esta prop tiene el `event` del cual desestructuramos los objetos de interes y haremos
unos calculos para saber si el scroll llego al final.
```tsx
import React, { useEffect, useRef } from 'react';
import { View, Text, NativeSyntheticEvent, NativeScrollEvent } from 'react-native';
import { Movie } from '../../../core/entities/movie.entity';
import { FlatList } from 'react-native-gesture-handler';
import { MoviePoster } from './MoviePoster';
interface Props {
movies: Movie[]
title?: string;
loadNextPage?: () => void;
}
const HANDICAP: number = 600;
export const HorizontalCarousel = ({ movies, title, loadNextPage }: Props) => {
//usamos useRef porqeu no nos interesa mostrar un spinner
//si queres poner un loader tenes que usar useState
const isLoading = useRef(false);
useEffect(() => {
setTimeout(() => {
isLoading.current = false;
}, 300);
}, [movies]);
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
// Evitamos que se ejecute muchas veces la funcion si se estan
// cargando nuevas movies
if (isLoading.current) { return; }
const { contentOffset, layoutMeasurement, contentSize } = event.nativeEvent;
const isEndReached = (contentOffset.x + layoutMeasurement.width + HANDICAP) >= contentSize.width;
if (!isEndReached) { return; }
isLoading.current = true;
//cargar siguientes peliculas
loadNextPage && loadNextPage();
};
return (
<View
style={{ height: title ? 260 : 220 }}
>
{
title && (
<Text
style={{
fontSize: 30,
fontWeight: '300',
marginLeft: 10,
marginBottom: 10,
}}
>{title}</Text>
)
}
<FlatList
data={movies}
renderItem={({ item }) => (
<MoviePoster movie={item} width={140} height={200} />
)}
keyExtractor={item => item.id.toString()}
horizontal={true}
showsHorizontalScrollIndicator={false}
onScroll={onScroll}
/>
</View>
);
};
```
Hacemos uso de `useEffect` para cambiar el estado de `isLoading` , `isLoading` permite que no se dispare
multiples veces la llamda a la api si es que se esta cargando las demas peliculas.
`HomeScreen` pasa la funcion __popularNextPage__ que viene de `useMovies`
```tsx
import React from 'react';
import { View, Text, ScrollView } from 'react-native';
import { useMovies } from '../../hooks/useMovies';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { PosterCarousel } from '../../componentes/movies/PosterCarousel';
import { HorizontalCarousel } from '../../componentes/movies/HorinzontalCarousel';
export const HomeScreen = () => {
const { isLoading, nowPlaying, popular, topRated, upcoming, popularNextPage } = useMovies();
const { top } = useSafeAreaInsets();
if (isLoading) {
return (<Text>Cargando</Text>);
}
return (
<ScrollView>
<View style={{ marginTop: top + 20, paddingBottom: 30 }} >
<PosterCarousel movies={nowPlaying} />
<HorizontalCarousel movies={popular} title="Populares"
loadNextPage={popularNextPage}
/>
<HorizontalCarousel movies={topRated} title="Top" />
<HorizontalCarousel movies={upcoming} title="Upcoming" />
</View>
</ScrollView>
);
```
El custom hook queda asi :
```tsx
import { useEffect, useState } from 'react';
import { Movie } from '../../core/entities/movie.entity';
import * as UseCases from '../../core/use-cases/movies/';
import { movieDBFetcher } from '../../config/adapters/http/movieDB.adapter';
let popularPageNumber = 1;
export const useMovies = () => {
const [isLoading, setIsLoading] = useState(true);
const [nowPlaying, setNowPlaying] = useState<Movie[]>([]);
const [popular, setPopular] = useState<Movie[]>([]);
const [topRated, setTopRated] = useState<Movie[]>([]);
const [upcoming, setUpcoming] = useState<Movie[]>([]);
useEffect(() => {
initialLoad();
}, []);
const initialLoad = async () => {
const [
nowPlayingMovies,
upcomingMovies,
topRatedMovies,
popularMovies,
] = await Promise.all([
UseCases.moviesNowPlayingUseCase(movieDBFetcher),
UseCases.upcomingUseCase(movieDBFetcher),
UseCases.topRatedUseCase(movieDBFetcher),
UseCases.popularUseCase(movieDBFetcher),
]);
setPopular(popularMovies);
setTopRated(topRatedMovies);
setUpcoming(upcomingMovies);
setNowPlaying(nowPlayingMovies);
setIsLoading(false);
};
return {
isLoading,
nowPlaying,
popular,
topRated,
upcoming,
popularNextPage: async () => {
popularPageNumber++;
const popularMovies = await UseCases.popularUseCase(movieDBFetcher, { page: popularPageNumber });
setPopular(prev => [...prev, ...popularMovies]);
},
};
};
```
Si tenes el error de que las key estan duplicadas tenes que editar el `FlatList` de `HorizontalCarousel`
de esta manera:
```tsx
<FlatList
data={movies}
renderItem={({ item }) => (
<MoviePoster movie={item} width={140} height={200} />
)}
keyExtractor={(item, index) => `${item.id.toString()}-${index}`}
horizontal={true}
showsHorizontalScrollIndicator={false}
onScroll={onScroll}
/>
```
## Ver detalle de peliculas por ID
Lo primero es obtener el id de la pelicula que se obtenemos por parametro en la ruta
`DetailsScreen` Lo cual podemos lograr de dos maneras. Con `useRoute().params` o la
propiedad `{route}`, yo usare la propiedad route porque podemos tiparlo como veremos
a continuacion con la linea ` interface Props extends StackScreenProps<RootStackParams, 'Details'> { }`
```tsx
import { StackScreenProps } from '@react-navigation/stack';
import React from 'react';
import { View, Text, ScrollView } from 'react-native';
import { RootStackParams } from '../../navigation/StackNav';
import { useMovie } from '../../hooks/useMovie';
import { MovieDetails } from '../../componentes/movie/MovieDetails';
import { MovieHeader } from '../../componentes/movie/MovieHeader';
// import { useRoute } from '@react-navigation/native';
interface Props extends StackScreenProps<RootStackParams, 'Details'> { }
export const DetailsScreen = ({ route }: Props) => {
// const {movieId} = useRoute().params
const { movieId } = route.params;
const { isLoading, movie, cast } = useMovie(movieId);
if (isLoading) { return <Text>Loading</Text>; }
return (
<ScrollView>
<View >
<MovieHeader
poster={movie?.poster!}
originalTitle={movie?.originalTitle!}
title={movie?.title!} />
<MovieDetails movie={movie!} cast={cast!} />
</View>
</ScrollView>
);
};
```
Lo siguiente es crear el __customHook__ que nos traiga las peliculas y el casting de actores.
```ts
const { isLoading, movie, cast } = useMovie(movieId);
```
```tsx
import { useEffect, useState } from 'react';
import * as UseCase from '../../core/use-cases/';
import { movieDBFetcher } from '../../config/adapters/http/movieDB.adapter';
import { FullMovie } from '../../core/entities/movie.entity';
import { Cast } from '../../core/entities/cast.entity';
export const useMovie = (movieId: number) => {
const [isLoading, setIsLoading] = useState(true);
const [movie, setMovie] = useState<FullMovie | null>();
const [cast, setCast] = useState<Cast[]>();
useEffect(() => {
loadMovie();
}, [movieId]);
const loadMovie = async () => {
setIsLoading(true);
try {
const fullMoviePromise = UseCase.getMovieById(movieDBFetcher, movieId);
const castPromise = UseCase.getMovieCastUseCase(movieDBFetcher, movieId);
const [fullMovie, cast] = await Promise.all([fullMoviePromise, castPromise]);
setMovie(fullMovie);
setCast(cast);
} catch (error) {
console.error('Error loading movie or cast:', error);
} finally {
setIsLoading(false);
}
};
return {
isLoading,
movie,
cast,
};
};
```
Ahora vayamos al `UseCase`
En `core/movie/` creamos dos ficheros, `get-by-id.use-case.ts` y `get-cast.use-case.ts`
en `get-by-id.use-case` pondremos :
```ts
import { HttpAdapter } from '../../../config/adapters/http/http.adapter';
import { MovieDBMovie } from '../../../infrastructure/interfaces/movie-db.responses';
import { MovieMapper } from '../../../infrastructure/mappers/movie.mapper';
import { FullMovie } from '../../entities/movie.entity';
export const getMovieById = async (fetcher: HttpAdapter, movieId: number): Promise<FullMovie> => {
try {
const movie = await fetcher.get<MovieDBMovie>(`/${movieId}`);
const fullMovie = MovieMapper.fromMovieDBToEntity(movie);
return fullMovie;
} catch (error) {
throw new Error(`error fetching ${movieId}`);
}
};
```
En este punto tenemos que crear la interface `FullMovie` que sera lo que devuelva nuestro mapper los datos
formateados de manera que personalizaremos nosotros para no depender de la implementacion de una
api de terceros, el `mapper` que convertira la respuesta a la api en nuestro propio objeto y la interface de la respuesta
del detalle de la pelicula que llamaremos `MovieDBMovie`
Comencemos con la interface, la crearemos en `core/entities/movie.entity.ts`
```tsx
export interface Movie {
id: number;
title: string;
description: string;
releaseDate: Date;
rating: number;
poster: string;
backdrop: string;
}
export interface FullMovie extends Movie {
genres: string[];
duration: number;
budget: number;
originalTitle: string;
productionCompanies: string[]
}
```
Ya que estamos en este folder de `entities` creemos tambien `cast.entity.ts`
```ts
export interface Cast {
id: number;
name: string;
character: string;
avatar: string;
}
```
Ahora haremos la interface de la respuesta `MovieDBMovie` en `infrastructure/interfaces/movie-db.responses.ts`
lo que deberias hacer para tener esto es ir a __postman__ y poner el id de una pelicula y copiar la respuesta
luego vas a (https://quicktype.io)[https://quicktype.io] y generas la interface automaticamente.
Ya que estamos te dejo tambien la interfaz de __Cast__
```ts
export interface NowPlayingResponse {
dates: Dates;
page: number;
results: Result[];
total_pages: number;
total_results: number;
}
export interface Dates {
maximum: Date;
minimum: Date;
}
export interface Result {
adult: boolean;
backdrop_path: string;
genre_ids: number[];
id: number;
original_language: string;
original_title: string;
overview: string;
popularity: number;
poster_path: string;
release_date: Date;
title: string;
video: boolean;
vote_average: number;
vote_count: number;
}
export interface MovieDBMovie {
adult: boolean;
backdrop_path: string;
belongs_to_collection: BelongsToCollection;
budget: number;
genres: Genre[];
homepage: string;
id: number;
imdb_id: string;
original_language: string;
original_title: string;
overview: string;
popularity: number;
poster_path: string;
production_companies: ProductionCompany[];
production_countries: ProductionCountry[];
release_date: string;
revenue: number;
runtime: number;
spoken_languages: SpokenLanguage[];
status: string;
tagline: string;
title: string;
video: boolean;
vote_average: number;
vote_count: number;
}
export interface BelongsToCollection {
id: number;
name: string;
poster_path: string;
backdrop_path: string;
}
export interface Genre {
id: number;
name: string;
}
export interface ProductionCompany {
id: number;
logo_path: null | string;
name: string;
origin_country: string;
}
export interface ProductionCountry {
iso_3166_1: string;
name: string;
}
export interface SpokenLanguage {
english_name: string;
iso_639_1: string;
name: string;
}
// Generated by https://quicktype.io
export interface MovieDBCastResponse {
id: number;
cast: MovieDBCast[];
crew: MovieDBCast[];
}
export interface MovieDBCast {
adult: boolean;
gender: number;
id: number;
known_for_department: string;
name: string;
original_name: string;
popularity: number;
profile_path: null | string;
cast_id?: number;
character?: string;
credit_id: string;
order?: number;
department?: string;
job?: string;
}
```
recuerda crear el archivo barril `index` en los casos de uso :
```sh
use-cases/
├── movie/
│ ├── get-by-id.use-case.ts
│ ├── get-cast.use-case.ts
│ ├── index.ts
├── movies/
│ ├── index.ts
│ ├── now-playing.use-case.ts
│ ├── popular.use-case.ts
│ ├── top-rated.use-case.ts
│ ├── upcoming.use-case.ts
│ ├── index.ts
```
en `movie` el index es este:
```ts
export * from './get-by-id.use-case.ts';
export * from './get-cast.use-case.ts';
```
en `movies` este :
```ts
export * from './now-playing.use-case.ts';
export * from './upcoming.use-case.ts';
export * from './top_rated.use-case.ts';
export * from './popular.use-case.ts';
```
Nos anticiparemos al componente de `Details` y crearemos un __helper__ que nos permite
formatear sumas de dinero, que usaremos al momento de renderizar lo que costo crear
la pelicula.
Vayamos a `src/config/helpers/formatter.ts`
```ts
export class Formatter {
public static currency(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(value);
}
}
```
Ya tenemos todo para crear los componentes
Creemos `MovieHeader.tsx` y `MovieDetails.tsx` en `/src/presentation/components/movie/` y la misma ruta con `cast/CastActor.tsx` con los siguiente
```tsx
import React from 'react';
import { Image, Pressable, StyleSheet, Text, View, useWindowDimensions } from 'react-native';
import { useNavigation } from '@react-navigation/native';
interface Props {
poster: string;
originalTitle: string;
title: string;
}
export const MovieHeader = ({ poster, originalTitle, title }: Props) => {
const { height: screenHeight } = useWindowDimensions();
const navigation = useNavigation();
return (
<>
<View style={{ ...styles.imageContainer, height: screenHeight * 0.7 }}>
<View style={styles.imageBorder}>
<Image
style={styles.posterImage}
source={{ uri: poster }}
/>
</View>
</View>
<View style={styles.marginContainer}>
<Text style={styles.subTitle}>{originalTitle}</Text>
<Text style={styles.title}>{title}</Text>
</View>
<View style={styles.backButton}>
<Pressable onPress={() => navigation.goBack()}>
<Text style={styles.backButtonText}>Regresar</Text>
</Pressable>
</View>
</>
);
};
const styles = StyleSheet.create({
imageContainer: {
width: '100%',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 10,
},
shadowOpacity: 0.24,
shadowRadius: 7,
elevation: 9,
borderBottomEndRadius: 25,
borderBottomStartRadius: 25,
},
imageBorder: {
flex: 1,
overflow: 'hidden',
borderBottomEndRadius: 25,
borderBottomStartRadius: 25,
},
posterImage: {
flex: 1,
},
marginContainer: {
marginHorizontal: 20,
marginTop: 20,
},
subTitle: {
fontSize: 16,
opacity: 0.8,
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
backButton: {
position: 'absolute',
zIndex: 999,
elevation: 9,
top: 35,
left: 10,
},
backButtonText: {
color: 'white',
fontSize: 25,
fontWeight: 'bold',
textShadowColor: 'rgba(0, 0, 0, 0.55)',
textShadowOffset: { width: -1, height: 1 },
textShadowRadius: 10,
},
});
```
```tsx
import React from 'react';
import { FlatList, View, Text } from 'react-native';
import { FullMovie } from '../../../core/entities/movie.entity';
import { Formatter } from '../../../config/helpers/formatter';
import { CastActor } from '../cast/CastActor';
import { Cast } from '../../../core/entities/cast.entity';
interface Props {
movie: FullMovie;
cast: Cast[];
}
export const MovieDetails = ({ movie, cast }: Props) => {
return (
<>
<View style={{ marginHorizontal: 20 }}>
<View style={{ flexDirection: 'row' }}>
<Text>{movie.rating}</Text>
<Text style={{ marginLeft: 5 }}>- {movie.genres.join(', ')}</Text>
</View>
<Text style={{ fontSize: 23, marginTop: 10, fontWeight: 'bold' }}>
Historia
</Text>
<Text style={{ fontSize: 16 }}>{movie.description}</Text>
<Text style={{ fontSize: 23, marginTop: 10, fontWeight: 'bold' }}>
Presupuesto
</Text>
<Text style={{ fontSize: 18 }}>
{Formatter.currency(movie.budget)}
</Text>
</View>
{/* Casting */}
<View style={{ marginTop: 10, marginBottom: 50 }}>
<Text style={{
fontSize: 23,
marginVertical: 10,
fontWeight: 'bold',
marginHorizontal: 20,
}}>
Actores
</Text>
<FlatList
data={cast}
keyExtractor={(item) => item.id.toString()}
horizontal
showsHorizontalScrollIndicator={false}
renderItem={({ item }) => <CastActor actor={item} />}
/>
</View>
</>
);
};
```
```tsx
import { Cast } from '../../../core/entities/cast.entity';
interface Props {
actor: Cast;
}
export const CastActor = ({ actor }: Props) => {
return (
<View style={styles.container}>
<Image
source={{ uri: actor.avatar }}
style={{ width: 100, height: 150, borderRadius: 10 }}
/>
<View style={styles.actorInfo}>
<Text style={{ fontSize: 15, fontWeight: 'bold' }}>{actor.name}</Text>
<Text style={{ fontSize: 12, opacity: 0.7 }}>{actor.character}</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginRight: 10,
paddingLeft: 10,
display: 'flex',
flexDirection: 'column',
width: 100,
},
actorInfo: {
marginLeft: 10,
marginTop: 4,
},
});
```
Ahora solo tenemos que usarlos en `DetailsScreen`
```tsx
import { StackScreenProps } from '@react-navigation/stack';
import React from 'react';
import { View, Text, ScrollView } from 'react-native';
import { RootStackParams } from '../../navigation/StackNav';
import { useMovie } from '../../hooks/useMovie';
import { MovieDetails } from '../../componentes/movie/MovieDetails';
import { MovieHeader } from '../../componentes/movie/MovieHeader';
// import { useRoute } from '@react-navigation/native';
interface Props extends StackScreenProps<RootStackParams, 'Details'> { }
export const DetailsScreen = ({ route }: Props) => {
// const {movieId} = useRoute().params
const { movieId } = route.params;
const { isLoading, movie, cast } = useMovie(movieId);
if (isLoading) { return <Text>Loading</Text>; }
return (
<ScrollView>
<View >
<MovieHeader
poster={movie?.poster!}
originalTitle={movie?.originalTitle!}
title={movie?.title!} />
<MovieDetails movie={movie!} cast={cast!} />
</View>
</ScrollView>
);
};
```
Al hacer click en una pelicula tenemos que poder ver la pantalla con los detalles y un scroll
horizontal con los actores.
## Variables de entorno
### REACT-NATIVE-DOT-ENV
[documentacion oficial](https://www.npmjs.com/package/react-native-dotenv)
1. `npm install -D react-native-dotenv`
2. Vamos a `babel.config.js` y pegamos esto:
```js
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
['module:react-native-dotenv', {
envName: 'APP_ENV',
moduleName: '@env',
path: '.env',
}],
],
};
```
3. Nos cremos un folder en la raiz a la misma altura que `src` __NO DENTRO__ , y creaso `types/env.d.ts`
__EL NOMBRE ES IMPORTANTE__
en el fichero ponemos :
```ts
declare module '@env' {
export const THE_MOVIE_DB_KEY: string;
}
```
`THE_MOVIE_DB_KEY` es la Variable que tenemos en `.env`
Ya podemos usarlo, vamos a `moviedb.adapters.ts`
```ts
import { THE_MOVIE_DB_KEY } from '@env';
import { AxiosAdapter } from './axios.adapter';
export const movieDBFetcher = new AxiosAdapter({
baseUrl: 'https://api.themoviedb.org/3/movie',
params: {
api_key: THE_MOVIE_DB_KEY ?? 'no-key',
language: 'es',
},
```
Luego hay q bajar la app y hacer un `--reset-cache`
```sh
npm start -- --reset-cache
```
y todo deberia seguir funcionando.
Finalmente para terminar nuestra app, creemos un `LOADER`
en `src/presentation/components/loader/FullScreenLoader`
```tsx
import { ActivityIndicator, View } from 'react-native';
export const FullScreenLoader = () => {
return (
<View style={{ flex: 1, justifyContent: 'center', alignContent: 'center' }}>
<ActivityIndicator size='large' color={ 'indigo' } />
</View>
)
}
```