|
@@ -0,0 +1,392 @@
|
|
|
|
+import React, { useCallback, useEffect, useState } from 'react';
|
|
|
|
+import { View, FlatList, Image, Text, TouchableOpacity } from 'react-native';
|
|
|
|
+import ImageView from 'react-native-image-viewing';
|
|
|
|
+import ReactModal from 'react-native-modal';
|
|
|
|
+import * as ImagePicker from 'expo-image-picker';
|
|
|
|
+import { useNavigation } from '@react-navigation/native';
|
|
|
|
+
|
|
|
|
+import { Header, Loading, PageWrapper, WarningModal } from 'src/components';
|
|
|
|
+import { CustomButton, PhotoEditModal } from '../Components';
|
|
|
|
+import {
|
|
|
|
+ useGetTempQuery,
|
|
|
|
+ usePostDeletePhotoMutation,
|
|
|
|
+ usePostUpdatePhotoMutation
|
|
|
|
+} from '@api/photos';
|
|
|
|
+
|
|
|
|
+import { API_HOST } from 'src/constants';
|
|
|
|
+import { Colors } from 'src/theme';
|
|
|
|
+import { NAVIGATION_PAGES } from 'src/types';
|
|
|
|
+import { StoreType, storage } from 'src/storage';
|
|
|
|
+import { AllRegions, PhotosData } from '../utils/types';
|
|
|
|
+import { itemWidth } from '../utils';
|
|
|
|
+import { styles } from './styles';
|
|
|
|
+
|
|
|
|
+import AddImgSvg from '../../../../../assets/icons/travels-screens/add-img.svg';
|
|
|
|
+import ChooseSvg from '../../../../../assets/icons/travels-screens/choose.svg';
|
|
|
|
+import DotsSvg from '../../../../../assets/icons/travels-screens/three-dots.svg';
|
|
|
|
+import TrashSvg from '../../../../../assets/icons/travels-screens/trash-solid.svg';
|
|
|
|
+import CircleCheckSvg from '../../../../../assets/icons/travels-screens/circle-check.svg';
|
|
|
|
+import PenSvg from '../../../../../assets/icons/travels-screens/pen-to-square.svg';
|
|
|
|
+
|
|
|
|
+const MorePhotosScreen = ({ route }: { route: any }) => {
|
|
|
|
+ const { data, allRegions } = route.params;
|
|
|
|
+ const token = storage.get('token', StoreType.STRING) as string;
|
|
|
|
+ const { data: tempData, refetch } = useGetTempQuery(token, true);
|
|
|
|
+ const { mutate: deletePhoto } = usePostDeletePhotoMutation();
|
|
|
|
+ const { mutate: updatePhoto } = usePostUpdatePhotoMutation();
|
|
|
|
+ const navigation = useNavigation();
|
|
|
|
+
|
|
|
|
+ const [selectionMode, setSelectionMode] = useState(false);
|
|
|
|
+ const [selectedPhotos, setSelectedPhotos] = useState<{ [key: string]: boolean }>({});
|
|
|
|
+ const [photoToEdit, setPhotoToEdit] = useState<PhotosData | null>(null);
|
|
|
|
+ const [isLoading, setIsLoading] = useState(false);
|
|
|
|
+ const [photos, setPhotos] = useState<PhotosData[]>(data.photos ?? []);
|
|
|
|
+ const [shouldOpenWarningModal, setShouldOpenWarningModal] = useState(false);
|
|
|
|
+ const [shouldOpenEditModal, setShouldOpenEditModal] = useState(false);
|
|
|
|
+ const [selectedDate, setSelectedDate] = useState<string | null>(null);
|
|
|
|
+ const [description, setDescription] = useState<string | null>(null);
|
|
|
|
+ const [selectedRegion, setSelectedRegion] = useState<AllRegions | null>(null);
|
|
|
|
+ const [fullImages, setFullImages] = useState<PhotosData[]>([]);
|
|
|
|
+ const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
|
|
|
+ const [modalState, setModalState] = useState({
|
|
|
|
+ isModalVisible: false,
|
|
|
|
+ isWarningVisible: false,
|
|
|
|
+ isEditModalVisible: false,
|
|
|
|
+ isViewerVisible: false
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const handleSelectPhoto = useCallback((id: number) => {
|
|
|
|
+ setSelectedPhotos((prevSelected) => {
|
|
|
|
+ const isSelected = !!prevSelected[id];
|
|
|
|
+ return {
|
|
|
|
+ ...prevSelected,
|
|
|
|
+ [id]: !isSelected
|
|
|
|
+ };
|
|
|
|
+ });
|
|
|
|
+ }, []);
|
|
|
|
+
|
|
|
|
+ const toggleSelectionMode = () => {
|
|
|
|
+ setSelectionMode(!selectionMode);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ useEffect(() => {
|
|
|
|
+ setFullImages(
|
|
|
|
+ photos.map((photo) => ({
|
|
|
|
+ ...photo,
|
|
|
|
+ uri: API_HOST + photo.url_mid
|
|
|
|
+ }))
|
|
|
|
+ );
|
|
|
|
+ }, [photos]);
|
|
|
|
+
|
|
|
|
+ const openImageViewer = (index: number) => {
|
|
|
|
+ setCurrentImageIndex(index);
|
|
|
|
+ openModal('isViewerVisible');
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const deleteSelectedPhotos = () => {
|
|
|
|
+ const photoIds = Object.entries(selectedPhotos)
|
|
|
|
+ .filter(([key, value]) => value)
|
|
|
|
+ .map(([key, value]) => +key);
|
|
|
|
+ photoIds.forEach((id) => {
|
|
|
|
+ deletePhoto({ token, photo_id: id });
|
|
|
|
+ });
|
|
|
|
+ setPhotos(photos.filter((photo) => !photoIds.includes(photo.id)));
|
|
|
|
+ setSelectedPhotos({});
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const cancelSelection = () => {
|
|
|
|
+ setSelectedPhotos({});
|
|
|
|
+ setSelectionMode(false);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const handleDeletePhoto = () => {
|
|
|
|
+ photoToEdit &&
|
|
|
|
+ deletePhoto(
|
|
|
|
+ { token, photo_id: photoToEdit.id },
|
|
|
|
+ {
|
|
|
|
+ onSuccess: (res) => {
|
|
|
|
+ if (res.result === 'OK') {
|
|
|
|
+ setPhotos(photos.filter((photo) => photo.id !== photoToEdit.id));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+ setPhotoToEdit(null);
|
|
|
|
+ closeModal('isModalVisible');
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const handleUpdatePhoto = () => {
|
|
|
|
+ photoToEdit &&
|
|
|
|
+ updatePhoto(
|
|
|
|
+ {
|
|
|
|
+ token,
|
|
|
|
+ photo_id: photoToEdit.id,
|
|
|
|
+ date: selectedDate,
|
|
|
|
+ region: selectedRegion?.id ?? null,
|
|
|
|
+ description: description ?? photoToEdit.title
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ onSuccess: (res) => {
|
|
|
|
+ if (res.result === 'OK') {
|
|
|
|
+ const filteredPhotos = photos.filter((photo) => {
|
|
|
|
+ if (selectedRegion && selectedRegion?.id !== data?.region) {
|
|
|
|
+ return photo.id !== photoToEdit.id;
|
|
|
|
+ } else if (selectedDate && selectedDate !== data?.date) {
|
|
|
|
+ return photo.id !== photoToEdit.id;
|
|
|
|
+ } else {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const updatedPhotos =
|
|
|
|
+ typeof description === 'string'
|
|
|
|
+ ? filteredPhotos.map((photo) => {
|
|
|
|
+ if (photo.id === photoToEdit.id) {
|
|
|
|
+ return { ...photo, title: description };
|
|
|
|
+ }
|
|
|
|
+ return photo;
|
|
|
|
+ })
|
|
|
|
+ : filteredPhotos;
|
|
|
|
+
|
|
|
|
+ setPhotos(updatedPhotos);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ closeModal('isEditModalVisible');
|
|
|
|
+ setSelectedDate(null);
|
|
|
|
+ setSelectedRegion(null);
|
|
|
|
+ setDescription(null);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const renderItem = ({ item, index }: { item: PhotosData; index: number }) => (
|
|
|
|
+ <View style={{ alignItems: 'center', marginVertical: 8 }}>
|
|
|
|
+ <TouchableOpacity
|
|
|
|
+ style={{ position: 'relative' }}
|
|
|
|
+ onPress={() => (selectionMode ? handleSelectPhoto(item.id) : openImageViewer(index))}
|
|
|
|
+ >
|
|
|
|
+ <Image
|
|
|
|
+ source={{ uri: API_HOST + item?.url_small }}
|
|
|
|
+ style={[styles.image, { width: itemWidth, height: itemWidth }]}
|
|
|
|
+ />
|
|
|
|
+ <TouchableOpacity
|
|
|
|
+ style={styles.btnDots}
|
|
|
|
+ onPress={() => {
|
|
|
|
+ setPhotoToEdit(item);
|
|
|
|
+ openModal('isModalVisible');
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ <DotsSvg />
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
+ {selectionMode && (
|
|
|
|
+ <View
|
|
|
|
+ style={[
|
|
|
|
+ styles.selectionIndicator,
|
|
|
|
+ { backgroundColor: selectedPhotos[item.id] ? Colors.WHITE : 'rgba(15, 63, 79, 0.3)' }
|
|
|
|
+ ]}
|
|
|
|
+ >
|
|
|
|
+ {selectedPhotos[item.id] && (
|
|
|
|
+ <CircleCheckSvg fill={Colors.ORANGE} height={22} width={22} />
|
|
|
|
+ )}
|
|
|
|
+ </View>
|
|
|
|
+ )}
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
+ {item.title && (
|
|
|
|
+ <Text style={[styles.description, { maxWidth: itemWidth }]}>{item.title}</Text>
|
|
|
|
+ )}
|
|
|
|
+ </View>
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ const handleImagePick = async () => {
|
|
|
|
+ setIsLoading(true);
|
|
|
|
+
|
|
|
|
+ await refetch().then(async (temp) => {
|
|
|
|
+ if (temp.data?.photos && temp.data?.photos.length > 0) {
|
|
|
|
+ setIsLoading(false);
|
|
|
|
+ navigation.navigate(
|
|
|
|
+ ...([
|
|
|
|
+ NAVIGATION_PAGES.ADD_PHOTO,
|
|
|
|
+ {
|
|
|
|
+ data: data,
|
|
|
|
+ images: [],
|
|
|
|
+ allRegions: allRegions,
|
|
|
|
+ tempData: temp.data.photos
|
|
|
|
+ }
|
|
|
|
+ ] as never)
|
|
|
|
+ );
|
|
|
|
+ } else {
|
|
|
|
+ setIsLoading(false);
|
|
|
|
+
|
|
|
|
+ let result = await ImagePicker.launchImageLibraryAsync({
|
|
|
|
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
|
|
+ quality: 1,
|
|
|
|
+ allowsMultipleSelection: true,
|
|
|
|
+ exif: true
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (!result.canceled) {
|
|
|
|
+ navigation.navigate(
|
|
|
|
+ ...([
|
|
|
|
+ NAVIGATION_PAGES.ADD_PHOTO,
|
|
|
|
+ {
|
|
|
|
+ data: data,
|
|
|
|
+ images: result.assets,
|
|
|
|
+ allRegions: allRegions
|
|
|
|
+ }
|
|
|
|
+ ] as never)
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const handleEditModalClose = () => {
|
|
|
|
+ closeModal('isEditModalVisible');
|
|
|
|
+ setSelectedDate(null);
|
|
|
|
+ setSelectedRegion(null);
|
|
|
|
+ setDescription(null);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const closeModal = (modalName: string) => {
|
|
|
|
+ setModalState((prevState) => ({ ...prevState, [modalName]: false }));
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const openModal = (modalName: string) => {
|
|
|
|
+ setModalState((prevState) => ({ ...prevState, [modalName]: true }));
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return (
|
|
|
|
+ <PageWrapper style={{ position: 'relative' }}>
|
|
|
|
+ <Header label={data.country ?? data.date} />
|
|
|
|
+ {isLoading && <Loading />}
|
|
|
|
+ <View style={{ alignItems: 'center', marginBottom: 8 }}>
|
|
|
|
+ {data.name && <Text style={styles.title}>{data.name}</Text>}
|
|
|
|
+ <View
|
|
|
|
+ style={[
|
|
|
|
+ styles.btnContainer,
|
|
|
|
+ {
|
|
|
|
+ width: selectionMode ? 'auto' : '85%'
|
|
|
|
+ }
|
|
|
|
+ ]}
|
|
|
|
+ >
|
|
|
|
+ {selectionMode ? (
|
|
|
|
+ <>
|
|
|
|
+ <View>
|
|
|
|
+ <CustomButton
|
|
|
|
+ onPress={() => openModal('isWarningVisible')}
|
|
|
|
+ icon={<TrashSvg fill={Colors.DARK_BLUE} />}
|
|
|
|
+ />
|
|
|
|
+ </View>
|
|
|
|
+ <CustomButton
|
|
|
|
+ title="Cancel"
|
|
|
|
+ onPress={cancelSelection}
|
|
|
|
+ icon={<CircleCheckSvg fill={Colors.DARK_BLUE} />}
|
|
|
|
+ />
|
|
|
|
+ </>
|
|
|
|
+ ) : (
|
|
|
|
+ <CustomButton
|
|
|
|
+ title="Choose"
|
|
|
|
+ icon={<ChooseSvg fill={Colors.DARK_BLUE} />}
|
|
|
|
+ onPress={toggleSelectionMode}
|
|
|
|
+ />
|
|
|
|
+ )}
|
|
|
|
+ <CustomButton
|
|
|
|
+ title="Add photo"
|
|
|
|
+ icon={<AddImgSvg fill={Colors.DARK_BLUE} />}
|
|
|
|
+ onPress={handleImagePick}
|
|
|
|
+ />
|
|
|
|
+ </View>
|
|
|
|
+ </View>
|
|
|
|
+ <FlatList
|
|
|
|
+ data={photos}
|
|
|
|
+ renderItem={renderItem}
|
|
|
|
+ keyExtractor={(item) => item.id.toString()}
|
|
|
|
+ numColumns={2}
|
|
|
|
+ columnWrapperStyle={styles.columnWrapper}
|
|
|
|
+ showsVerticalScrollIndicator={false}
|
|
|
|
+ />
|
|
|
|
+ <ReactModal
|
|
|
|
+ isVisible={modalState.isModalVisible}
|
|
|
|
+ onBackdropPress={() => closeModal('isModalVisible')}
|
|
|
|
+ style={styles.modal}
|
|
|
|
+ statusBarTranslucent={true}
|
|
|
|
+ presentationStyle="overFullScreen"
|
|
|
|
+ onModalHide={() => {
|
|
|
|
+ if (shouldOpenWarningModal) {
|
|
|
|
+ openModal('isWarningVisible');
|
|
|
|
+ setShouldOpenWarningModal(false);
|
|
|
|
+ } else if (shouldOpenEditModal) {
|
|
|
|
+ openModal('isEditModalVisible');
|
|
|
|
+ setShouldOpenEditModal(false);
|
|
|
|
+ }
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ <View style={styles.wrapper}>
|
|
|
|
+ <View style={{ paddingVertical: 36 }}>
|
|
|
|
+ <TouchableOpacity
|
|
|
|
+ style={styles.btnModalEdit}
|
|
|
|
+ onPress={() => {
|
|
|
|
+ closeModal('isModalVisible');
|
|
|
|
+ setShouldOpenEditModal(true);
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ <PenSvg fill={Colors.DARK_BLUE} />
|
|
|
|
+ <Text style={styles.btnModalEditText}>Edit photo</Text>
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
+
|
|
|
|
+ <TouchableOpacity
|
|
|
|
+ style={styles.btnModalEdit}
|
|
|
|
+ onPress={() => {
|
|
|
|
+ closeModal('isModalVisible');
|
|
|
|
+ setShouldOpenWarningModal(true);
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ <TrashSvg fill={Colors.DARK_BLUE} />
|
|
|
|
+ <Text style={styles.btnModalEditText}>Delete</Text>
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
+ </View>
|
|
|
|
+ </View>
|
|
|
|
+ </ReactModal>
|
|
|
|
+ <WarningModal
|
|
|
|
+ isVisible={modalState.isWarningVisible}
|
|
|
|
+ onClose={() => {
|
|
|
|
+ closeModal('isWarningVisible');
|
|
|
|
+ setPhotoToEdit(null);
|
|
|
|
+ }}
|
|
|
|
+ type="delete"
|
|
|
|
+ title={photoToEdit ? 'Delete photo?' : 'Delete photos?'}
|
|
|
|
+ message={
|
|
|
|
+ photoToEdit
|
|
|
|
+ ? 'Are you sure you want to delete this photo?'
|
|
|
|
+ : 'Are you sure you want to delete these photos?'
|
|
|
|
+ }
|
|
|
|
+ action={photoToEdit ? handleDeletePhoto : deleteSelectedPhotos}
|
|
|
|
+ />
|
|
|
|
+ <PhotoEditModal
|
|
|
|
+ isVisible={modalState.isEditModalVisible}
|
|
|
|
+ onClose={handleEditModalClose}
|
|
|
|
+ photo={photoToEdit as PhotosData}
|
|
|
|
+ allRegions={allRegions}
|
|
|
|
+ handleUpdate={handleUpdatePhoto}
|
|
|
|
+ description={description}
|
|
|
|
+ setDescription={setDescription}
|
|
|
|
+ selectedDate={selectedDate}
|
|
|
|
+ setSelectedDate={setSelectedDate}
|
|
|
|
+ selectedRegion={selectedRegion}
|
|
|
|
+ setSelectedRegion={setSelectedRegion}
|
|
|
|
+ />
|
|
|
|
+ <ImageView
|
|
|
|
+ images={fullImages}
|
|
|
|
+ keyExtractor={(imageSrc, index) => index.toString()}
|
|
|
|
+ imageIndex={currentImageIndex}
|
|
|
|
+ visible={modalState.isViewerVisible}
|
|
|
|
+ onRequestClose={() => closeModal('isViewerVisible')}
|
|
|
|
+ swipeToCloseEnabled={false}
|
|
|
|
+ backgroundColor="transparent"
|
|
|
|
+ />
|
|
|
|
+ </PageWrapper>
|
|
|
|
+ );
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+export default MorePhotosScreen;
|