index.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import React, { useCallback, useEffect, useMemo, useState } from 'react';
  2. import { View, Image, Text, TouchableOpacity } from 'react-native';
  3. import { useNavigation } from '@react-navigation/native';
  4. import * as Progress from 'react-native-progress';
  5. import { getImageUri, photoStyles, smallPhotoHeight, smallPhotoWidth } from '../../utils';
  6. import { NAVIGATION_PAGES } from 'src/types';
  7. import { styles } from '../../Components/styles';
  8. import { API_HOST } from 'src/constants';
  9. import ImageView from 'better-react-native-image-viewing';
  10. import { EventPhotos } from '@api/events';
  11. import { ImageSource } from 'expo-image';
  12. import { Colors } from 'src/theme';
  13. type Photo = {
  14. id: number;
  15. filetype: string;
  16. data: 0 | 1;
  17. preview: 0 | 1;
  18. uid: number;
  19. name: string;
  20. avatar: string | null;
  21. uri: string;
  22. isSending?: boolean;
  23. };
  24. export const PhotoItem = React.memo(
  25. ({ photos, eventId, photosLeft }: { photos: any; eventId: number; photosLeft: number }) => {
  26. const navigation = useNavigation();
  27. const [currentImageIndex, setCurrentImageIndex] = useState(0);
  28. const [isViewerVisible, setIsViewerVisible] = useState(false);
  29. const [fullImages, setFullImages] = useState<EventPhotos[]>([]);
  30. const openImageViewer = (index: number) => {
  31. setCurrentImageIndex(index);
  32. setIsViewerVisible(true);
  33. };
  34. useEffect(() => {
  35. setFullImages(
  36. photos
  37. .filter((p: EventPhotos) => p.data)
  38. .map((photo: Photo) => ({
  39. ...photo,
  40. uri: API_HOST + '/webapi/events/get-photo/' + eventId + '/' + photo.id
  41. }))
  42. );
  43. }, [photos]);
  44. const renderSpinner = (style: any) => (
  45. <View
  46. style={[
  47. style,
  48. {
  49. alignItems: 'center',
  50. justifyContent: 'center',
  51. backgroundColor: Colors.FILL_LIGHT
  52. }
  53. ]}
  54. >
  55. <Progress.CircleSnail
  56. borderWidth={0}
  57. color={Colors.DARK_BLUE}
  58. unfilledColor="rgba(0, 0, 0, 0.1)"
  59. />
  60. </View>
  61. );
  62. const renderPhotos = () => {
  63. const photosToShow = photos.filter((p: EventPhotos) => p.preview).slice(0, 3);
  64. const morePhotosCount = photos.length - 3 + photosLeft;
  65. if (photosToShow.length === 1) {
  66. return (
  67. <TouchableOpacity onPress={() => openImageViewer(0)}>
  68. {photosToShow[0].isSending ? (
  69. renderSpinner(photoStyles.onePhoto)
  70. ) : (
  71. <Image
  72. source={{
  73. uri: getImageUri(
  74. '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow[0].id
  75. )
  76. }}
  77. style={photoStyles.onePhoto}
  78. />
  79. )}
  80. </TouchableOpacity>
  81. );
  82. } else if (photosToShow.length === 2) {
  83. return photosToShow.map((photo: Photo, index: number) => (
  84. <TouchableOpacity key={photo.id} onPress={() => openImageViewer(index)}>
  85. {photo?.isSending ? (
  86. renderSpinner({ ...photoStyles.twoPhotos, marginRight: index === 0 ? 8 : 0 })
  87. ) : (
  88. <Image
  89. source={{
  90. uri: getImageUri(
  91. '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow.id
  92. )
  93. }}
  94. style={[photoStyles.twoPhotos, index === 0 && { marginRight: 8 }]}
  95. />
  96. )}
  97. </TouchableOpacity>
  98. ));
  99. } else {
  100. return (
  101. <View style={{ flexDirection: 'row' }}>
  102. <TouchableOpacity onPress={() => openImageViewer(0)}>
  103. {photosToShow[0].isSending ? (
  104. renderSpinner(photoStyles.bigPhoto)
  105. ) : (
  106. <Image
  107. source={{
  108. uri: getImageUri(
  109. '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow[0].id
  110. )
  111. }}
  112. style={photoStyles.bigPhoto}
  113. />
  114. )}
  115. </TouchableOpacity>
  116. <View style={styles.smallPhotoContainer}>
  117. <TouchableOpacity onPress={() => openImageViewer(1)}>
  118. {photosToShow[1].isSending ? (
  119. renderSpinner(photoStyles.smallPhoto)
  120. ) : (
  121. <Image
  122. source={{
  123. uri: getImageUri(
  124. '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow[1].id
  125. )
  126. }}
  127. style={photoStyles.smallPhoto}
  128. />
  129. )}
  130. </TouchableOpacity>
  131. <TouchableOpacity onPress={() => openImageViewer(2)}>
  132. <Image
  133. source={{
  134. uri: getImageUri(
  135. '/webapi/events/get-photo-preview/' + eventId + '/' + photosToShow[2].id
  136. )
  137. }}
  138. style={[photoStyles.smallPhoto, { position: 'relative' }]}
  139. />
  140. </TouchableOpacity>
  141. {morePhotosCount > 0 && (
  142. <TouchableOpacity
  143. onPress={() => handlePress()}
  144. style={[
  145. styles.morePhotosOverlay,
  146. { width: smallPhotoWidth, height: smallPhotoHeight }
  147. ]}
  148. >
  149. <Text style={styles.morePhotosText}>+{morePhotosCount}</Text>
  150. </TouchableOpacity>
  151. )}
  152. </View>
  153. </View>
  154. );
  155. }
  156. };
  157. const photoViews = useMemo(() => renderPhotos(), [photos]);
  158. const handlePress = useCallback(() => {
  159. navigation.navigate(
  160. ...([
  161. NAVIGATION_PAGES.ALL_EVENT_PHOTOS,
  162. {
  163. data: photos,
  164. eventId,
  165. lastPhotoId: photos[photos.length - 1].id,
  166. photosCountLeft: photosLeft
  167. }
  168. ] as never)
  169. );
  170. }, [navigation, photos]);
  171. return (
  172. <View>
  173. {photoViews}
  174. <ImageView
  175. images={fullImages as ImageSource[]}
  176. keyExtractor={(imageSrc, index) => index.toString()}
  177. imageIndex={currentImageIndex}
  178. visible={isViewerVisible}
  179. onRequestClose={() => setIsViewerVisible(false)}
  180. swipeToCloseEnabled={false}
  181. backgroundColor={Colors.DARK_BLUE}
  182. FooterComponent={({ imageIndex }) => (
  183. <View style={styles.imageFooter}>
  184. <TouchableOpacity
  185. onPress={() => {
  186. setIsViewerVisible(false);
  187. navigation.navigate(
  188. ...([
  189. NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW,
  190. { userId: fullImages[imageIndex].uid }
  191. ] as never)
  192. );
  193. }}
  194. disabled={!fullImages[imageIndex].uid}
  195. style={styles.imageOwner}
  196. >
  197. {fullImages[imageIndex].avatar ? (
  198. <Image
  199. source={{ uri: API_HOST + fullImages[imageIndex].avatar }}
  200. style={{
  201. width: 28,
  202. height: 28,
  203. borderRadius: 14
  204. }}
  205. />
  206. ) : null}
  207. <Text style={styles.imageOwnerText}>{fullImages[imageIndex].name}</Text>
  208. </TouchableOpacity>
  209. </View>
  210. )}
  211. />
  212. </View>
  213. );
  214. }
  215. );