index.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import { useEffect, useRef } from 'react';
  2. import { Text, TouchableOpacity, View, StyleSheet, Image, Animated } from 'react-native';
  3. interface Region {
  4. id: number;
  5. name: string;
  6. region_photos: string;
  7. visitors_count: number;
  8. }
  9. interface RegionPopupProps {
  10. region: Region;
  11. userAvatars: string[];
  12. onMarkVisited: () => void;
  13. }
  14. const RegionPopup: React.FC<RegionPopupProps> = ({ region, userAvatars, onMarkVisited }) => {
  15. const fadeAnim = useRef(new Animated.Value(0)).current;
  16. useEffect(() => {
  17. Animated.timing(fadeAnim, {
  18. toValue: 1,
  19. duration: 300,
  20. useNativeDriver: true,
  21. }).start();
  22. }, [fadeAnim]);
  23. const splitRegionName = (fullName: string) => {
  24. const parts = fullName.split(/ – | - /);
  25. return {
  26. regionTitle: parts[0],
  27. regionSubtitle: parts.length > 1 ? parts[1] : '',
  28. };
  29. };
  30. const { regionTitle, regionSubtitle } = splitRegionName(region.name);
  31. const regionImg = JSON.parse(region.region_photos)[0];
  32. function formatNumber(number: number) {
  33. if (number >= 1000) {
  34. return (number / 1000).toFixed(1) + 'k';
  35. }
  36. return number.toString();
  37. }
  38. const formattedCount = formatNumber(region.visitors_count);
  39. return (
  40. <Animated.View style={[styles.popupContainer, {opacity: fadeAnim}]}>
  41. <View style={styles.regionInfoContainer}>
  42. {regionImg && (
  43. <Image source={{ uri: regionImg}} style={styles.regionImage} />
  44. )}
  45. <View style={styles.regionTextContainer}>
  46. <Text style={styles.regionTitle}>{regionTitle}</Text>
  47. <Text style={styles.regionSubtitle}>{regionSubtitle}</Text>
  48. </View>
  49. </View>
  50. <View style={styles.separator} />
  51. <View style={styles.bottomContainer}>
  52. <View style={styles.userContainer}>
  53. <View style={styles.userImageContainer}>
  54. {userAvatars?.map((avatar, index) => (
  55. <Image key={index} source={{ uri: avatar }} style={styles.userImage} />
  56. ))}
  57. <View style={styles.userCountContainer}>
  58. <Text style={styles.userCount}>{formattedCount}</Text>
  59. </View>
  60. </View>
  61. </View>
  62. <TouchableOpacity style={styles.markVisitedButton} onPress={onMarkVisited}>
  63. <Text style={styles.markVisitedText}>Mark Visited</Text>
  64. </TouchableOpacity>
  65. </View>
  66. </Animated.View>
  67. );
  68. };
  69. export default RegionPopup;
  70. const styles = StyleSheet.create({
  71. popupContainer: {
  72. position: 'absolute',
  73. bottom: 22,
  74. left: 0,
  75. right: 0,
  76. backgroundColor: 'white',
  77. padding: 16,
  78. borderRadius: 8,
  79. alignItems: 'center',
  80. justifyContent: 'center',
  81. height: 152,
  82. marginHorizontal: 24,
  83. shadowColor: 'rgba(33, 37, 41, 0.12)',
  84. shadowOffset: { width: 0, height: 4 },
  85. shadowRadius: 8,
  86. elevation: 5,
  87. zIndex: 2,
  88. },
  89. regionInfoContainer: {
  90. flexDirection: 'row',
  91. alignItems: 'center',
  92. justifyContent: 'flex-start',
  93. width: '100%',
  94. },
  95. regionImage: {
  96. width: 60,
  97. height: 60,
  98. borderRadius: 6,
  99. marginRight: 10,
  100. },
  101. regionTextContainer: {
  102. justifyContent: 'center',
  103. flex: 1,
  104. },
  105. regionTitle: {
  106. fontSize: 16,
  107. fontWeight: 'bold',
  108. color: '#0F3F4F'
  109. },
  110. regionSubtitle: {
  111. fontSize: 13,
  112. color: '#3E6471',
  113. },
  114. separator: {
  115. borderBottomWidth: 1,
  116. borderBottomColor: '#E5E5E5',
  117. width: '100%',
  118. marginVertical: 16,
  119. },
  120. bottomContainer: {
  121. flexDirection: 'row',
  122. alignItems: 'center',
  123. justifyContent: 'space-between',
  124. width: '100%',
  125. },
  126. userContainer: {
  127. flexDirection: 'row',
  128. alignItems: 'center',
  129. marginLeft: 6,
  130. },
  131. userImageContainer: {
  132. flexDirection: 'row',
  133. alignItems: 'center',
  134. marginRight: 10,
  135. },
  136. userImage: {
  137. width: 28,
  138. height: 28,
  139. borderRadius: 14,
  140. marginLeft: -6,
  141. borderWidth: 1,
  142. borderColor: '#E6E6E6',
  143. resizeMode: 'cover',
  144. },
  145. userCountContainer: {
  146. width: 28,
  147. height: 28,
  148. borderRadius: 14,
  149. backgroundColor: '#E5E5E5',
  150. alignItems: 'center',
  151. justifyContent: 'center',
  152. marginLeft: -6,
  153. },
  154. userCount: {
  155. fontSize: 12,
  156. color: '#0F3F4F',
  157. lineHeight: 24,
  158. },
  159. markVisitedButton: {
  160. backgroundColor: '#ED9334',
  161. paddingHorizontal: 12,
  162. paddingVertical: 8,
  163. borderRadius: 6,
  164. alignItems: 'center',
  165. justifyContent: 'center',
  166. height: 32,
  167. },
  168. markVisitedText: {
  169. fontSize: 12,
  170. color: 'white',
  171. fontWeight: '700',
  172. letterSpacing: 0.01,
  173. lineHeight: 16,
  174. },
  175. });