Bladeren bron

added users map

Viktoriia 1 jaar geleden
bovenliggende
commit
2e486ad47b

+ 5 - 0
Route.tsx

@@ -33,6 +33,7 @@ import StatisticsListScreen from './src/screens/InAppScreens/TravellersScreen/St
 import TriumphsScreen from 'src/screens/InAppScreens/TravellersScreen/TriumphsScreen';
 import SeriesRankingScreen from 'src/screens/InAppScreens/TravellersScreen/SeriesRankingScreen';
 import SeriesRankingListScreen from 'src/screens/InAppScreens/TravellersScreen/SeriesRankingListScreen';
+import UsersMapScreen from 'src/screens/InAppScreens/ProfileScreen/UsersMap';
 
 import SeriesScreen from 'src/screens/InAppScreens/TravelsScreen/Series';
 import { SeriesItemScreen } from 'src/screens/InAppScreens/TravelsScreen/SeriesItemScreen';
@@ -215,6 +216,10 @@ const Route = () => {
                   <ScreenStack.Screen name={NAVIGATION_PAGES.TRIUMPHS} component={TriumphsScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.SERIES_RANKING} component={SeriesRankingScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.SERIES_RANKING_LIST} component={SeriesRankingListScreen} />
+                  <ScreenStack.Screen
+                    name={NAVIGATION_PAGES.USERS_MAP}
+                    component={UsersMapScreen}
+                  />
                 </ScreenStack.Navigator>
               )}
             </BottomTab.Screen>

BIN
assets/nm-map.png


+ 5 - 2
src/components/AvatarWithInitials/index.tsx

@@ -2,15 +2,18 @@ import React from 'react';
 import { View, Text, Image } from 'react-native';
 
 import { styles } from './styles';
+import { Colors } from 'src/theme';
 
 export const AvatarWithInitials = ({
   text,
   flag,
-  size
+  size,
+  borderColor = Colors.FILL_LIGHT
 }: {
   text: string;
   flag: string;
   size: number;
+  borderColor?: string;
 }) => {
   const shadowProps = [
     { textShadowOffset: { width: -0.5, height: -0.5 } },
@@ -28,7 +31,7 @@ export const AvatarWithInitials = ({
   }));
 
   return (
-    <View style={[styles.container, { width: size, height: size, borderRadius: size / 2 }]}>
+    <View style={[styles.container, { width: size, height: size, borderRadius: size / 2, borderColor }]}>
       <Image style={styles.avatar} source={{ uri: flag }} />
       <View style={styles.initialsContainer}>
         {shadowProps.map((props, index) => (

+ 0 - 1
src/components/AvatarWithInitials/styles.tsx

@@ -6,7 +6,6 @@ export const styles = StyleSheet.create({
     alignItems: 'center',
     justifyContent: 'center',
     overflow: 'hidden',
-    borderColor: Colors.FILL_LIGHT,
     borderWidth: 2
   },
   avatar: {

+ 118 - 0
src/screens/InAppScreens/ProfileScreen/UsersMap/index.tsx

@@ -0,0 +1,118 @@
+import { Platform, TouchableOpacity, View, Image } from 'react-native';
+import React, { FC, useEffect, useRef } from 'react';
+import MapView, { UrlTile } from 'react-native-maps';
+
+import { styles } from './styles';
+import { API_HOST, FASTEST_MAP_HOST } from 'src/constants';
+import { NavigationProp } from '@react-navigation/native';
+import { AvatarWithInitials } from 'src/components';
+import { Colors } from 'src/theme';
+
+type Props = {
+  navigation: NavigationProp<any>;
+  route: any;
+};
+
+const UsersMapScreen: FC<Props> = ({ navigation, route }) => {
+  const userId = route.params?.userId;
+  const data = route.params?.data;
+
+  const tilesBaseURL = `${FASTEST_MAP_HOST}/tiles_osm`;
+  const gridUrl = `${FASTEST_MAP_HOST}/tiles_nm/grid`;
+  const visitedTiles = `${FASTEST_MAP_HOST}/tiles_nm/user_visited/${userId}`;
+
+  const mapRef = useRef<MapView>(null);
+
+  useEffect(() => {
+    navigation.getParent()?.setOptions({
+      tabBarStyle: {
+        display: 'none',
+        position: 'absolute',
+        ...Platform.select({
+          android: {
+            height: 58
+          }
+        })
+      }
+    });
+  }, [navigation]);
+
+  const renderMapTiles = (url: string, zIndex: number, opacity = 1) => (
+    <UrlTile
+      urlTemplate={`${url}/{z}/{x}/{y}`}
+      maximumZ={15}
+      maximumNativeZ={13}
+      shouldReplaceMapContent
+      minimumZ={0}
+      opacity={opacity}
+      zIndex={zIndex}
+    />
+  );
+
+  const handleGoBack = () => {
+    navigation.getParent()?.setOptions({
+      tabBarStyle: {
+        display: 'flex',
+        position: 'absolute',
+        ...Platform.select({
+          android: {
+            height: 58
+          }
+        })
+      }
+    });
+    navigation.goBack();
+  }
+
+  return (
+    <View style={styles.container}>
+      <MapView
+        initialRegion={{
+          latitude: 0,
+          longitude: 0,
+          latitudeDelta: 180,
+          longitudeDelta: 180
+        }}
+        ref={mapRef}
+        showsMyLocationButton={false}
+        showsCompass={false}
+        zoomControlEnabled={false}
+        style={styles.map}
+        mapType={Platform.OS == 'android' ? 'none' : 'standard'}
+        maxZoomLevel={15}
+        minZoomLevel={0}
+      >
+        {renderMapTiles(tilesBaseURL, 1)}
+        {renderMapTiles(gridUrl, 2)}
+        {userId && renderMapTiles(visitedTiles, 2, 0.5)}
+      </MapView>
+
+      <TouchableOpacity
+        style={[styles.cornerButton, styles.topLeftButton]}
+        onPress={handleGoBack}
+      >
+        {data.avatar ? (
+          <Image
+            style={{
+              borderRadius: 48 / 2,
+              width: 48,
+              height: 48,
+              borderWidth: 2,
+              borderColor: Colors.WHITE
+            }}
+            source={{ uri: API_HOST + data.avatar }}
+          />
+        ) : (
+          <AvatarWithInitials
+            text={`${data.first_name[0] ?? ''}${data.last_name[0] ?? ''}`}
+            flag={API_HOST + data.homebase_flag}
+            size={48}
+            borderColor={Colors.WHITE}
+          />
+        )}
+      </TouchableOpacity>
+    </View>
+  );
+};
+
+export default UsersMapScreen;

+ 31 - 0
src/screens/InAppScreens/ProfileScreen/UsersMap/styles.tsx

@@ -0,0 +1,31 @@
+import { StyleSheet, Platform } from 'react-native';
+import { Colors } from '../../../../theme';
+
+export const styles = StyleSheet.create({
+  container: {
+    ...StyleSheet.absoluteFillObject,
+    alignItems: 'center',
+    justifyContent: 'flex-end',
+  },
+  map: {
+    ...StyleSheet.absoluteFillObject,
+  },
+  cornerButton: {
+    position: 'absolute',
+    backgroundColor: Colors.WHITE,
+    padding: 12,
+    width: 48,
+    height: 48,
+    borderRadius: 24,
+    alignItems: 'center',
+    justifyContent: 'center',
+    shadowColor: 'rgba(33, 37, 41, 0.12)',
+    shadowOffset: { width: 0, height: 4 },
+    shadowRadius: 8,
+    elevation: 5,
+  },
+  topLeftButton: {
+    top: 44,
+    left: 16,
+  }
+});

+ 49 - 25
src/screens/InAppScreens/ProfileScreen/index.tsx

@@ -1,4 +1,4 @@
-import React, { FC, ReactNode, useState } from 'react';
+import React, { FC, ReactNode, useEffect, useState } from 'react';
 import { Linking, ScrollView, Text, TouchableOpacity, View } from 'react-native';
 import { Image } from 'expo-image';
 import { CommonActions, NavigationProp, useNavigation } from '@react-navigation/native';
@@ -36,6 +36,7 @@ import IconGlobe from '../../../../assets/icons/bottom-navigation/globe.svg';
 import IconLink from '../../../../assets/icons/link.svg';
 import GearIcon from '../../../../assets/icons/gear.svg';
 import ArrowIcon from '../../../../assets/icons/next.svg';
+import axios from 'axios';
 
 type Props = {
   navigation: NavigationProp<any>;
@@ -46,6 +47,8 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
   const isPublicView = route.name === NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW;
 
   const token = storage.get('token', StoreType.STRING) as string;
+  const [isUserMapAvailable, setIsUserMapAvailable] = useState<boolean>(false);
+  const [loading, setLoading] = useState(true);
 
   if (!token) return <UnauthenticatedProfileScreen />;
 
@@ -53,23 +56,57 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
     ? usePostGetProfileInfoPublicQuery(route.params?.userId, true)
     : usePostGetProfileInfoQuery(token, true);
 
-  if (!data || isFetching) return <Loading />;
+  useEffect(() => {
+    const getUserMapUrl = async () => {
+      const response = await axios.get(`${API_HOST}/img/single_maps/${route.params?.userId}.png`);
+      return Boolean(response.data);
+    };
+    getUserMapUrl().then((res) => {
+      setIsUserMapAvailable(res);
+      setLoading(false);
+    });
+  }, []);
+
+  if (!data || isFetching || loading) return <Loading />;
+
+  const handleGoToMap = () => {
+    isPublicView
+      ? navigation.navigate(NAVIGATION_PAGES.USERS_MAP, { userId: route.params?.userId, data })
+      : navigation.dispatch(
+          CommonActions.reset({
+            index: 1,
+            routes: [{ name: NAVIGATION_PAGES.MAP_TAB }]
+          })
+        );
+  };
 
   return (
     <PageWrapper>
       {isPublicView && <Header label="Profile" />}
-      <View style={[styles.pageWrapper, isPublicView && { marginTop: 0 }]}>
-        <View>
+      <TouchableOpacity
+        style={[styles.usersMap, { backgroundColor: '#EBF2F5' }]}
+        onPress={handleGoToMap}
+      >
+        <Image
+          source={
+            isUserMapAvailable
+              ? { uri: `${API_HOST}/img/single_maps/${route.params?.userId}.png` }
+              : require('../../../../assets/nm-map.png')
+          }
+          style={styles.usersMap}
+        />
+      </TouchableOpacity>
+
+      <View style={styles.pageWrapper}>
+        <View style={{ top: -34 }}>
           {data.avatar ? (
-            <Image
-              style={{ borderRadius: 64 / 2, width: 64, height: 64 }}
-              source={{ uri: API_HOST + data.avatar }}
-            />
+            <Image style={styles.avatar} source={{ uri: API_HOST + data.avatar }} />
           ) : (
             <AvatarWithInitials
               text={`${data.first_name[0] ?? ''}${data.last_name[0] ?? ''}`}
               flag={API_HOST + data.homebase_flag}
               size={64}
+              borderColor={Colors.WHITE}
             />
           )}
         </View>
@@ -77,22 +114,8 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
           <Text style={[styles.headerText, { fontSize: getFontSize(18), flex: 0 }]}>
             {data.first_name} {data.last_name}
           </Text>
-          <View
-            style={{
-              display: 'flex',
-              justifyContent: 'space-between',
-              flexDirection: 'row',
-              alignItems: 'center'
-            }}
-          >
-            <View
-              style={{
-                display: 'flex',
-                flexDirection: 'row',
-                gap: 10,
-                alignItems: 'center'
-              }}
-            >
+          <View style={styles.userInfoContainer}>
+            <View style={styles.userInfo}>
               <Text
                 style={{ color: Colors.DARK_BLUE, fontWeight: '600', fontSize: getFontSize(12) }}
               >
@@ -123,6 +146,7 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
           </View>
         </View>
       </View>
+
       <PersonalInfo
         data={{
           bio: data.bio,
@@ -180,7 +204,7 @@ const PersonalInfo: FC<PersonalInfoProps> = ({ data }) => {
     <ScrollView
       showsVerticalScrollIndicator={false}
       contentContainerStyle={{ gap: 20 }}
-      style={{ marginTop: 20 }}
+      style={{ paddingTop: 20 }}
     >
       <InfoItem inline={true} title={'RANKING'}>
         <View style={{ flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' }}>

+ 20 - 1
src/screens/InAppScreens/ProfileScreen/styles.ts

@@ -7,7 +7,6 @@ export const styles = StyleSheet.create({
     display: 'flex',
     flexDirection: 'row',
     alignItems: 'center',
-    marginTop: 20,
     gap: 20
   },
   headerText: {
@@ -16,6 +15,26 @@ export const styles = StyleSheet.create({
     color: Colors.DARK_BLUE,
     fontSize: getFontSize(14)
   },
+  usersMap: { height: 100, width: '100%', borderRadius: 12 },
+  avatar: {
+    borderRadius: 64 / 2,
+    width: 64,
+    height: 64,
+    borderWidth: 2,
+    borderColor: Colors.WHITE
+  },
+  userInfoContainer: {
+    display: 'flex',
+    justifyContent: 'space-between',
+    flexDirection: 'row',
+    alignItems: 'center'
+  },
+  userInfo: {
+    display: 'flex',
+    flexDirection: 'row',
+    gap: 10,
+    alignItems: 'center'
+  },
   rankingItem: {
     width: '23%',
     margin: '1%',

+ 1 - 0
src/types/navigation.ts

@@ -38,4 +38,5 @@ export enum NAVIGATION_PAGES {
   TRIUMPHS = 'inAppTriumphs',
   SERIES_RANKING = 'inAppSeriesRanking',
   SERIES_RANKING_LIST = 'inAppSeriesRankingList',
+  USERS_MAP = 'inAppUsersMap',
 }