인스타그램 클론코딩 16일차 - App

박병준·2021년 8월 26일
0
post-thumbnail

#10.0 MaterialTopTab Navigator

탭을 드래깅을 통해 스크린을 바꾼다.
npm install @react-navigation/material-top-tabs react-native-tab-view@^2.16.0


#10.1 Select Photo

MediaLibrary를 이용하여 갤러리의 사진에 접근한다.
expo install expo-media-library

getPermissionsAsync를 이용하여 접근에 대한 permission이 있는지 확인한 다음 있으면 접근하고 없으면 requestPermissionsAsync를 통해 permission을 구한다.

이제 getAssetsAsync를 이용하여 사진들을 받아온다.

//SelectPhoto.js
const getPhotos = async () => {
        const { assets: photos } = await MediaLibrary.getAssetsAsync();
        setPhotos(photos);
        setChosenPhoto(photos[0]?.uri);
    };
const getPermissions = async () => {
    const {
        status,
        canAskAgain,
    } = await MediaLibrary.getPermissionsAsync();

    if (status === "undetermined" && canAskAgain) {
        const { status } = await MediaLibrary.requestPermissionsAsync();
        if (status !== "undetermined") {
            setOk(true);
            getPhotos();
        }
    } else if (status !== "undetermined") {
        setOk(true);
        getPhotos();
    }
};

#10.2 Take Photo

카메라는 expo-camera를 이용한다.
expo install expo-camera

먼저 카메라 접근에 관련된 permission을 구한다.

const getPermissions = async () => {
    const { granted } = await Camera.requestPermissionsAsync();
    setOk(granted);
};
useEffect(() => {
    getPermissions();
}, []);

카메라 component를 사용할 수 있다.
Type front, back이 있는데 전면, 후면 카메라이다.
<Camera type={cameraType} style={{ flex: 1 }} zoom={zoom} flashMode={flashMode}/>

전면 후면 toggle하는 함수

const onCameraSwitch = () => {
    if (cameraType === Camera.Constants.Type.front) {
        setCameraType(Camera.Constants.Type.back);
    } else {
        setCameraType(Camera.Constants.Type.front);
    }
};

<TouchableOpacity onPress={onCameraSwitch}>
    <Ionicons
        size={30}
        color="white"
        name={
        cameraType === Camera.Constants.Type.front
                ? camera-reverse"
                : "camera"
        }
    />
</TouchableOpacity>

Slider를 이용하여 줌기능을 만든다.
expo install @react-native-community/slider
onValueChange prop을 통해 값이 바뀔 때마다 카메라의 줌 값을 변경해준다.

const [zoom, setZoom] = useState(0);

const onZoomValueChange = (e) => {
    setZoom(e);
};

<SliderContainer>
    <Slider
        style={{ width: 200, height: 20 }}
        minimumValue={0}
        maximumValue={1}
        minimumTrackTintColor="#FFFFFF"
        maximumTrackTintColor="rgba(255, 255, 255, 0.5)"
        onValueChange={onZoomValueChange}
    />
</SliderContainer>

Camera의 FlashMode도 사용 가능하다.

const [flashMode, setFlashMode] = useState(Camera.Constants.FlashMode.off);
// off, on, auto 순으로 바뀐다.
const onFlashChange = () => {
    if (flashMode === Camera.Constants.FlashMode.off) {
        setFlashMode(Camera.Constants.FlashMode.on);
    } else if (flashMode === Camera.Constants.FlashMode.on) {
        setFlashMode(Camera.Constants.FlashMode.auto);
    } else if (flashMode === Camera.Constants.FlashMode.auto) {
        setFlashMode(Camera.Constants.FlashMode.off);
    }
};

<TouchableOpacity
    onPress={onFlashChange}
    style={{ marginRight: 30 }}
>
    <Ionicons
        size={30}
        color="white"
        name={
            flashMode === Camera.Constants.FlashMode.off
                ? "flash-off"
                : flashMode === Camera.Constants.FlashMode.on
                    ? "flash"
                    : flashMode === Camera.Constants.FlashMode.auto
                        ? "eye"
                        : ""
             }
        />
</TouchableOpacity>

takePictureAsync를 이용하여 카메라를 찍는다.
onCameraReady를 통해 카메라가 사용가능한 상태인지 확인한다.
quality는 최대 1까지 가능하다.
exif는 사진이 가지고 있는 메타데이터이다.

const camera = useRef();
const [cameraReady, setCameraReady] = useState(false);

const onCameraReady = () => setCameraReady(true);
const takePhoto = async () => {
    if (camera.current && cameraReady) {
        const photo = await camera.current.takePictureAsync({
            quality: 1,
            exif: true,
        });
    }
};

<Camera
    type={cameraType}
    style={{ flex: 1 }}
    zoom={zoom}
    flashMode={flashMode}
    ref={camera}
    onCameraReady={onCameraReady}
>
      
<TakePhotoBtn onPress={takePhoto} />

takePictureAsync를 사용하면 사진이 캐시에 저장된다.

갤러리에 저장하고 싶을 때는 아래 두가지 방법이 있다.

  • createAssetAsync(uri)
    asset이란 객체를 반환한다.
  • saveToLibraryAsync(uri)
    아무것도 반환하지 않는다.
const goToUpload = async (save) => {
    if (save) {
        await MediaLibrary.saveToLibraryAsync(takenPhoto);
    }
    navigation.navigate("UploadForm", {
        file: takenPhoto,
    });
};

useIsFocused를 사용하면 해당 스크린을 보고있는지 알 수 있다.

const isFocused = useIsFocused();
return (
    <Container>
        {isFocused ? <StatusBar hidden={true} /> : null}

#10.3 Upload Photo

upload할 때 이미지를 파일 형식으로 보내게 된다.
이 때 ReactNativeFile을 이용한다.
expo install apollo-upload-client

const onValid = ({ caption }) => {
    const file = new ReactNativeFile({
        uri: route.params.file,
        name: `1.jpg`,
        type: "image/jpeg",
    });
    uploadPhotoMutation({
        variables: {
            caption,
            file,
        },
    });
};

다른 쿼리의 캐시를 수정하려면 id를 "ROOT_QUERY"로 작성하고 field에 바꾸고 싶은 쿼리의 캐시를 적는다.

seeFeed는 offset이라는 인자를 가지고 있어 사실 불가능하지만 캐시에서 인자를 무시해줬기 때문에 가능하다.

const updateUploadPhoto = (cache, result) => {
    const {
        data: { uploadPhoto },
    } = result;
    if (uploadPhoto.id) {
        cache.modify({
            id: "ROOT_QUERY",
            fields: {
                seeFeed(prev) {
                    return [uploadPhoto, ...prev];
                },
            },
        });
        navigation.navigate("Tabs");
    }
};

upload할 때 그냥 httpLink는 ReactNativeFile의 파일 형식을 읽을 수가 없다.
따라서 apollo-upload-client의 createUploadLink를 이용한다.

//apollo.js
const uploadHttpLink = createUploadLink({
    uri: "http://localhost:4000/graphql",
});

const client = new ApolloClient({
    link: authLink.concat(onErrorLink).concat(uploadHttpLink),
    cache,
});

link를 이어서 쓸 때는 httpLink를 항상 마지막에 써야한다.
httpLink는 종료하는 Link이다.

const httpLink = createHttpLink({
    uri: "http://192.168.0.43:4000/graphql",
});
const authLink = setContext((_, { headers }) => {
    return {
        headers: {
            ...headers,
            token: tokenVar(),
        },
    };
});
const onErrorLink = onError(({ graphQLErrors, networkError }) => {
    if (graphQLErrors) {
        console.log(`GraphQL Error`, graphQLErrors);
    }
    if (networkError) {
        console.log("Network Error", networkError);
    }
});
const client = new ApolloClient({
    link: authLink.concat(onErrorLink).concat(httpLink),
    cache,
});

#11.0 Subscription

subscription을 이용하기 위해서는 webSocket을 설정해야 한다.
npm install subscriptions-transport-ws

wsLink를 만든다.
인증이 가능하도록 토큰을 실은 link를 보낸다.

//apollo.js
const wsLink = new WebSocketLink({
    uri: "ws://localhost:4000/graphql",
    options: {
        connectionParams: () => ({
            token: tokenVar(),
        }),
    },
});

@apollo/client의 split함수를 통해 subscription이면 ws로 아니면 http로 통신하게 한다.
split의 3개 인자 중 첫번 째는 Boolean을 반환하는 함수, 두번째는 true일 때 link, 세번째는 false일 때 Link이다.

//apollo.js
const httpLinks = authLink.concat(onErrorLink).concat(uploadHttpLink);

const splitLink = split(
    ({ query }) => {
        const definition = getMainDefinition(query);
        return (
            definition.kind === "OperationDefinition" &&
            definition.operation === "subscription"
        );
    },
    wsLink,
    httpLinks
);

const client = new ApolloClient({
    link: splitLink,
    cache,
});

useSubscription()을 이용할 수도 있다. 하지만 이 hook을 사용하면 반환값을 받기 때문에 그 반환값으로 할 수 있는 것이 없다.

useQery의 subscribeToMore로 대신한다.

//Room.js
const { data, loading, subscribeToMore } = useQuery(ROOM_QUERY, {
        variables: {
            id: route?.params?.id,
        },
    });
    useEffect(() => {
        if (data?.seeRoom) {
            subscribeToMore({
                document: ROOM_UPDATES,
                variables: {
                    id: route?.params?.id,
                },
            });
        }
    }, [data]);
profile
뿌셔뿌셔

0개의 댓글

관련 채용 정보