RN) 토이플젝_영화앱_2

소정·2023년 6월 8일
0

ReactNative

목록 보기
17/17
post-thumbnail

[0] types.tsx


//1. 최상위 스택 네비게이터의 화면 리스트 타입 지정
export type RootScreenList = {
    Intro : undefined,
    LoginNav : undefined,
    MainNav : undefined, //undefined = putExtra
}


// 2. LoginNav Stack Navigator 화면 리스트 지정
export type LoginNavScreenList = {
    Login : undefined,
    SignUp : undefined,
    ResetPassWd : undefined,

    //메인 Nav화면으로 전환할 수 있도록 등록
    MainNav : undefined,
}


// 3. MainNav BottomTabnavigator 화면 리스트 타입
export type MainNavScreenList = {
    MovieNav : undefined,
    Favorite : undefined,
    Community : undefined,
}


//4. MovieNav Stack Navigator 화면 리스트 지정
export type MovieNavScreenList = {
    MovieList : undefined, 
    MovieDetail : undefined,

    //로그 아웃시에 Intro화면으로 이동하기 위해 리스트 등록 추가
    Intro : undefined
}


// 빅카탈로그용
// 영화 1개의 정보 타입 중 필요한 정보 받음
export interface MovieInfo {
    id : string,
    title : string,
    year : string,
    genres : string[],
    large_cover_image : string,
}



[1] MovieNav

  • 메뉴에서 보여줄 페이지 지정

import React from 'react'

// 스택네비게이터 객체 생성 [MovieList , MovieDetail 화면 등록]

import { createStackNavigator } from '@react-navigation/stack'
import { MovieNavScreenList } from '../types'

const Stack = createStackNavigator<MovieNavScreenList>()


//등록할 스크린 컴포넌트 import
import MovieList from './MovieList'
import MovieDetail from './MovieDetail'

export default function MovieNav(): JSX.Element{
    return(
        <Stack.Navigator>
            <Stack.Screen name= 'MovieList' component={MovieList}></Stack.Screen>
            <Stack.Screen name='MovieDetail' component={MovieDetail}></Stack.Screen>
        </Stack.Navigator>
    )
}



[2] MovieList

1. BigCatalogList

  • 메인페이지 상단에 보여지는 viewPager 담당 부분
  • REST API를에 데이터를 전달 받아 fetch로 네트워크 연결 하여 데이터 읽어오는 부분
  • Hook기술 중 화면이 처음 보여지거나 갱신될 때 자동 호출되는 useEffect()를 사용하여 데이터 로드
    => UI도 없는데 데이터 보여달라고 하면 다운되니까...
  • 이 받아온 데이터를 보여줄 화면 UI는 BigCatalog.tsx 파일로 따로 제작
import React, { useEffect, useState } from "react";
import { View, FlatList, StyleSheet, Text } from "react-native";

//영화 임포트
import BigCatalog from "./BigCatalog";


type Props = {
    url:string,
}

export default function BigCatalogList(props:Props):JSX.Element {

    //REST API를 이용하여 파싱한 영화데이터들 state변수
    const [movies,setMovies] = useState([])

    //영화리스트
    const loadData = ()=> {
        //전달받은 url 을 통해 json으로 인기 영화 정보를 읽어오기
        fetch(props.url)
            .then(response=>response.json())
            .then(json => setMovies(json.data.movies))
    }

    //화면이 처음 보여지거나 갱신될 때 자동 호출되는 Hook기술
    useEffect(()=>loadData())

    
    return(
        <View style={style.container}>

            {/* 1. 가장 상단 광고 viewpager부분 */}
            <FlatList
                horizontal={true} //스크롤 가로
                pagingEnabled = {true} //이 속성 쓰면 viewPager느낌 남
                data={movies}
                renderItem={(obj)=>{ //renderItem엔 object(서버에서 받아온 정보 가진) 가 온다
                    return <BigCatalog movie={obj.item}></BigCatalog>
                }}></FlatList>
        </View>
    )

}

const style = StyleSheet.create({

    container : {
        height : 300,

    },
})


2. BigCatalog

BigCatalogList 안에서 item 화면 담당 부분!!

import React from 'react'
import {View, Text, TouchableOpacity, Image, StyleSheet, Dimensions} from 'react-native'
import { MovieInfo } from '../types'


type Props = {
    movie : MovieInfo //타입 지정하기 위해 데이타클래스 필요
}

export default function BigCatalog(props : Props): JSX.Element {
    return (
        <TouchableOpacity>
            <Image 
                //화면의 가로 사이즈를 얻어와서 이미지의 가로 사이즈로 지정 : Dimensions.get('window')
                style={{width : Dimensions.get('window').width, height : 300}}
                source={{uri: props.movie.large_cover_image}}></Image>
            {/* 영화의 제목 , 개봉일, 장르 등의 정보를 보여주기 */}
            <View style={style.infoContainer}>
                <Text style={style.labelYear}>{props.movie.year}년 개봉</Text>
                <View style={style.labelContainer}>
                    <Text style={style.labelTitle}>{props.movie.title}</Text>
                    <Text style={style.labelGenres}>{props.movie.genres.join(' | ')}</Text>
                </View>
            </View>
        </TouchableOpacity>
    )
}

const style= StyleSheet.create({
    infoContainer:{
        position:'absolute',
        bottom:0,
        width:'100%',
        alignItems:'flex-start',
    },
    labelYear:{
        color:'#FFFFFF',
        padding:8,
        fontWeight:'bold',
        marginLeft:4,
        backgroundColor:'#E70915',
    },
    labelContainer:{
        backgroundColor:'#141414',
        width:'100%',
        padding:8,
        opacity: 0.8,
    },
    labelTitle:{
        fontSize:18,
        fontWeight:'bold',
        color:'#FFFFFF',
        padding:8,
    },
    labelGenres:{
        fontSize:12,
        color:'#FFFFFF',
        padding:8,
    }
})

3. MovieList.tsx

3.1) MovieList에서 받아온 파싱할 url 데이터

3.2) 제작 순서

  1. MovieList에서 받아올 데이터들 Props type 지정
  2. 영화들의 정보를 가지는 state 변수 생성
    => 변수명과 그 변수 값을 바꾸는 메소드 함께 다님
    => 무비 정보가 하나가 아닌 여러개라 배열로 만듦
  3. 영화정보 받아오는 기능 메소드 url fetch 하기
  4. 화면이 처음 보여지거나 갱신될 때 자동 호출되는 Hook기술 메소드
    => useEffect() 할 때 loadData() 실행
  5. 대량의 데이터 부르기 resycler view 대신하는 FlatList로 화면 구성
import React, { useEffect, useState } from "react";
import { View, Text, StyleSheet, Image, FlatList, TouchableOpacity } from "react-native";
import { MovieInfo } from "../types";

//1. 메인에서 받아올 데이터들 지정
//자료형 만들기 키워드 type
type Props = {
    title : string,
    url : string,
    onPress? : (id:string) => void | undefined 
}

export default function SmallCatalogList(props : Props):JSX.Element {

    //2. 영화들의 정보를 가지는 state 변수
    //변수명과 그 변수 값을 바꾸는 메소드 함께 다님
    const [movies, setMovies] = useState<MovieInfo[]>([]) //무비 정보가 하나가 아닌 여러개라 배열로 만듦

    //3. 영화정보 받아오는 기능 메소드
    const loadData = () => {
        //전달 받은 url을 통해 json으로 인기 영화정보를 읽어오기
        fetch(props.url)
            .then(res => res.json() ) //응답 결과(res)를 json으로 파싱해줘 
            .then(json => setMovies(json.data.movies))

    }       

    //4. 화면이 처음 보여지거나 갱신될 때 자동 호출되는 Hook기술 메소드
    useEffect( ()=> loadData())

    return(
        <View style={style.container}>
            {/* 서브 타이틀 제목 표시 */}
            <Text style={style.title}>{props.title}</Text>

            {/* 5. 대량의 데이터 부르기 resycler view 대신하는 FlatList */}
            <FlatList
                horizontal={true}
                data={movies} //대량의 데이터
                // renderItem={( obj )=> { //리턴으로 obj 객체 하나 옴 : {item, index}
                //     return (
                //         <TouchableOpacity>
                //             <Image source={{uri : obj.item.large_cover_image}}></Image>
                //         </TouchableOpacity>
                //     ) 
                // }//아이템 모양
                renderItem={( {item,index} )=>{ //obj객체 : {item,index} //구조분해할당
                    return (
                        <TouchableOpacity activeOpacity={0.8} style={{paddingLeft:4, paddingRight:4}} onPress={()=> props.onPress!!(item.id)}>
                            <Image source={{uri:item.large_cover_image}} style={{width:140,height:200}}></Image>
                        </TouchableOpacity>
                    )
                }
            } 
            ></FlatList>

        </View>
    )

}

const style= StyleSheet.create({

    container : {
        flex :1,
        marginTop : 8,
        marginBottom : 8,
    },
    title : {
        padding : 8,
        fontSize : 16,
        fontWeight : 'bold',

    }

})

4. MovieList.tsx

movieList

import React, {useEffect} from 'react'
import { ScrollView, Text, StyleSheet, Button, TouchableOpacity, Image, Alert } from 'react-native'
import AsyncStorage from '@react-native-async-storage/async-storage'

//내가 만든 컴포넌트 임포트
import BigCatalogList from '../components_movie/BigCatalogList'
import SmallCatalogList from '../components_movie/smallCatalogList'


import { StackScreenProps } from '@react-navigation/stack'
import { MovieNavScreenList } from '../types'
type MovieListProp = StackScreenProps<MovieNavScreenList, 'MovieList'>


export default function MovieList(props:MovieListProp):JSX.Element {

    //헤더 모양 설정 - rander() 메소드로 UI완료 된 후 설정 가능함
    //리턴 다음해 해야됨
    //화면이 그려진 후 발동하는 라이프사이클 메소드 효과를 주는 Hooks 기술
    // useState() , useEffect()

    const setHeader = () => {
        props.navigation.setOptions({
            headerTitleAlign : 'center',
            headerRight : () => (
                <TouchableOpacity style={{marginRight:16}} onPress={()=>Alert.alert('매뉴')}>
                    <Image source={require('../Images/ic_dot_menu.png')}></Image>
                </TouchableOpacity>
            ),
            headerLeft : () => (
                <TouchableOpacity style={{flexDirection : 'row' ,marginLeft:16, alignItems:'center'}} 
                    onPress={async ()=>{
                        await AsyncStorage.removeItem('email') //얘 비동기 작업임
                        props.navigation.replace('Intro') //await-async로 동기 맞춰줘야함
                    }}>
                    <Image source={require('../Images/Tabs/ic_profile.png')}></Image>
                    <Text style={{marginLeft:4}}>로그아웃</Text>
                </TouchableOpacity>
            )
        })
    }

    useEffect(()=> setHeader()) //useEffect에 의해 UI 작업 후 setHeader() 발동
    


    //인기 영화 정보 불러오는 url [get방식]
    const bigUrl="https://yts.lt/api/v2/list_movies.json?sort_by=like_count&order_by=desc&limit=5";

    // 최신등록순 영화 정보 불러오는 url 
    const recentUrl="https://yts.lt/api/v2/list_movies.json?sort_by=date_added&order_by=desc&limit=10";
    
    // 평점순 영화 정보 불러오는 url 
    const ratingtUrl="https://yts.lt/api/v2/list_movies.json?sort_by=rating&order_by=desc&limit=10";
    
    // 다운로드순 영화 정보 불러오는 url 
    const downloadUrl="https://yts.lt/api/v2/list_movies.json?sort_by=download_count&order_by=desc&limit=10";
 

    return (
        <ScrollView style={style.root}>
            {/* 큰 이미지로 가장 높은 선호도를 가진 가로 스크롤(페이징)로 보여주기 */}
            {/* 이 작업을 별도의 컴포넌트를 만들어서 제작하면 코드가 분리되어 유지보수가 용이함 */}
            <BigCatalogList url={bigUrl}
                onPress={(id)=>props.navigation.navigate('MovieDetail', {id})}
            ></BigCatalogList>


            {/* 세 종류의 영화 목록이 모두 같은 디자인을 가짐 별도의 컴포넌트 만들어서 재사용 */}
            {/* 최신 등록 순 */}
            <SmallCatalogList 
                title='최신등록순'
                url={recentUrl}

                // onPress={(id)=>props.navigation.navigate('MovieDetail', {'id':id})} //식별자와 변수이름 같으면 생략 가능
                onPress={(id)=>props.navigation.navigate('MovieDetail', {id})} //식별자와 변수이름 같으면 생략 가능
            ></SmallCatalogList>

            {/* 평점순 */}
            <SmallCatalogList 
                title='평점순'
                url={ratingtUrl}

                onPress={(id)=>props.navigation.navigate('MovieDetail', {'id':id})}
            ></SmallCatalogList>

            {/* 다운로드순 */}
            <SmallCatalogList 
                title='다운로드순'
                url={downloadUrl}

                onPress={(id)=>props.navigation.navigate('MovieDetail', {'id':id})}
            ></SmallCatalogList>


        </ScrollView>
    )
}


const style = StyleSheet.create({

    root : {
        flex :1,
        backgroundColor : '#FEFFFF'
    },
    
})



[3] MovieDetail

목록 item을 누르면 디테일 페이지로 이동
그러려면 movieList에 있는 navigation능력을 각 SmallCatalogList와 BigCatalog로 넘겨줘야함 onPress 함수 기능을 각 화면에 prop로 넘겨줘서 버튼 클릭 시 디테일 페이지로 이동하도록함

import React, {useState, useEffect} from 'react'
import { View, Text, StyleSheet, ScrollView, ActivityIndicator } from 'react-native'


import { StackScreenProps } from '@react-navigation/stack'
import { MovieInfo, MovieNavScreenList } from '../types'
import BigCatalog from '../components_movie/BigCatalog'
type MovieDetailProp = StackScreenProps<MovieNavScreenList,'MovieDetail'>


export default function MovieDetail(props:MovieDetailProp):JSX.Element {

    //영화정보들을 저장할 변수
    const [movie, setMovie] = useState<MovieInfo>()

    //전달받은 id로 영화상세정보를 fetch하는 기능 메소드
    const loadData = () => {
        const {id} = props.route.params!! //구조분해 할당

        fetch('https://yts.lt/api/v2/movie_details.json?movie_id='+id+'&with_image=true&with_cast=true')
            .then(res=> res.json())
            .then(json => setMovie(json.data.movie))
    }

    useEffect(() => loadData())

    //fetch 데이터가 있는지 확인하여 없다면 로딩 화면이 보이도록
    return movie? (
        <ScrollView style={style.root}>
            {/* 1. 상세화면에 큰 이미지는 BigCatalog 재사용 */}
            <BigCatalog movie={movie}></BigCatalog>

            {/* 2. 영화 정보 출력 영역 */}
            <View>
                <Text style={style.title}>영화정보</Text>
                <View style={style.infoContainer}>
                    <Text>{movie.runtime}</Text>
                    <Text>평점 : {movie.rating}</Text>
                    <Text>좋아요 : {movie.like_count}</Text>
                </View>
            </View>

            {/* 3. 줄거리 출력 영역 */}
            <View>
                <Text style={style.title}>줄거리</Text>
                <Text style={style.description}>{movie.description_full}</Text>
            </View>

            {/* 4. 배우 캐스팅 정보 출력 */}


            {/* 5. 스크린 샛 이미지들 출력화면 */}


        </ScrollView>
    ) : (
        <View style={style.loadingContainer}> 
            <ActivityIndicator size='large' color='#E70915'></ActivityIndicator>
        </View>
    )
    
}

const style = StyleSheet.create({

    root : {
        flex :1,
        backgroundColor : '#FFFFFF',
    },
    loadingContainer : {
        flex : 1,
        justifyContent : 'center',
        alignItems : 'center',
    },
    title : {
        fontSize : 16,
        fontWeight : 'bold',
        paddingTop : 24,
        paddingRight : 16,
        paddingLeft : 16,
        paddingBottom : 8,
    },
    infoContainer : {
        flexDirection : 'row',
        justifyContent : 'space-between',
        paddingLeft : 16,
        paddingRight : 16,
    },
    description : {
        paddingLeft : 16,
        paddingRight : 16,
        marginBottom : 16,
    }
})
profile
보조기억장치

0개의 댓글