Browse Source

image viewer

Viktoriia 1 month ago
parent
commit
d9a3e084d1

+ 1 - 2
package.json

@@ -40,7 +40,6 @@
     "@tanstack/react-query": "^5.89.0",
     "@turf/turf": "^7.2.0",
     "axios": "^1.12.2",
-    "better-react-native-image-viewing": "^0.2.7",
     "dayjs": "^1.11.18",
     "dotenv": "^16.6.1",
     "expo": "^54.0.12",
@@ -81,7 +80,7 @@
     "react-native-gifted-chat": "^2.8.1",
     "react-native-google-places-autocomplete": "^2.5.7",
     "react-native-haptic-feedback": "^2.3.3",
-    "react-native-image-viewing": "^0.2.2",
+    "react-native-image-zoom-viewer": "^3.0.1",
     "react-native-keyboard-aware-scroll-view": "^0.9.5",
     "react-native-keyboard-controller": "1.18.5",
     "react-native-linear-gradient": "^2.8.3",

+ 121 - 0
src/components/CustomImageViewer/index.tsx

@@ -0,0 +1,121 @@
+import React from 'react';
+import { Modal, StyleSheet, View, TouchableOpacity, ActivityIndicator } from 'react-native';
+import ImageViewer from 'react-native-image-zoom-viewer';
+import { Colors } from 'src/theme';
+import CloseIcon from 'assets/icons/close.svg';
+
+interface Photo {
+  uri: string;
+  [key: string]: any;
+}
+
+interface CustomImageViewerProps {
+  images: Photo[];
+  imageIndex: number;
+  visible: boolean;
+  onRequestClose: () => void;
+  backgroundColor?: string;
+  onImageIndexChange?: (index: number) => void;
+  renderFooter?: (currentIndex: number) => React.ReactElement;
+  loadingColor?: string;
+}
+
+export const CustomImageViewer: React.FC<CustomImageViewerProps> = ({
+  images,
+  imageIndex,
+  visible,
+  onRequestClose,
+  backgroundColor = Colors.DARK_BLUE,
+  onImageIndexChange,
+  renderFooter,
+  loadingColor = Colors.WHITE
+}) => {
+  const [currentIndex, setCurrentIndex] = React.useState(imageIndex);
+
+  React.useEffect(() => {
+    setCurrentIndex(imageIndex);
+  }, [imageIndex]);
+
+  const handleIndexChange = (index?: number) => {
+    if (index !== undefined) {
+      setCurrentIndex(index);
+      onImageIndexChange?.(index);
+    }
+  };
+
+  const formattedImages = images.map((photo) => ({
+    url: photo.uri,
+    props: photo
+  }));
+
+  const footerRenderer = renderFooter
+    ? (currentIdx: number) => renderFooter(currentIdx)
+    : undefined;
+
+  const renderCloseButton = () => (
+    <TouchableOpacity
+      style={styles.closeButton}
+      onPress={onRequestClose}
+      hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
+    >
+      <View style={styles.closeIconContainer}>
+        <CloseIcon fill={Colors.WHITE} height={16} width={16} />
+      </View>
+    </TouchableOpacity>
+  );
+
+  const renderLoadingIndicator = () => (
+    <View style={styles.loadingContainer}>
+      <ActivityIndicator size="large" color={loadingColor} />
+    </View>
+  );
+
+  return (
+    <Modal visible={visible} transparent={true} onRequestClose={onRequestClose}>
+      <ImageViewer
+        imageUrls={formattedImages}
+        index={currentIndex}
+        onSwipeDown={onRequestClose}
+        enableSwipeDown
+        useNativeDriver
+        backgroundColor={backgroundColor}
+        onChange={handleIndexChange}
+        renderFooter={footerRenderer}
+        footerContainerStyle={styles.footerContainer}
+        renderIndicator={() => <View />}
+        onLongPress={() => null}
+        saveToLocalByLongPress={false}
+        enablePreload
+        loadingRender={renderLoadingIndicator}
+        renderHeader={renderCloseButton}
+      />
+    </Modal>
+  );
+};
+
+const styles = StyleSheet.create({
+  footerContainer: {
+    alignItems: 'center',
+    width: '100%'
+  },
+  closeButton: {
+    position: 'absolute',
+    top: 50,
+    right: 6,
+    zIndex: 1000,
+    padding: 8
+  },
+  closeIconContainer: {
+    width: 46,
+    height: 46,
+    borderRadius: 23,
+    justifyContent: 'center',
+    alignItems: 'center',
+    backgroundColor: 'rgba(15, 63, 79, 0.7)'
+  },
+  loadingContainer: {
+    flex: 1,
+    justifyContent: 'center',
+    alignItems: 'center'
+  }
+});

+ 1 - 0
src/components/index.ts

@@ -23,3 +23,4 @@ export * from './MapButton';
 export * from './ScaleBar';
 export * from './MultiSelectorCountries';
 export * from './TabViewWrapper';
+export * from './CustomImageViewer';

+ 11 - 6
src/screens/InAppScreens/MapScreen/CountryViewScreen/index.tsx

@@ -1,8 +1,13 @@
 import React, { FC, useCallback, useEffect, useState } from 'react';
 import { View, Text, Image, TouchableOpacity, Platform, FlatList } from 'react-native';
-import ImageView from 'better-react-native-image-viewing';
 import { styles } from '../RegionViewScreen/styles';
-import { Button, HorizontalTabView, Loading, Modal as ReactModal } from 'src/components';
+import {
+  Button,
+  CustomImageViewer,
+  HorizontalTabView,
+  Loading,
+  Modal as ReactModal
+} from 'src/components';
 import { CommonActions, useFocusEffect } from '@react-navigation/native';
 import { Colors } from 'src/theme';
 import { ScrollView } from 'react-native-gesture-handler';
@@ -96,7 +101,7 @@ const CountryViewScreen: FC<Props> = ({ navigation, route }) => {
         routesData && staticGroups.push(...routesData);
 
         setPhotos(
-          data?.data?.photos?.map((item) => ({
+          data?.data?.photos?.slice(0, 100).map((item) => ({
             ...item,
             uriSmall: `${API_HOST}/ajax/pic/${item.id}/small`,
             uri: `${API_HOST}/ajax/pic/${item.id}/full`
@@ -663,14 +668,14 @@ const CountryViewScreen: FC<Props> = ({ navigation, route }) => {
           )} */}
         </View>
 
-        <ImageView
+        <CustomImageViewer
           images={photos}
           imageIndex={currentImageIndex}
           visible={isModalVisible}
           onRequestClose={() => setModalVisible(false)}
-          backgroundColor={Colors.DARK_BLUE}
           onImageIndexChange={setActiveIndex}
-          FooterComponent={({ imageIndex }) => (
+          backgroundColor={Colors.DARK_BLUE}
+          renderFooter={(imageIndex: number) => (
             <View style={styles.imageFooter}>
               <Text style={styles.imageDescription}>{photos[imageIndex].title}</Text>
               <TouchableOpacity

+ 5 - 5
src/screens/InAppScreens/MapScreen/RegionViewScreen/index.tsx

@@ -1,9 +1,9 @@
 import React, { FC, useCallback, useEffect, useState } from 'react';
 import { View, Text, Image, TouchableOpacity, Platform } from 'react-native';
-import ImageView from 'better-react-native-image-viewing';
 import { styles } from './styles';
 import {
   Button,
+  CustomImageViewer,
   EditNmModal,
   HorizontalTabView,
   Loading,
@@ -115,7 +115,7 @@ const RegionViewScreen: FC<Props> = ({ navigation, route }) => {
         routesData && staticGroups.push(...routesData);
 
         setPhotos(
-          data?.data?.photos?.map((item) => ({
+          data?.data?.photos?.slice(0, 100).map((item) => ({
             ...item,
             uriSmall: `${API_HOST}/ajax/pic/${item.id}/small`,
             uri: `${API_HOST}/ajax/pic/${item.id}/full`
@@ -602,14 +602,14 @@ const RegionViewScreen: FC<Props> = ({ navigation, route }) => {
           ) : null}
         </View>
 
-        <ImageView
+        <CustomImageViewer
           images={photos}
           imageIndex={currentImageIndex}
           visible={isModalVisible}
           onRequestClose={() => setModalVisible(false)}
-          backgroundColor={Colors.DARK_BLUE}
           onImageIndexChange={setActiveIndex}
-          FooterComponent={({ imageIndex }) => (
+          backgroundColor={Colors.DARK_BLUE}
+          renderFooter={(imageIndex: number) => (
             <View style={styles.imageFooter}>
               <Text style={styles.imageDescription}>{photos[imageIndex].title}</Text>
               <TouchableOpacity

+ 3 - 4
src/screens/InAppScreens/MessagesScreen/ChatScreen/index.tsx

@@ -30,7 +30,7 @@ import {
 } from 'react-native-gifted-chat';
 import { MaterialCommunityIcons } from '@expo/vector-icons';
 import { GestureHandlerRootView, Swipeable } from 'react-native-gesture-handler';
-import { AvatarWithInitials, Header, WarningModal } from 'src/components';
+import { AvatarWithInitials, CustomImageViewer, Header, WarningModal } from 'src/components';
 import { Colors } from 'src/theme';
 import { useFocusEffect, useNavigation } from '@react-navigation/native';
 import { setAudioModeAsync } from 'expo-audio';
@@ -73,7 +73,6 @@ import { dismissChatNotifications, isMessageEdited } from '../utils';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import FileViewer from 'react-native-file-viewer';
 import * as FileSystem from 'expo-file-system/legacy';
-import ImageView from 'better-react-native-image-viewing';
 import * as MediaLibrary from 'expo-media-library';
 
 import BanIcon from 'assets/icons/messages/ban.svg';
@@ -212,7 +211,7 @@ const ChatScreen = ({ route }: { route: any }) => {
   }, []);
 
   const [isKeyboardVisible, setKeyboardVisible] = useState(false);
-  
+
   useEffect(() => {
     const keyboardWillShow = Keyboard.addListener(
       Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
@@ -2125,7 +2124,7 @@ const ChatScreen = ({ route }: { route: any }) => {
           <ActivityIndicator size="large" color={Colors.DARK_BLUE} />
         )}
 
-        <ImageView
+        <CustomImageViewer
           images={[
             {
               uri: selectedMedia,

+ 2 - 4
src/screens/InAppScreens/MessagesScreen/GroupChatScreen/index.tsx

@@ -34,10 +34,9 @@ import {
 } from 'react-native-gifted-chat';
 import { MaterialCommunityIcons } from '@expo/vector-icons';
 import { GestureHandlerRootView, Swipeable } from 'react-native-gesture-handler';
-import { Header, WarningModal } from 'src/components';
+import { CustomImageViewer, Header, WarningModal } from 'src/components';
 import { Colors } from 'src/theme';
 import { useFocusEffect, useNavigation } from '@react-navigation/native';
-// import { Audio } from 'expo-av';
 import { setAudioModeAsync } from 'expo-audio';
 import ChatMessageBox from '../Components/ChatMessageBox';
 import ReplyMessageBar from '../Components/ReplyMessageBar';
@@ -82,7 +81,6 @@ import { dismissChatNotifications, isMessageEdited } from '../utils';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import FileViewer from 'react-native-file-viewer';
 import * as FileSystem from 'expo-file-system/legacy';
-import ImageView from 'better-react-native-image-viewing';
 import * as MediaLibrary from 'expo-media-library';
 
 import BanIcon from 'assets/icons/messages/ban.svg';
@@ -2443,7 +2441,7 @@ const GroupChatScreen = ({ route }: { route: any }) => {
           <ActivityIndicator size="large" color={Colors.DARK_BLUE} />
         )}
 
-        <ImageView
+        <CustomImageViewer
           images={[
             {
               uri: selectedMedia,

+ 9 - 6
src/screens/InAppScreens/MessagesScreen/GroupSettingsScreen/index.tsx

@@ -15,11 +15,17 @@ import {
   useFocusEffect,
   useNavigation
 } from '@react-navigation/native';
-import ImageView from 'react-native-image-viewing';
 import RenderHtml, { defaultHTMLElementModels } from 'react-native-render-html';
 
 import { storage, StoreType } from 'src/storage';
-import { PageWrapper, Header, Loading, Input, WarningModal } from 'src/components';
+import {
+  PageWrapper,
+  Header,
+  Loading,
+  Input,
+  WarningModal,
+  CustomImageViewer
+} from 'src/components';
 import {
   usePostGetGroupMembersQuery,
   usePostGetGroupSettingsQuery,
@@ -418,15 +424,12 @@ const GroupSettingScreen: FC<Props> = ({ navigation, route }) => {
         onClose={() => setModalState({ ...modalState, isWarningVisible: false })}
         title={modalState.title}
       />
-      <ImageView
+      <CustomImageViewer
         images={[{ uri: `${API_HOST}${data.settings.avatar_full}?cacheBust=${cacheKey}` }]}
-        keyExtractor={(imageSrc, index) => index.toString()}
         imageIndex={0}
         visible={fullSizeImageVisible}
         onRequestClose={() => setFullSizeImageVisible(false)}
-        swipeToCloseEnabled={false}
         backgroundColor={Colors.DARK_BLUE}
-        doubleTapToZoomEnabled={true}
       />
       <EditGroupModal />
       <AddNomadsModal />

+ 3 - 6
src/screens/InAppScreens/ProfileScreen/index.tsx

@@ -2,7 +2,6 @@ import React, { FC, useCallback, useEffect, useState } from 'react';
 import { Linking, ScrollView, Text, TouchableOpacity, View, Image, Platform } from 'react-native';
 import { CommonActions, NavigationProp, useFocusEffect } from '@react-navigation/native';
 import ReactModal from 'react-native-modal';
-import ImageView from 'react-native-image-viewing';
 
 import { usePostGetProfileInfoDataQuery, usePostGetProfileUpdatesQuery } from '@api/user';
 import {
@@ -10,7 +9,8 @@ import {
   Loading,
   AvatarWithInitials,
   Header,
-  WarningModal
+  WarningModal,
+  CustomImageViewer
 } from '../../../components';
 import { adaptiveStyle, Colors } from '../../../theme';
 import { styles } from './styles';
@@ -545,17 +545,14 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
         onClose={() => closeModal('isWarningVisible')}
         title=""
       />
-      <ImageView
+      <CustomImageViewer
         images={[
           { uri: API_HOST + '/img/avatars/' + data.user_data.avatar + '?v=' + avatarVersion }
         ]}
-        keyExtractor={(imageSrc, index) => index.toString()}
         imageIndex={0}
         visible={fullSizeImageVisible}
         onRequestClose={() => setFullSizeImageVisible(false)}
-        swipeToCloseEnabled={false}
         backgroundColor={Colors.DARK_BLUE}
-        doubleTapToZoomEnabled={true}
       />
     </PageWrapper>
   );

+ 3 - 6
src/screens/InAppScreens/TravelsScreen/AllEventPhotosScreen/index.tsx

@@ -1,9 +1,8 @@
 import React, { useEffect, useState } from 'react';
 import { View, FlatList, Image, Text, TouchableOpacity } from 'react-native';
-import ImageView from 'better-react-native-image-viewing';
 import { useNavigation } from '@react-navigation/native';
 
-import { Header, PageWrapper } from 'src/components';
+import { CustomImageViewer, Header, PageWrapper } from 'src/components';
 
 import { API_HOST } from 'src/constants';
 import { Colors } from 'src/theme';
@@ -92,15 +91,13 @@ const AllEventPhotosScreen = ({ route }: { route: any }) => {
         showsVerticalScrollIndicator={false}
       />
 
-      <ImageView
+      <CustomImageViewer
         images={fullImages}
-        keyExtractor={(imageSrc, index) => index.toString()}
         imageIndex={currentImageIndex}
         visible={modalState.isViewerVisible}
         onRequestClose={() => closeModal('isViewerVisible')}
-        swipeToCloseEnabled={false}
         backgroundColor={Colors.DARK_BLUE}
-        FooterComponent={({ imageIndex }) => (
+        renderFooter={(imageIndex: number) => (
           <View style={styles.imageFooter}>
             <TouchableOpacity
               onPress={() => {

+ 5 - 6
src/screens/InAppScreens/TravelsScreen/Components/PhotoItem.tsx

@@ -9,7 +9,8 @@ import { styles } from './styles';
 
 import ChevronRightIcon from '../../../../../assets/icons/travels-screens/chevron-right.svg';
 import { API_HOST } from 'src/constants';
-import ImageView from 'react-native-image-viewing';
+import { Colors } from 'src/theme';
+import { CustomImageViewer } from 'src/components';
 
 export const PhotoItem = React.memo(
   ({ item, allRegions }: { item: any; allRegions: AllRegions[] }) => {
@@ -130,14 +131,12 @@ export const PhotoItem = React.memo(
           </TouchableOpacity>
         </View>
         <View style={styles.photoContainer}>{photoViews}</View>
-        <ImageView
-          images={fullImages}
-          keyExtractor={(imageSrc, index) => index.toString()}
+        <CustomImageViewer
+          images={fullImages as any}
           imageIndex={currentImageIndex}
           visible={isViewerVisible}
           onRequestClose={() => setIsViewerVisible(false)}
-          swipeToCloseEnabled={false}
-          backgroundColor="transparent"
+          backgroundColor={Colors.DARK_BLUE}
         />
       </View>
     );

+ 10 - 5
src/screens/InAppScreens/TravelsScreen/CreateEvent/index.tsx

@@ -15,12 +15,19 @@ import * as yup from 'yup';
 import { useNavigation } from '@react-navigation/native';
 import { styles } from './styles';
 import { ButtonVariants } from 'src/types/components';
-import ImageView from 'better-react-native-image-viewing';
 
 import { StoreType, storage } from 'src/storage';
 
 import AddImgSvg from 'assets/icons/travels-screens/add-img.svg';
-import { Button, Header, Input, Loading, PageWrapper, WarningModal } from 'src/components';
+import {
+  Button,
+  CustomImageViewer,
+  Header,
+  Input,
+  Loading,
+  PageWrapper,
+  WarningModal
+} from 'src/components';
 import { Colors } from 'src/theme';
 
 import EarthIcon from 'assets/icons/travels-section/earth.svg';
@@ -557,17 +564,15 @@ const CreateEventScreen = ({ route }: { route: any }) => {
                     allowRangeSelection={false}
                     selectedDate={props.values.date}
                   />
-                  <ImageView
+                  <CustomImageViewer
                     images={[
                       {
                         uri: `${API_HOST}/webapi/photos/${props.values.photo}/full`
                       }
                     ]}
-                    keyExtractor={(imageSrc, index) => index.toString()}
                     imageIndex={0}
                     visible={isViewerVisible}
                     onRequestClose={() => setIsViewerVisible(false)}
-                    swipeToCloseEnabled={false}
                     backgroundColor={Colors.DARK_BLUE}
                   />
                 </View>

+ 10 - 5
src/screens/InAppScreens/TravelsScreen/CreateSharedTrip/index.tsx

@@ -15,12 +15,19 @@ import * as yup from 'yup';
 import { useNavigation } from '@react-navigation/native';
 import { styles } from '../CreateEvent/styles';
 import { ButtonVariants } from 'src/types/components';
-import ImageView from 'better-react-native-image-viewing';
 
 import { StoreType, storage } from 'src/storage';
 
 import AddImgSvg from 'assets/icons/travels-screens/add-img.svg';
-import { Button, CheckBox, Header, Input, PageWrapper, WarningModal } from 'src/components';
+import {
+  Button,
+  CheckBox,
+  CustomImageViewer,
+  Header,
+  Input,
+  PageWrapper,
+  WarningModal
+} from 'src/components';
 import { Colors } from 'src/theme';
 
 import { getFontSize } from 'src/utils';
@@ -676,17 +683,15 @@ const CreateSharedTripScreen = ({ route }: { route: any }) => {
                     selectedDate={props.values[calendarVisible ?? 'start_date']}
                     disablePastDates={true}
                   />
-                  <ImageView
+                  <CustomImageViewer
                     images={[
                       {
                         uri: `${API_HOST}/webapi/photos/${props.values.photo}/full`
                       }
                     ]}
-                    keyExtractor={(imageSrc, index) => index.toString()}
                     imageIndex={0}
                     visible={isViewerVisible}
                     onRequestClose={() => setIsViewerVisible(false)}
-                    swipeToCloseEnabled={false}
                     backgroundColor={Colors.DARK_BLUE}
                   />
                 </View>

+ 4 - 6
src/screens/InAppScreens/TravelsScreen/EventScreen/PhotoItem/index.tsx

@@ -8,10 +8,10 @@ import { NAVIGATION_PAGES } from 'src/types';
 import { styles } from '../../Components/styles';
 
 import { API_HOST } from 'src/constants';
-import ImageView from 'better-react-native-image-viewing';
 import { EventPhotos } from '@api/events';
 import { ImageSource } from 'expo-image';
 import { Colors } from 'src/theme';
+import { CustomImageViewer } from 'src/components';
 
 type Photo = {
   id: number;
@@ -184,15 +184,13 @@ export const PhotoItem = React.memo(
     return (
       <View>
         {photoViews}
-        <ImageView
-          images={fullImages as ImageSource[]}
-          keyExtractor={(imageSrc, index) => index.toString()}
+        <CustomImageViewer
+          images={fullImages as any}
           imageIndex={currentImageIndex}
           visible={isViewerVisible}
           onRequestClose={() => setIsViewerVisible(false)}
-          swipeToCloseEnabled={false}
           backgroundColor={Colors.DARK_BLUE}
-          FooterComponent={({ imageIndex }) => (
+          renderFooter={(imageIndex) => (
             <View style={styles.imageFooter}>
               <TouchableOpacity
                 onPress={() => {

+ 8 - 3
src/screens/InAppScreens/TravelsScreen/EventScreen/index.tsx

@@ -25,7 +25,6 @@ import { API_HOST, APP_VERSION } from 'src/constants';
 import { StoreType, storage } from 'src/storage';
 import { MaterialCommunityIcons } from '@expo/vector-icons';
 import * as Progress from 'react-native-progress';
-import ImageView from 'better-react-native-image-viewing';
 
 import ChevronLeft from 'assets/icons/chevron-left.svg';
 import MapSvg from 'assets/icons/travels-screens/map-location.svg';
@@ -56,7 +55,13 @@ import {
   usePostUploadPhotoMutation,
   usePostUploadTempFileMutation
 } from '@api/events';
-import { AvatarWithInitials, Input, Loading, WarningModal } from 'src/components';
+import {
+  AvatarWithInitials,
+  CustomImageViewer,
+  Input,
+  Loading,
+  WarningModal
+} from 'src/components';
 import moment from 'moment';
 import { renderSpotsText } from '../EventsScreen/utils';
 
@@ -2082,7 +2087,7 @@ const EventScreen = ({ route }: { route: any }) => {
         onClose={() => setModalInfo({ ...modalInfo, visible: false })}
         title={modalInfo.title}
       />
-      <ImageView
+      <CustomImageViewer
         images={photosForRegion}
         imageIndex={currentImageIndex}
         visible={isImageModalVisible}

+ 4 - 7
src/screens/InAppScreens/TravelsScreen/MorePhotosScreen/index.tsx

@@ -1,11 +1,10 @@
 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 { CustomImageViewer, Header, Loading, PageWrapper, WarningModal } from 'src/components';
 import { CustomButton, PhotoEditModal } from '../Components';
 import {
   useGetTempQuery,
@@ -378,14 +377,12 @@ const MorePhotosScreen = ({ route }: { route: any }) => {
         selectedRegion={selectedRegion}
         setSelectedRegion={setSelectedRegion}
       />
-      <ImageView
-        images={fullImages}
-        keyExtractor={(imageSrc, index) => index.toString()}
+      <CustomImageViewer
+        images={fullImages as any}
         imageIndex={currentImageIndex}
         visible={modalState.isViewerVisible}
         onRequestClose={() => closeModal('isViewerVisible')}
-        swipeToCloseEnabled={false}
-        backgroundColor="transparent"
+        backgroundColor={Colors.DARK_BLUE}
       />
     </PageWrapper>
   );