Explorar el Código

added earth screen

Viktoriia hace 1 año
padre
commit
c2aff5a16a

+ 3 - 1
Route.tsx

@@ -29,6 +29,8 @@ import UNMastersScreen from './src/screens/InAppScreens/TravellersScreen/UNMaste
 import StatisticsScreen from './src/screens/InAppScreens/TravellersScreen/StatisticsScreen';
 
 import SeriesScreen from 'src/screens/InAppScreens/TravelsScreen/Series';
+import { SeriesItemScreen } from 'src/screens/InAppScreens/TravelsScreen/SeriesItemScreen';
+import EarthScreen from 'src/screens/InAppScreens/TravelsScreen/EarthScreen';
 
 import { NAVIGATION_PAGES } from './src/types';
 import { storage, StoreType } from './src/storage';
@@ -37,7 +39,6 @@ import { openDatabases } from './src/db';
 import TabBarButton from './src/components/TabBarButton';
 import { ParamListBase, RouteProp } from '@react-navigation/native';
 import setupDatabaseAndSync from 'src/database';
-import { SeriesItemScreen } from 'src/screens/InAppScreens/TravelsScreen/SeriesItemScreen';
 
 const ScreenStack = createStackNavigator();
 const BottomTab = createBottomTabNavigator();
@@ -162,6 +163,7 @@ const Route = () => {
                     name={NAVIGATION_PAGES.SERIES_ITEM}
                     component={SeriesItemScreen}
                   />
+                  <ScreenStack.Screen name={NAVIGATION_PAGES.EARTH} component={EarthScreen} />
                 </ScreenStack.Navigator>
               )}
             </BottomTab.Screen>

+ 2 - 0
app.config.ts

@@ -6,6 +6,7 @@ import dotenv from 'dotenv';
 import type { ConfigContext, ExpoConfig } from 'expo/config';
 
 const API_HOST = env.ENV === 'production' ? env.PRODUCTION_API_HOST : env.DEVELOPMENT_API_HOST;
+const MAP_HOST = env.ENV === 'production' ? env.PRODUCTION_MAP_HOST : env.DEVELOPMENT_MAP_HOST;
 
 dotenv.config({
   path: path.resolve(process.cwd(), '.env')
@@ -27,6 +28,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
   extra: {
     ENV: env.ENV,
     API_HOST: API_HOST,
+    MAP_HOST: MAP_HOST,
     eas: {
       projectId: env.EAS_PROJECT_ID
     }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
assets/geojson/kye.json


+ 1 - 1
src/constants/secrets.ts

@@ -1,5 +1,5 @@
 import Constants from 'expo-constants';
 
-export const { API_HOST } = Constants.manifest2?.extra?.expoClient?.extra ?? {};
+export const { API_HOST, MAP_HOST } = Constants.manifest2?.extra?.expoClient?.extra ?? {};
 
 export const API_URL = `${API_HOST}/webapi`;

+ 3 - 0
src/modules/api/travels/index.ts

@@ -0,0 +1,3 @@
+export * from './queries';
+export * from './travels-api';
+export * from './travels-query-keys';

+ 2 - 0
src/modules/api/travels/queries/index.ts

@@ -0,0 +1,2 @@
+export * from './use-post-get-kye';
+export * from './use-post-set-kye';

+ 17 - 0
src/modules/api/travels/queries/use-post-get-kye.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { travelsQueryKeys } from '../travels-query-keys';
+import { travelsApi, type PostGetKyeReturn } from '../travels-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetKyeQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetKyeReturn, BaseAxiosError>({
+    queryKey: travelsQueryKeys.getKye(token),
+    queryFn: async () => {
+      const response = await travelsApi.getKye(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 21 - 0
src/modules/api/travels/queries/use-post-set-kye.tsx

@@ -0,0 +1,21 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { travelsQueryKeys } from '../travels-query-keys';
+import { travelsApi, type PostSetKyeReturn } from '../travels-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostSetKye = () => {
+  return useMutation<
+    PostSetKyeReturn,
+    BaseAxiosError,
+    { token: string; qid: number; visited: 0 | 1 },
+    PostSetKyeReturn
+  >({
+    mutationKey: travelsQueryKeys.setKye(),
+    mutationFn: async (variables) => {
+      const response = await travelsApi.setKye(variables.token, variables.qid, variables.visited);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/travels/travels-api.tsx

@@ -0,0 +1,17 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetKyeReturn extends ResponseType {
+  max: number;
+  regions: { qid: number; name: string }[];
+  visited: number[];
+}
+
+export interface PostSetKyeReturn extends ResponseType {}
+
+export const travelsApi = {
+  getKye: (token: string) => request.postForm<PostGetKyeReturn>(API.GET_KYE, { token }),
+  setKye: (token: string, qid: number, visited: 0 | 1) =>
+    request.postForm<PostSetKyeReturn>(API.SET_KYE, { token, qid, visited })
+};

+ 4 - 0
src/modules/api/travels/travels-query-keys.tsx

@@ -0,0 +1,4 @@
+export const travelsQueryKeys = {
+  getKye: (token: string) => ['getKye', { token }] as const,
+  setKye: () => ['setKye'] as const
+};

+ 1 - 2
src/screens/InAppScreens/MapScreen/index.tsx

@@ -42,8 +42,7 @@ import {
   Region,
   Series
 } from '../../../types/map';
-
-const MAP_HOST = 'https://maps.nomadmania.eu';
+import { MAP_HOST } from 'src/constants';
 
 const tilesBaseURL = `${MAP_HOST}/tiles_osm`;
 const localTileDir = `${FileSystem.cacheDirectory}tiles/background`;

+ 195 - 0
src/screens/InAppScreens/TravelsScreen/EarthScreen/index.tsx

@@ -0,0 +1,195 @@
+import React, { useEffect, useState } from 'react';
+import { View, Platform, SafeAreaView, Text } from 'react-native';
+import MapView, { Geojson, UrlTile } from 'react-native-maps';
+
+import kye from '../../../../../assets/geojson/kye.json';
+import { Header } from 'src/components';
+import { useGetKyeQuery, usePostSetKye } from '@api/travels';
+import { StoreType, storage } from 'src/storage';
+
+import { FeatureCollection } from '@turf/turf';
+import { MAP_HOST } from 'src/constants';
+import { styles } from './styles';
+
+interface PropertiesData {
+  id: number;
+  tt: string;
+  mega: string;
+  name: string;
+  fill?: string;
+}
+
+const EarthScreen = () => {
+  const token = storage.get('token', StoreType.STRING) as string;
+  const [geojson, setGeojson] = useState<FeatureCollection>(kye as FeatureCollection);
+  const { data } = useGetKyeQuery(token, true);
+  const { mutateAsync } = usePostSetKye();
+  const [visited, setVisited] = useState<number[]>([]);
+  const [score, setScore] = useState({ base: 0, percentage: 0 });
+  const [quadrantsAll, setQuadrantsAll] = useState<number>(1);
+
+  useEffect(() => {
+    if (!data) return;
+    setQuadrantsAll(data.regions.length);
+    setVisited(data.visited);
+  }, [data]);
+
+  useEffect(() => {
+    const updatedGeojson = updateGeojsonStyles(geojson);
+    setGeojson(updatedGeojson);
+    showScore();
+  }, [visited]);
+
+  const getFeatureStyle = (properties: PropertiesData) => {
+    const style = {
+      fill: 'transparent'
+    };
+
+    if (properties.tt == '1') {
+      if (properties.id > 612) {
+        if (Math.max(...visited) > 612) {
+          style.fill = 'rgba(132, 138, 68, 0.5)';
+        } else {
+          style.fill = 'rgba(21, 99, 123, 0.5)';
+        }
+      } else {
+        if (visited.includes(properties.id)) {
+          style.fill = 'rgba(132, 138, 68, 0.5)';
+        } else {
+          style.fill = 'rgba(21, 99, 123, 0.5)';
+        }
+      }
+    } else {
+      style.fill = 'rgba(230, 230, 230, 0.5)';
+    }
+
+    return style;
+  };
+
+  const updateGeojsonStyles = (geojson: FeatureCollection) => {
+    const updatedFeatures = geojson.features.map((feature) => {
+      const style = getFeatureStyle(feature.properties as PropertiesData);
+      return { ...feature, properties: { ...feature.properties, ...style } };
+    });
+
+    return { ...geojson, features: updatedFeatures };
+  };
+
+  const markQuadrant = async (qid: number) => {
+    let vis: 0 | 1 = 1;
+    if (qid > 612) {
+      if (Math.max(...visited) > 612) {
+        vis = 0;
+      }
+    } else {
+      if (visited.includes(qid)) {
+        vis = 0;
+      }
+    }
+
+    try {
+      await mutateAsync(
+        { token, qid, visited: vis },
+        {
+          onSuccess: () => {
+            if (vis == 0) {
+              quadRemove(qid);
+            } else {
+              quadAdd(qid);
+            }
+          }
+        }
+      );
+    } catch (error) {
+      console.error('Failed to update kye state', error);
+    }
+  };
+
+  const quadAdd = (qid: number) => {
+    setVisited((prevVisited) => {
+      if (qid > 612) {
+        const newVisited = [...prevVisited];
+        for (let i = 613; i < 649; i++) {
+          if (!newVisited.includes(i)) {
+            newVisited.push(i);
+          }
+        }
+        return newVisited;
+      } else {
+        if (!prevVisited.includes(qid)) {
+          return [...prevVisited, qid];
+        }
+        return prevVisited;
+      }
+    });
+  };
+
+  const quadRemove = (qid: number) => {
+    setVisited((prevVisited) => {
+      if (qid > 612) {
+        return prevVisited.filter((id) => id < 613 || id > 648);
+      } else {
+        return prevVisited.filter((id) => id !== qid);
+      }
+    });
+  };
+
+  const showScore = () => {
+    let baseList = visited.filter((id) => id < 613);
+    let score = baseList.length;
+    if (Math.max(...visited) > 612) {
+      score += 1;
+    }
+    let calc = Math.round((score / quadrantsAll) * 10000) / 100;
+    setScore({ base: score, percentage: calc });
+  };
+
+  return (
+    <SafeAreaView style={{ height: '100%' }}>
+      <View style={styles.wrapper}>
+        <Header label={'My Earth'} />
+        <View style={styles.score}>
+          <Text style={[styles.scoreText, { fontWeight: 'bold' }]}>Your score: {score.base}</Text>
+          <Text style={[styles.scoreText, { fontSize: 14 }]}>{`(${score.percentage}%)`}</Text>
+        </View>
+      </View>
+
+      <View style={styles.container}>
+        <MapView
+          style={styles.map}
+          showsMyLocationButton={false}
+          showsCompass={false}
+          zoomControlEnabled={false}
+          mapType={Platform.OS == 'android' ? 'none' : 'standard'}
+          maxZoomLevel={15}
+          minZoomLevel={0}
+          initialRegion={{
+            latitude: 0,
+            longitude: 0,
+            latitudeDelta: 180,
+            longitudeDelta: 180
+          }}
+        >
+          <UrlTile
+            urlTemplate={`${MAP_HOST}/tiles_osm/{z}/{x}/{y}`}
+            maximumZ={15}
+            maximumNativeZ={13}
+            shouldReplaceMapContent
+            minimumZ={0}
+          />
+          <Geojson
+            geojson={geojson as any}
+            zIndex={1}
+            tappable={true}
+            onPress={(event) => {
+              markQuadrant(event.feature.properties?.id);
+            }}
+            tracksViewChanges={false}
+          />
+        </MapView>
+      </View>
+    </SafeAreaView>
+  );
+};
+
+export default EarthScreen;

+ 26 - 0
src/screens/InAppScreens/TravelsScreen/EarthScreen/styles.tsx

@@ -0,0 +1,26 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+
+export const styles = StyleSheet.create({
+  container: {
+    flex: 1
+  },
+  wrapper: {
+    marginLeft: '5%',
+    marginRight: '5%',
+    alignItems: 'center'
+  },
+  map: {
+    ...StyleSheet.absoluteFillObject
+  },
+  score: {
+    flexDirection: 'row',
+    alignItems: 'baseline',
+    marginBottom: 16,
+    gap: 5
+  },
+  scoreText: {
+    color: Colors.DARK_BLUE,
+    fontSize: 16
+  }
+});

+ 15 - 8
src/screens/InAppScreens/TravelsScreen/index.tsx

@@ -22,15 +22,15 @@ const TravelsScreen = () => {
   const buttons: MenuButtonType[] = [
     {
       label: 'Countries',
-      icon: <FlagsIcon fill={Colors.DARK_BLUE} width={20} height={20} />,
+      icon: <FlagsIcon fill={Colors.DARK_BLUE} width={20} height={20} />
     },
     {
       label: 'Regions',
-      icon: <RegionsIcon fill={Colors.DARK_BLUE} width={20} height={20} />,
+      icon: <RegionsIcon fill={Colors.DARK_BLUE} width={20} height={20} />
     },
     {
       label: 'DARE',
-      icon: <MapLocationIcon fill={Colors.DARK_BLUE} width={20} height={20} />,
+      icon: <MapLocationIcon fill={Colors.DARK_BLUE} width={20} height={20} />
     },
     {
       label: 'Series',
@@ -45,18 +45,25 @@ const TravelsScreen = () => {
     },
     {
       label: 'Earth',
-      icon: <EarthIcon fill={Colors.DARK_BLUE} width={20} height={20} />
+      icon: <EarthIcon fill={Colors.DARK_BLUE} width={20} height={20} />,
+      buttonFn: (navigation) => {
+        if (!token) {
+          setIsModalVisible(true);
+        } else {
+          navigation.navigate(NAVIGATION_PAGES.EARTH);
+        }
+      }
     },
     {
       label: 'Trips',
-      icon: <TripIcon fill={Colors.DARK_BLUE} width={20} height={20} />,
+      icon: <TripIcon fill={Colors.DARK_BLUE} width={20} height={20} />
     },
     {
       label: 'Photos',
-      icon: <ImagesIcon fill={Colors.DARK_BLUE} width={20} height={20} />,
-    },
+      icon: <ImagesIcon fill={Colors.DARK_BLUE} width={20} height={20} />
+    }
   ];
-  
+
   return (
     <PageWrapper>
       {buttons.map((button, index) => (

+ 8 - 3
src/types/api.ts

@@ -7,7 +7,8 @@ export enum API_ROUTE {
   RANKING = 'ranking',
   UN_MASTERS = 'un-masters',
   AVATARS = 'avatars',
-  STATISTICS = 'statistics'
+  STATISTICS = 'statistics',
+  KYE = 'kye'
 }
 
 export enum API_ENDPOINT {
@@ -38,7 +39,9 @@ export enum API_ENDPOINT {
   GET_COUNTRIES_RANKING_LPI = 'get-countries-ranking-lpi',
   GET_COUNTRIES_RANKING_MEMORIAM = 'get-countries-ranking-in-memoriam',
   GET_ITEMS_FOR_SERIES = 'get-items-for-series-grouped-app',
-  TOGGLE_ITEM_SERIES = 'toggle-item'
+  TOGGLE_ITEM_SERIES = 'toggle-item',
+  GET_KYE = 'get-kye',
+  SET_KYE = 'set-kye'
 }
 
 export enum API {
@@ -68,7 +71,9 @@ export enum API {
   GET_COUNTRIES_RANKING_LPI = `${API_ROUTE.RANKING}/${API_ENDPOINT.GET_COUNTRIES_RANKING_LPI}`,
   GET_COUNTRIES_RANKING_MEMORIAM = `${API_ROUTE.RANKING}/${API_ENDPOINT.GET_COUNTRIES_RANKING_MEMORIAM}`,
   GET_ITEMS_FOR_SERIES = `${API_ROUTE.SERIES}/${API_ENDPOINT.GET_ITEMS_FOR_SERIES}`,
-  TOGGLE_ITEM_SERIES = `${API_ROUTE.SERIES}/${API_ENDPOINT.TOGGLE_ITEM_SERIES}`
+  TOGGLE_ITEM_SERIES = `${API_ROUTE.SERIES}/${API_ENDPOINT.TOGGLE_ITEM_SERIES}`,
+  GET_KYE = `${API_ROUTE.KYE}/${API_ENDPOINT.GET_KYE}`,
+  SET_KYE = `${API_ROUTE.KYE}/${API_ENDPOINT.SET_KYE}`
 }
 
 export type BaseAxiosError = AxiosError;

+ 1 - 0
src/types/navigation.ts

@@ -24,4 +24,5 @@ export enum NAVIGATION_PAGES {
   TRAVELS_TAB = 'inAppTravels',
   SERIES = 'inAppSeries',
   SERIES_ITEM = 'inAppSeriesItem',
+  EARTH = 'inAppEarth',
 }

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio