index.tsx 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import { useEffect, useRef } from 'react';
  2. import { View, Image, Text, TouchableOpacity, Platform, Share } from 'react-native';
  3. import { styles } from './styles';
  4. import { Colors } from 'src/theme';
  5. import CheckSvg from 'assets/icons/mark.svg';
  6. import ShareIcon from 'assets/icons/share.svg';
  7. import * as MapLibreRN from '@maplibre/maplibre-react-native';
  8. import { API_HOST } from 'src/constants';
  9. import { NAVIGATION_PAGES } from 'src/types';
  10. import { useNavigation } from '@react-navigation/native';
  11. const MarkerItem = ({
  12. marker,
  13. toggleSeries,
  14. token,
  15. isPremium,
  16. setPremiumModalVisible
  17. }: {
  18. marker: any;
  19. toggleSeries: (item: any) => void;
  20. token: string;
  21. isPremium: boolean;
  22. setPremiumModalVisible: (premium: boolean) => void;
  23. }) => {
  24. const navigation = useNavigation();
  25. if (!marker || !marker.coordinates) {
  26. return null;
  27. }
  28. function formatNumber(value?: number | null) {
  29. const number = typeof value === 'number' && !isNaN(value) ? value : 0;
  30. if (number >= 1000 && number < 10000) {
  31. return (number / 1000).toFixed(1) + 'k';
  32. } else if (number >= 10000) {
  33. return (number / 1000).toFixed(0) + 'k';
  34. }
  35. return number.toString();
  36. }
  37. const formattedCount = formatNumber(marker.no_visited);
  38. const parsedAvatars =
  39. typeof marker.avatars === 'string' ? JSON.parse(marker.avatars) : marker.avatars;
  40. return (
  41. <>
  42. {Platform.OS === 'ios' ? (
  43. <MapLibreRN.PointAnnotation
  44. id="selected_marker_callout"
  45. coordinate={marker.coordinates}
  46. anchor={{ x: 0.5, y: 1 }}
  47. >
  48. <View style={styles.customView}>
  49. <View style={styles.calloutContainer}>
  50. <View style={styles.calloutImageContainer}>
  51. {!!marker.icon?.uri && (
  52. <Image
  53. source={{ uri: marker.icon.uri }}
  54. style={styles.calloutImage}
  55. resizeMode="contain"
  56. />
  57. )}
  58. </View>
  59. <View style={styles.calloutTextContainer}>
  60. <Text style={styles.seriesName}>{marker.series_name}</Text>
  61. <Text style={styles.markerName} selectable={true}>
  62. {marker.name}
  63. </Text>
  64. </View>
  65. <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
  66. <TouchableOpacity
  67. style={[
  68. styles.calloutButton,
  69. { flex: 1 },
  70. (marker.visited === 1 &&
  71. token && {
  72. backgroundColor: Colors.WHITE,
  73. borderWidth: 1,
  74. borderColor: Colors.BORDER_LIGHT
  75. }) ||
  76. {}
  77. ]}
  78. onPress={() => toggleSeries(marker)}
  79. >
  80. <View style={styles.completedContainer}>
  81. <View style={{ display: marker?.visited === 1 && token ? 'flex' : 'none' }}>
  82. <CheckSvg width={14} height={14} fill={Colors.DARK_BLUE} />
  83. </View>
  84. <Text
  85. style={[
  86. styles.calloutButtonText,
  87. marker?.visited === 1 && token ? { color: Colors.DARK_BLUE } : {}
  88. ]}
  89. >
  90. {marker?.visited === 1 && token ? 'Completed' : 'To Complete'}
  91. </Text>
  92. </View>
  93. </TouchableOpacity>
  94. <TouchableOpacity
  95. onPress={() => {
  96. Share.share(
  97. Platform.OS === 'ios'
  98. ? { url: `${API_HOST}/series-item/${marker.id}` }
  99. : { message: `${API_HOST}/series-item/${marker.id}` }
  100. );
  101. }}
  102. style={{ padding: 6, backgroundColor: Colors.FILL_LIGHT, borderRadius: 8, height: 30, marginBottom: 12 }}
  103. >
  104. <ShareIcon width={18} height={18} fill={Colors.DARK_BLUE} />
  105. </TouchableOpacity>
  106. </View>
  107. {parsedAvatars && (
  108. <TouchableOpacity
  109. onPress={() => {
  110. if (!isPremium) {
  111. setPremiumModalVisible(true);
  112. } else {
  113. navigation.navigate(
  114. ...([
  115. NAVIGATION_PAGES.USERS_LIST,
  116. {
  117. id: marker.id,
  118. isFromHere: false,
  119. type: 'series'
  120. }
  121. ] as never)
  122. );
  123. }
  124. }}
  125. style={styles.userImageContainer}
  126. >
  127. {parsedAvatars?.map((avatar: number, index: number) => (
  128. <Image
  129. key={`${avatar}-${index}`}
  130. source={{ uri: API_HOST + '/img/avatars/' + avatar + '.webp' }}
  131. style={styles.userImageSmall}
  132. />
  133. ))}
  134. <View style={styles.userCountContainer}>
  135. <Text style={styles.userCount}>{formattedCount}</Text>
  136. </View>
  137. </TouchableOpacity>
  138. )}
  139. </View>
  140. </View>
  141. </MapLibreRN.PointAnnotation>
  142. ) : (
  143. <MapLibreRN.MarkerView
  144. key={`${marker.id}-${marker.visited}`}
  145. id="selected_marker_callout"
  146. coordinate={marker.coordinates}
  147. anchor={{ x: 0.5, y: 0.9 }}
  148. >
  149. <View style={styles.customView}>
  150. <View style={styles.calloutContainer}>
  151. <View style={styles.calloutImageContainer}>
  152. {!!marker.icon?.uri && (
  153. <Image
  154. source={{ uri: marker.icon.uri }}
  155. style={styles.calloutImage}
  156. resizeMode="contain"
  157. />
  158. )}
  159. </View>
  160. <View style={[styles.calloutTextContainer, { flex: 0 }]}>
  161. <Text style={styles.seriesName}>{marker.series_name}</Text>
  162. <Text style={styles.markerName} selectable={true}>
  163. {marker.name}
  164. </Text>
  165. </View>
  166. <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
  167. <TouchableOpacity
  168. style={[
  169. styles.calloutButton,
  170. { flex: 1 },
  171. (marker.visited === 1 &&
  172. token && {
  173. backgroundColor: Colors.WHITE,
  174. borderWidth: 1,
  175. borderColor: Colors.BORDER_LIGHT
  176. }) ||
  177. {}
  178. ]}
  179. onPressIn={() => toggleSeries(marker)}
  180. >
  181. <View style={styles.completedContainer}>
  182. <View style={{ display: marker?.visited === 1 && token ? 'flex' : 'none' }}>
  183. <CheckSvg width={14} height={14} fill={Colors.DARK_BLUE} />
  184. </View>
  185. <Text
  186. style={[
  187. styles.calloutButtonText,
  188. marker?.visited === 1 && token ? { color: Colors.DARK_BLUE } : {}
  189. ]}
  190. >
  191. {marker?.visited === 1 && token ? 'Completed' : 'To Complete'}
  192. </Text>
  193. </View>
  194. </TouchableOpacity>
  195. <TouchableOpacity
  196. onPressIn={() => {
  197. Share.share(
  198. Platform.OS === 'ios'
  199. ? { url: `${API_HOST}/series-item/${marker.id}` }
  200. : { message: `${API_HOST}/series-item/${marker.id}` }
  201. );
  202. }}
  203. style={{ padding: 6, backgroundColor: Colors.FILL_LIGHT, borderRadius: 8, height: 30, marginBottom: 12 }}
  204. >
  205. <ShareIcon width={18} height={18} fill={Colors.DARK_BLUE} />
  206. </TouchableOpacity>
  207. </View>
  208. {parsedAvatars && (
  209. <TouchableOpacity
  210. onPressIn={() => {
  211. if (!isPremium) {
  212. setPremiumModalVisible(true);
  213. } else {
  214. navigation.navigate(
  215. ...([
  216. NAVIGATION_PAGES.USERS_LIST,
  217. {
  218. id: marker.id,
  219. isFromHere: false,
  220. type: 'series'
  221. }
  222. ] as never)
  223. );
  224. }
  225. }}
  226. style={styles.userImageContainer}
  227. >
  228. {parsedAvatars?.map((avatar: number, index: number) => (
  229. <Image
  230. key={`${avatar}-${index}`}
  231. source={{ uri: API_HOST + '/img/avatars/' + avatar + '.webp' }}
  232. style={styles.userImageSmall}
  233. />
  234. ))}
  235. <View style={styles.userCountContainer}>
  236. <Text style={styles.userCount}>{formattedCount}</Text>
  237. </View>
  238. </TouchableOpacity>
  239. )}
  240. </View>
  241. </View>
  242. </MapLibreRN.MarkerView>
  243. )}
  244. </>
  245. );
  246. };
  247. export default MarkerItem;