카메라, 갤러리 기능

miin·2024년 2월 3일
0

ReactNative

목록 보기
10/10

기능목록

  • 사진첨부하기 클릭시 카메라와 갤러리를 선택 할 수 있는 모달
  • 카메라
    - 카메라 권한 묻기
    • 사진촬영
    • 사진 촬영 시 재촬영 또는 저장하기 선택
  • 갤러리
    - 사진 권한 묻기
    • 사진 목록 불러오기
    • 사진 선택하기, 선택시 카운트 올리기

모달

  const gotoCamera = (type: string) => {
    navigation.navigate('CameraScreen', {
      screen: 'CameraScreen',
      params: {type},
    });
    setIsShowModal(false);
  };

  return( <Modal
        isVisible={isShowModal}
        onBackdropPress={toggleModal}
        style={addFileModalStyle}>
        <AttachTypeContainer>
          <TypeContainer onPress={() => gotoCamera('camera')}>
            <IconCircle>
              <CameraIcon />
            </IconCircle>
            <TypeText>카메라</TypeText>
          </TypeContainer>

          <TypeContainer onPress={() => gotoCamera('gallery')}>
            <IconCircle>
              <GalleryIcon />
            </IconCircle>
            <TypeText>갤러리</TypeText>
          </TypeContainer>
        </AttachTypeContainer>

        <AttachGuide>첨부 방법을 선택해 주세요.</AttachGuide>
      </Modal>
)

const CameraScreen = () => {
  const {params}: RouteProp<CameraProps> = useRoute();

  return (
    <Container>
      {params?.type === 'camera' && <CameraModule />}
      {params?.type === 'gallery' && <GalleryModule />}
    </Container>
  );
};

카메라

const CameraModule = () => {
  const cameraRef = useRef<Camera | null>(null);
  const [cameraType, setCameraType] = useState(CameraType.back);
  const [photo, setPhoto] = useState('');
  const [isPermission, setIsPermission] = useState(false);

  const takePictureHandler = async () => {
    // cameraRef가 없으면 해당 함수가 실행되지 않게 가드
    if (!cameraRef.current) return;

    // takePictureAsync를 통해 사진을 찍음
    const photo = await cameraRef.current.takePictureAsync();

    setPhoto(photo.uri);
  };

  //접근 권한 묻기
  useEffect(() => {
    const getCameraPermission = async () => {
      const {status} = await Camera.requestCameraPermissionsAsync();
      if (status === 'granted') {
        setIsPermission(true);
      } else {
        alert('카메라 접근 허용은 필수입니다.');
      }
    };

    getCameraPermission();
  }, []);

  const savePicture = async () => {
    if (photo) {
      const {status} = await MediaLibrary.requestPermissionsAsync();

      if (status === 'granted') {
        const asset = await MediaLibrary.createAssetAsync(photo);
        retakePicture();
        alert('저장완료!');
        try {
          await MediaLibrary.createAlbumAsync('Camera', asset, false);
        } catch (error) {
          console.error(
            'An error occurred while saving the photo to camera roll:',
            error,
          );
        }
      }
    }
  };

  const retakePicture = () => {
    setPhoto('');
  };

  return (
    <>
      <CustomHeader isBackButton></CustomHeader>
      {isPermission && (
        <>
          {photo !== '' ? (
            <View
              style={{
                flex: 1,
                justifyContent: 'center',
                alignItems: 'center',
              }}>
              <Image
                source={{uri: photo}}
                style={{flex: 1, width: '100%'}}
                resizeMode="contain"
              />
              <AfterBtn>
                <Pressable onPress={retakePicture}>
                  <ButtonText>다시 찍기</ButtonText>
                </Pressable>
                <Pressable onPress={savePicture}>
                  <ButtonText>저장</ButtonText>
                </Pressable>
              </AfterBtn>
            </View>
          ) : (
            <>
              <Camera
                ref={cameraRef}
                type={cameraType}
                autoFocus={AutoFocus.on}
                style={{flex: 1}}
              />
              <ShootingBtn onPress={takePictureHandler}>
                <ButtonText>촬영</ButtonText>
              </ShootingBtn>
            </>
          )}
        </>
      )}
    </>
  );
};

갤러리

const GalleryModule = () => {
  const [endCursor, setEndCursor] = useState('');
  const [hasNextPage, setHasNextPage] = useState(false);
  const [galleryList, setGalleryList] = useState<MediaLibrary.Asset[]>([]);
  const [isPermission, setIsPermission] = useState(false);
  const [selectedPhotos, setSelectedPhotos] = useRecoilState(SelectedPhotos);

  const navigation = useNavigation<CameraProps>();
  const windowWidth = Dimensions.get('window').width;
  const itemWidth = windowWidth / 3;

  //권한 설정
  useEffect(() => {
    const getGalleryPermission = async () => {
      const {status} = await MediaLibrary.requestPermissionsAsync();

      //권한을 허용하지 않으면 핸드폰설정으로 이동됨
      if (status !== 'granted') {
        console.log('Media library permission denied');
        Linking.openSettings();
        return;
      }
      setIsPermission(true);
      fetchPhotos();
    };

    getGalleryPermission();
  }, []);

  const renderPhotoItem: React.FC<{item: MediaLibrary.Asset}> = ({item}) => {
    const selectedIndex = [...selectedPhotos].findIndex(
      ({id}) => item.id === id,
    ); // -1 || 3
    const isSelected = selectedIndex !== -1; // false || true

    return (
      <Pressable
        style={{width: itemWidth, aspectRatio: 1, position: 'relative'}}
        onPress={() => {
          if (isSelected) {
            const clone = [...selectedPhotos];
            clone.splice(selectedIndex, 1);
            setSelectedPhotos(clone);
          } else {
            if (selectedPhotos.length < 6) {
              const clone = [...selectedPhotos];
              clone.push(item);
              setSelectedPhotos(clone);
            } else {
              // openModal();
            }
          }
          ``;
        }}>
        <DotStyle isSelected={isSelected}>
          {isSelected && decideSelectedIndicator(selectedIndex)}
        </DotStyle>
        <Image
          source={{uri: item.uri}}
          style={{flex: 1, resizeMode: 'cover'}}
        />
      </Pressable>
    );
  };

  const decideSelectedIndicator = (idx: number) => {
    return (
      <Text style={{color: '#ffffff', backgroundColor: '#287FFF'}}>
        {idx + 1}
      </Text>
    );
  };

  const renderCameraButton = () => {
    const moveToCamera = () => {
      navigation.navigate('CameraScreen', {
        screen: 'CameraScreen',
        params: {type: 'camera'},
      });
    };

    return (
      <Pressable
        onPress={moveToCamera}
        style={{
          width: itemWidth,
          aspectRatio: 1,
          backgroundColor: '#ffffff',
          flexDirection: 'column',
          justifyContent: 'center',
          alignItems: 'center',
        }}>
        <CameraIcon />
        <Text>사진 촬영</Text>
      </Pressable>
    );
  };

  const handleEndReached = () => {
    if (hasNextPage) {
      fetchPhotos(endCursor);
    }
  };

  const fetchPhotos = async (cursor?: string | undefined) => {
    try {
      const {
        assets,
        endCursor: newEndCursor,
        hasNextPage,
      } = await MediaLibrary.getAssetsAsync({
        mediaType: MediaLibrary.MediaType.photo,
        first: 20,
        after: cursor,
      });

      setGalleryList(prevPhoto => [...prevPhoto, ...assets]);
      setEndCursor(newEndCursor);
      setHasNextPage(hasNextPage);
    } catch (error) {
      console.log('Error fetching photos:', error);
    }
  };

  const submitHandler = () => {
    setSelectedPhotos(selectedPhotos.slice(0, 5));
    navigation.goBack();
  };
  return (
    <>
      <View style={{backgroundColor: '#ffffff'}}>
        <CustomHeader isBackButton>
          <Text style={{fontSize: 20, marginLeft: -10}}>최근 항목</Text>
        </CustomHeader>
      </View>
      {isPermission && (
        <>
          <FlatList
            data={[null, ...galleryList]}
            keyExtractor={(item, index) => index.toString()}
            renderItem={({item, index}) =>
              item
                ? renderPhotoItem({item})
                : index === 0
                  ? renderCameraButton()
                  : null
            }
            numColumns={3}
            onEndReached={handleEndReached}
            onEndReachedThreshold={0.1}
          />
          <ShootingBtn onPress={submitHandler}>
            <ButtonText>완료</ButtonText>
          </ShootingBtn>
        </>
      )}
    </>
  );
};

참고블로그

0개의 댓글