인스타그램, 페이스북등을 보면 유저가 선택한 사진의 순서를 바꿀 수 있다.
ootdzip
에도 해당 기능을 통해 유저가 사진 순서를 바꿀 수 있게 해주기로 했다.
아니라면
확대만 시켜준다.이라면
선택 해제 한다.//갤러리 페이지 진입 시 'OOTD' 메시지 보냄
useEffect(() => {
sendReactNativeMessage({ type: 'OOTD' });
}, []);
getphoto.tsx
import * as ImagePicker from "expo-image-picker";
const usegetPhotos = () => {
const [status, requestPermission] = ImagePicker.useMediaLibraryPermissions();
const getPhoto = async (target: string) => {
try {
//권한 설정
if (!status?.granted) {
const permission = await requestPermission();
if (!permission.granted) {
return null;
}
}
// 사진 선택 실행
const item = target !== "OOTD";
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
aspect: [1, 1],
quality: 0.1,
allowsMultipleSelection: !item, //OOTD는 여러장 선택 가능, 옷은 한장만 선택 가능
});
//사진 선택 취소
if (result.canceled) {
return null;
}
return result;
} catch (error) {
console.error("에러 발생:121", error);
}
};
return { getPhoto };
};
export default usegetPhotos;
Main.tsx
getSelectedPhoto(parseData.type).then(async (res) => {
const formData = new FormData();
//이미지 개수만큼 진행
res.assets.forEach((item) => {
const type = `${item.type}/${item.uri.split(".").pop()}`;
const localUri = item.uri;
const name = localUri.split("/").pop();
formData.append("images", {
uri: localUri,
type,
name,
} as unknown as Blob);
});
});
fetch("https://ootdzip.com/api/v1/s3/image", {
method: "POST",
headers: {
"Content-Type": "multipart/form-data",
Authorization: `Bearer ${await SecureStore.getItemAsync(
"accessToken"
)}`,
},
body: formData,
})
.then((response) => response.json())
.then((response) => {
sendMessage({
webViewRef,
type: parseData.type,
payload: response.result.imageUrls,
});
})
.catch((error) => {
console.log("error", error);
});
});
accessToken
을 s3 업로드 api에 사용 해야하는데, 그 과정에서 액세스 토큰 만료가 되는 경우가 발생했다.
그래서 나는 갤러리 사진 선택 메세지를 보낼 때 토큰을 재발급 한 뒤 함께 보내 refresh 시키기로 했다.
const { getNewToken } = PublicApi();
const getToken = async () => {
//토큰을 재발급 하는 함수
await getNewToken();
//액세스 토큰 보냄
sendReactNativeMessage({
type: 'accessToken',
payload: localStorage.getItem('accessToken'),
});
//리프레시 토큰 보냄
sendReactNativeMessage({
type: 'refreshToken',
payload: localStorage.getItem('refreshToken'),
});
};
//갤러리 페이지 진입 시 토큰과 'OOTD' 메시지 보냄
useEffect(() => {
getToken();
sendReactNativeMessage({ type: 'OOTD' });
}, []);
이미지 크기가 너무 크다는 이유로 서버에서 api 요청이 실패했다는 메시지가 왔다. 최근 휴대폰 카메라들의 성능이 좋아지면서 사진의 크기가 많이 커진것이다. 이에 우리는 expo-image-picker
의 option중 하나인 quality(0.1~1)
속성을 낮추기로 했다.
어디까지 낮춰야 화질의 변화가 많이 없을까싶어 테스트를 진행 해 보았는데 0.1까지 낮춰도 충분히 잘 보이는걸로 확인되었다.
실제로 파일의 크기도 많이 작아졌고 성공 응답을 받을 수 있었다. 하지만 며칠뒤 스크린샷 이미지들에서 에러가 발생한다는 소식을 들었고 확인해보았는데, error [SyntaxError: JSON Parse error: Unexpected character: <]
에러 메세지가 발생했다.
이미지 업로드 api 후 서버의 요청을 json으로 변환하는 과정을 거치는데 해당 과정에서 에러가 난 것 같았다.
그래서 해당 코드에서 console.log(response)
를 해보니 아래와 같은 로그가 나왔다.
status:413
에 대한 자료를 찾아보니 nginx 데이터 용량 초과였고, nginx 데이터 용량 설정을 따로 하지 않으면 1mb로 제한되어있다는 사실을 알게 되었다. 제한 용량을 늘려주었고, 잘 작동하게 되었다..!
Expo go
를 활용해 테스트를 진행할 때는 모든것이 순조로웠다. 그리고 비공개 테스트.. 안드로이드 기기를 가진 친구에게 연락이 왔다. "사진 선택이 안돼"
그래서 급히 내 안드로이드 기기로 비공개 테스트 진행해보았는데, 안된다. 바로 Expo go
로 실행해 보았는데 잘된다. ???? 물음 표에 빠진 나는 백엔드 강민이와 한시간 가량 모든 경우의 수를 찾았다. 그런데 아무리 찾아도 오류가 보이지 않았고, 내일 다시 하자 라는 말을 하기 직전 눈에 보이는 http
.. 도메인을 연결하기 전 http
로 api를 보내고 있었다. 그래서 이거 때문이구나 하는 생각으로 https
로 바꾸자 정상 작동하였다. 아무래도 플레이 스토어에서 http
로의 호출을 막고 있는게 아닌가 생각이 된다.
const [selectedImage, setSelectedImage] = useState<ImageWithTag>([]); //선택한 이미지 리스트
const [realTouch, setRealTouch] = useState<number>(100); // 현재 선택한 인덱스
선택된 이미지가 없을때 회색 이미지를 기본으로 렌더링해준다.
{selectedImage.length === 0 && (
<S.BigImage>
<NextImage
className="bigImage"
src={defaultImage}
alt="basic"
fill={true}
/>
</S.BigImage>
)}
인덱스를 비교해 현재 선택된 인덱스의 사진을 렌더링 해준다.
{selectedImage &&
selectedImage.map((item, index) => {
if (item.ootdId === realTouch)
return (
<S.BigImage key={index}>
<NextImage
className="bigImage"
src={item.ootdImage}
alt="bigImage"
fill={true}
/>
</S.BigImage>
);
})}
{imageAndTag &&
imageAndTag.map((item, index) => {
return (
<S.SmallImage key={index} state={item.ootdId === realTouch}>
<NextImage
className="smallImage"
onClick={() => onClickImage(item.ootdId, item.ootdImage)}
src={item.ootdImage}
alt="smallImage"
fill={false}
width={106}
height={106}
/>
</S.SmallImage>
);
})}
선택한 이미지 리스트에서 해당 ootdId
를 가지고 있는 이미지를 제외한다.
만약 선택한 이미지 리스트의 개수가 한개뿐이라면 realTouch
상태를 100으로 업데이트 한다.
const onClickImage = (ootdId: number, ootdImage: string) => {
const newSelectedImage = selectedImage.filter((item) => {
if (item.ootdId !== ootdId) {
return item;
}
});
if (selectedImage.length === 1) {
setRealTouch(100);
} else {
setRealTouch(newSelectedImage[newSelectedImage.length - 1].ootdId);
}
setSelectedImage(newSelectedImage);
};
선택한 이미지를 이미지 선택 리스트에 마지막 인덱스로 업데이트
if (ootdId !== realTouch) {
setRealTouch(ootdId);
const alive = selectedImage.filter(
(item) => item.ootdId === ootdId
).length;
if (!alive) {
setRealTouch(ootdId);
setSelectedImage([
...selectedImage,
{ ootdImage: ootdImage, ootdId: ootdId },
]);
}
return;
}
만약 해당 이미지의 id가 선택된 이미지에 포함 되어있다면 인덱스 표시, 그렇지 않다면 빈 숫자
표시
{imageAndTag &&
imageAndTag.map((item, index) => {
const matchingIndex = selectedImage.findIndex(
(items) => item.ootdId === items.ootdId
);
const isMatched = matchingIndex !== -1;
return (
<S.ImageNumber
onClick={() => onClickImage(item.ootdId, item.ootdImage)}
state={isMatched}>
{isMatched ? (
<Caption1>{matchingIndex + 1}</Caption1>
) : ('')}
</S.ImageNumber>
);
})}
webview
에서 native
의 갤러리의 사진을 선택하고 순서를 변경하는 기능을 구현했다. 역시 native
가 섞이니 더 어려운 것 같다. 그래도 완성하고 나니 뿌듯하다!