123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- import React, { useEffect, useState } from 'react';
- import { View, FlatList, Image, Text, TouchableOpacity } from 'react-native';
- import { useNavigation } from '@react-navigation/native';
- import * as Progress from 'react-native-progress';
- import * as ImagePicker from 'expo-image-picker';
- import { Header, Input, Modal, PageWrapper, FlatList as List } from 'src/components';
- import { API_HOST } from 'src/constants';
- import { CustomButton } from '../Components';
- import RangeCalendar from 'src/components/Calendars/RangeCalendar';
- import { postSetUploadTemp, usePostRemoveTempMutation, usePostSaveTempMutation } from '@api/photos';
- import { StoreType, storage } from 'src/storage';
- import { AllRegions, ImageStatus, TempData } from '../utils/types';
- import { itemWidth } from '../utils';
- import { styles } from './styles';
- import { Colors } from 'src/theme';
- import AddImgSvg from '../../../../../assets/icons/travels-screens/add-img.svg';
- import ChooseSvg from '../../../../../assets/icons/travels-screens/choose.svg';
- import SaveSvg from '../../../../../assets/icons/travels-screens/save.svg';
- import XCircleSvg from '../../../../../assets/icons/travels-screens/x-circle.svg';
- import ChevronIcon from '../../../../../assets/icons/travels-screens/chevron-bottom.svg';
- import CalendarSvg from '../../../../../assets/icons/calendar.svg';
- const AddPhotoScreen = ({ route }: { route: any }) => {
- const navigation: any = useNavigation();
- const { data, images, allRegions, tempData } = route.params;
- const token = storage.get('token', StoreType.STRING) as string;
- const [isModalVisible, setIsModalVisible] = useState(false);
- const [selectedRegion, setSelectedRegion] = useState<AllRegions | null>(
- data && data.country
- ? ({
- id: data.id,
- country: data.country
- } as AllRegions)
- : null
- );
- const [description, setDescription] = useState('');
- const [selectedDate, setSelectedDate] = useState<string | null>(
- data && data.date ? data.date : new Date().toISOString().slice(0, 10)
- );
- const { mutate: removeTemp } = usePostRemoveTempMutation();
- const { mutate: saveTemp, data: saveResponse } = usePostSaveTempMutation();
- const [imagesStatus, setImagesStatus] = useState<ImageStatus[]>(
- images?.map((image: ImagePicker.ImagePickerAsset) => ({
- uri: image.uri,
- assetId: image.assetId,
- loaded: false,
- progress: 0,
- uploadResponse: null,
- dateTime: image.exif?.DateTimeOriginal?.split(' ')[0].replaceAll(':', '-') ?? null
- }))
- );
- const [calendarVisible, setCalendarVisible] = useState(false);
- useEffect(() => {
- if (tempData) {
- setImagesStatus(
- tempData.map((temp: TempData) => {
- return { assetId: temp.guid, loaded: true, uploadResponse: temp };
- })
- );
- return;
- } else {
- images.forEach((image: ImagePicker.ImagePickerAsset) => uploadTempImage(image));
- }
- const date = images[images.length - 1]?.exif?.DateTimeOriginal?.split(' ')[0].replaceAll(
- ':',
- '-'
- );
- date && setSelectedDate(date);
- }, [images]);
- const uploadTempImage = async (image: ImagePicker.ImagePickerAsset) => {
- const uriParts = image.uri.split('.');
- const fileType = uriParts[uriParts.length - 1];
- const imgData = {
- token,
- file: {
- uri: image.uri,
- name: image.uri.split('/').pop()!,
- type: `image/${fileType}`
- }
- };
- image.assetId && simulateUploadProgress(image.assetId);
- postSetUploadTemp(imgData)
- .then((response) => {
- if (response && response.result === 'OK') {
- setImagesStatus((currentStatus) =>
- currentStatus.map((item) => {
- if (item.assetId === image.assetId) {
- return {
- ...item,
- loaded: true,
- progress: 1,
- uploadResponse: { guid: response.guid, link: response.link }
- };
- }
- return item;
- })
- );
- }
- })
- .catch((error) => {
- console.error('Error', error);
- });
- };
- const simulateUploadProgress = (assetId: string) => {
- let progress = 0;
- const intervalId = setInterval(() => {
- progress += 0.05 + Math.random() * 0.25;
- if (progress >= 1) {
- clearInterval(intervalId);
- progress = 1;
- }
- setImagesStatus((currentStatus) =>
- currentStatus.map((item) => {
- if (item.assetId === assetId) {
- return { ...item, progress };
- }
- return item;
- })
- );
- }, 300);
- };
- const deleteTemp = (item: ImageStatus) => {
- removeTemp(
- { token, guid: item.uploadResponse?.guid as string },
- {
- onSuccess: (res) => {
- if (res.result === 'OK') {
- setImagesStatus(
- imagesStatus.filter(
- (img: ImageStatus) => img.uploadResponse?.guid !== item.uploadResponse?.guid
- )
- );
- }
- }
- }
- );
- };
- const handleSelectPhoto = async () => {
- await ImagePicker.launchImageLibraryAsync({
- mediaTypes: ImagePicker.MediaTypeOptions.Images,
- quality: 1,
- allowsMultipleSelection: true,
- exif: true
- }).then((result) => {
- if (!result.canceled) {
- addPhoto(result.assets);
- }
- });
- };
- const addPhoto = (newImages: ImagePicker.ImagePickerAsset[]) => {
- const date = newImages[newImages.length - 1]?.exif?.DateTimeOriginal?.split(' ')[0].replaceAll(
- ':',
- '-'
- );
- date && setSelectedDate(date);
- setImagesStatus([
- ...imagesStatus,
- ...newImages.map((image: any) => ({
- uri: image.uri,
- assetId: image.assetId,
- loaded: false,
- progress: 0,
- uploadResponse: null,
- dateTime: image.exif?.DateTimeOriginal?.split(' ')[0].replaceAll(':', '-') ?? null
- }))
- ]);
- newImages.forEach((image: ImagePicker.ImagePickerAsset) => uploadTempImage(image));
- };
- const saveTempData = () => {
- const tempData = {
- token,
- guids: imagesStatus.map((img) => img.uploadResponse?.guid!),
- date: selectedDate,
- region: selectedRegion?.id!,
- description
- };
- saveTemp(tempData, {
- onSuccess: () => {
- data && allRegions?.length ? navigation.pop(2) : navigation.goBack();
- }
- });
- };
- const renderItem = ({ item }: { item: ImageStatus }) => (
- <View style={styles.itemContainer}>
- <View style={{ position: 'relative' }}>
- {!item.loaded ? (
- <Progress.Bar
- progress={item.progress}
- width={itemWidth}
- color={Colors.DARK_BLUE}
- borderWidth={0}
- borderRadius={5}
- unfilledColor="rgba(0, 0, 0, 0.1)"
- />
- ) : (
- <Image
- source={{ uri: API_HOST + item.uploadResponse?.link }}
- style={[styles.image, { width: itemWidth, height: itemWidth }]}
- />
- )}
- {item.loaded && (
- <TouchableOpacity style={styles.deleteTemp} onPress={() => deleteTemp(item)}>
- <XCircleSvg />
- </TouchableOpacity>
- )}
- </View>
- </View>
- );
- const getHeaderLabel = () => {
- if (data?.country) return data.country;
- if (data?.date) return data.date;
- return 'Add photo';
- };
- return (
- <PageWrapper>
- <Header label={getHeaderLabel()} />
- <View style={{ alignItems: 'center', marginBottom: 8 }}>
- {data?.name && <Text style={styles.title}>{data.name}</Text>}
- <View style={styles.btnContainer}>
- <CustomButton
- title="Save"
- onPress={saveTempData}
- icon={<SaveSvg fill={Colors.DARK_BLUE} />}
- disabled={!selectedRegion || imagesStatus.length === 0}
- />
- <CustomButton
- title="Add photo"
- icon={<AddImgSvg fill={Colors.DARK_BLUE} />}
- onPress={handleSelectPhoto}
- />
- </View>
- </View>
- <View style={{ gap: 10, marginVertical: 8 }}>
- <Input
- placeholder="Photo description"
- inputMode={'text'}
- onChange={(text) => setDescription(text)}
- value={description}
- />
- {!data?.country && (
- <TouchableOpacity
- style={[styles.regionSelector, { justifyContent: 'space-between' }]}
- onPress={() => setIsModalVisible(true)}
- >
- <Text style={styles.regionText}>{selectedRegion?.country ?? 'Choose a region'}</Text>
- <ChevronIcon />
- </TouchableOpacity>
- )}
- {!data?.date && (
- <TouchableOpacity style={styles.regionSelector} onPress={() => setCalendarVisible(true)}>
- <CalendarSvg />
- <Text style={styles.regionText}>{selectedDate ?? 'Add date'}</Text>
- </TouchableOpacity>
- )}
- </View>
- <FlatList
- data={imagesStatus}
- renderItem={renderItem}
- keyExtractor={(item, index) => index.toString()}
- numColumns={2}
- columnWrapperStyle={styles.columnWrapper}
- showsVerticalScrollIndicator={false}
- />
- <Modal
- onRequestClose={() => setIsModalVisible(false)}
- headerTitle={'Select Region'}
- visible={isModalVisible}
- >
- <List
- itemObject={(object) => {
- setSelectedRegion(object);
- setIsModalVisible(false);
- }}
- initialData={allRegions}
- />
- </Modal>
- <RangeCalendar
- isModalVisible={calendarVisible}
- closeModal={(startDate?: string | null, endDate?: string | null) => {
- startDate && setSelectedDate(startDate.toString());
- setCalendarVisible(false);
- }}
- allowRangeSelection={false}
- disableFutureDates={true}
- />
- </PageWrapper>
- );
- };
- export default AddPhotoScreen;
|