index.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import React, { useEffect, useRef, useState } from 'react';
  2. import { View, Platform, Text, StatusBar } from 'react-native';
  3. import { SafeAreaView } from 'react-native-safe-area-context';
  4. import kye from '../../../../../assets/geojson/kye.json';
  5. import { Header } from 'src/components';
  6. import { useGetKyeQuery, usePostSetKye } from '@api/travels';
  7. import { StoreType, storage } from 'src/storage';
  8. import { VECTOR_MAP_HOST } from 'src/constants';
  9. import { styles } from './styles';
  10. import * as MapLibreRN from '@maplibre/maplibre-react-native';
  11. let kye_fill = {
  12. id: 'kye_fill',
  13. style: {
  14. fillColor: 'rgba(21, 99, 123, 0.4)'
  15. },
  16. maxzoom: 12
  17. };
  18. let kye_fill_visited = {
  19. id: 'kye_fill_visited',
  20. style: {
  21. fillColor: 'rgba(132, 138, 68, 0.6)'
  22. },
  23. maxzoom: 12
  24. };
  25. let kye_line = {
  26. id: 'kye_line',
  27. filter: ['all'],
  28. style: {
  29. lineColor: 'rgba(14, 80, 109, 1)',
  30. lineWidth: ['interpolate', ['linear'], ['zoom'], 0, 0.8, 4, 1, 5, 1.5, 12, 3],
  31. lineWidthTransition: { duration: 300, delay: 0 }
  32. },
  33. maxzoom: 16
  34. };
  35. const EarthScreen = () => {
  36. const token = (storage.get('token', StoreType.STRING) as string) || '';
  37. const { data } = useGetKyeQuery(token, !!token);
  38. const { mutateAsync } = usePostSetKye();
  39. const [visited, setVisited] = useState<number[]>([]);
  40. const [score, setScore] = useState({ base: 0, percentage: 0 });
  41. const [quadrantsAll, setQuadrantsAll] = useState<number>(1);
  42. const generateFilter = (ids: number[]) => {
  43. if (!ids || !ids.length) return ['==', 'id', -1];
  44. return [
  45. 'any',
  46. ...ids.map((id) => {
  47. if (id > 612) {
  48. return ['>=', 'id', 613];
  49. } else {
  50. return ['==', 'id', id];
  51. }
  52. })
  53. ];
  54. };
  55. const [filter, setFilter] = useState<any[]>(generateFilter([]));
  56. const mapRef = useRef<MapLibreRN.MapViewRef>(null);
  57. useEffect(() => {
  58. if (!data || !data.regions) return;
  59. setQuadrantsAll(data.regions?.length);
  60. token && setVisited(data.visited);
  61. }, [data]);
  62. useEffect(() => {
  63. setFilter(generateFilter(visited));
  64. showScore();
  65. }, [visited]);
  66. const markQuadrant = async (qid: number) => {
  67. if (!token) return;
  68. let vis: 0 | 1 = 1;
  69. if (qid > 612) {
  70. if (Math.max(...visited) > 612) {
  71. vis = 0;
  72. }
  73. } else {
  74. if (visited.includes(qid)) {
  75. vis = 0;
  76. }
  77. }
  78. try {
  79. await mutateAsync(
  80. { token, qid, visited: vis },
  81. {
  82. onSuccess: () => {
  83. if (vis == 0) {
  84. quadRemove(qid);
  85. } else {
  86. quadAdd(qid);
  87. }
  88. }
  89. }
  90. );
  91. } catch (error) {
  92. console.error('Failed to update kye state', error);
  93. }
  94. };
  95. const quadAdd = (qid: number) => {
  96. setVisited((prevVisited) => {
  97. if (qid > 612) {
  98. const newVisited = [...prevVisited];
  99. for (let i = 613; i < 649; i++) {
  100. if (!newVisited.includes(i)) {
  101. newVisited.push(i);
  102. }
  103. }
  104. return newVisited;
  105. } else {
  106. if (!prevVisited.includes(qid)) {
  107. return [...prevVisited, qid];
  108. }
  109. return prevVisited;
  110. }
  111. });
  112. };
  113. const quadRemove = (qid: number) => {
  114. setVisited((prevVisited) => {
  115. if (qid > 612) {
  116. return prevVisited.filter((id) => id < 613 || id > 648);
  117. } else {
  118. return prevVisited.filter((id) => id !== qid);
  119. }
  120. });
  121. };
  122. const showScore = () => {
  123. let baseList = visited.filter((id) => id < 613);
  124. let score = baseList.length;
  125. if (Math.max(...visited) > 612) {
  126. score += 1;
  127. }
  128. let calc = Math.round((score / quadrantsAll) * 10000) / 100;
  129. setScore({ base: score, percentage: calc });
  130. };
  131. const onMapPress = async (event: any) => {
  132. if (!mapRef.current) return;
  133. try {
  134. const { screenPointX, screenPointY } = event.properties;
  135. const { features } = await mapRef.current.queryRenderedFeaturesAtPoint(
  136. [screenPointX, screenPointY],
  137. undefined,
  138. ['kye_fill']
  139. );
  140. if (features?.length) {
  141. const selectedKye = features[0];
  142. if (selectedKye.properties && selectedKye.properties.tt == '1') {
  143. markQuadrant(selectedKye.properties.id);
  144. }
  145. }
  146. } catch (error) {
  147. console.error('Failed to get coordinates on EarthScreen', error);
  148. }
  149. };
  150. return (
  151. <SafeAreaView style={{ height: '100%' }} edges={['top']}>
  152. <View style={styles.wrapper}>
  153. <Header label={'My Earth'} />
  154. {token && (
  155. <View style={styles.score}>
  156. <Text style={[styles.scoreText, { fontWeight: 'bold' }]}>Your score: {score.base}</Text>
  157. <Text style={[styles.scoreText, { fontSize: 14 }]}>{`(${score.percentage}%)`}</Text>
  158. </View>
  159. )}
  160. </View>
  161. <View
  162. style={[
  163. styles.container,
  164. { paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 }
  165. ]}
  166. >
  167. <MapLibreRN.MapView
  168. ref={mapRef}
  169. style={styles.map}
  170. mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps.json'}
  171. rotateEnabled={false}
  172. attributionEnabled={false}
  173. onPress={onMapPress}
  174. >
  175. <MapLibreRN.ShapeSource id="kye" shape={kye as any}>
  176. <MapLibreRN.LineLayer
  177. id={kye_line.id}
  178. filter={kye_line.filter as any}
  179. maxZoomLevel={kye_line.maxzoom}
  180. style={kye_line.style as any}
  181. belowLayerID="waterway-name"
  182. />
  183. <MapLibreRN.FillLayer
  184. id={kye_fill.id}
  185. filter={['==', 'tt', '1']}
  186. style={kye_fill.style}
  187. maxZoomLevel={kye_fill.maxzoom}
  188. belowLayerID={kye_fill_visited.id}
  189. />
  190. <MapLibreRN.FillLayer
  191. id={kye_fill_visited.id}
  192. filter={filter as any}
  193. style={kye_fill_visited.style}
  194. maxZoomLevel={kye_fill_visited.maxzoom}
  195. belowLayerID={kye_line.id}
  196. />
  197. </MapLibreRN.ShapeSource>
  198. </MapLibreRN.MapView>
  199. </View>
  200. </SafeAreaView>
  201. );
  202. };
  203. export default EarthScreen;