탭을 드래깅을 통해 스크린을 바꾼다.
npm install @react-navigation/material-top-tabs react-native-tab-view@^2.16.0
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();
}
};
카메라는 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를 사용하면 사진이 캐시에 저장된다.
갤러리에 저장하고 싶을 때는 아래 두가지 방법이 있다.
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}
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, });
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]);