Просмотр исходного кода

android location fixes + change email func

Viktoriia 4 месяцев назад
Родитель
Сommit
f31066d9b6

+ 1 - 0
src/modules/api/user/queries/index.ts

@@ -9,3 +9,4 @@ export * from './use-post-get-map-years';
 export * from './use-post-save-notification-token';
 export * from './use-post-get-update';
 export * from './use-post-authenticate';
+export * from './use-post-update-email';

+ 25 - 0
src/modules/api/user/queries/use-post-update-email.tsx

@@ -0,0 +1,25 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { userQueryKeys } from '../user-query-keys';
+import { userApi } from '../user-api';
+import { ResponseType } from '@api/response-type';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostUpdateEmailMutation = () => {
+  return useMutation<
+    ResponseType,
+    BaseAxiosError,
+    {
+      token: string;
+      email: string;
+    },
+    ResponseType
+  >({
+    mutationKey: userQueryKeys.updateEmail(),
+    mutationFn: async (variables) => {
+      const response = await userApi.updateEmail(variables.token, variables.email);
+      return response.data;
+    }
+  });
+};

+ 3 - 1
src/modules/api/user/user-api.tsx

@@ -409,5 +409,7 @@ export const userApi = {
   getUpdate: <T extends string>(token: string, profile_id: number, type: T) =>
     request.postForm<PostGetUpdateReturn<T>>(API.GET_UPDATE, { token, profile_id, type }),
   autenticate: (token: string, profile_id: number) =>
-    request.postForm<ResponseType>(API.AUTHENTICATE, { token, profile_id })
+    request.postForm<ResponseType>(API.AUTHENTICATE, { token, profile_id }),
+  updateEmail: (token: string, email: string) =>
+    request.postForm<ResponseType>(API.UPDATE_EMAIL, { token, email })
 };

+ 2 - 1
src/modules/api/user/user-query-keys.tsx

@@ -9,5 +9,6 @@ export const userQueryKeys = {
   getMapYears: (userId: number) => ['getMapYears', userId] as const,
   setNotificationToken: () => ['setNotificationToken'] as const,
   getUpdate: (userId: number, type: string) => ['getUpdate', userId, type] as const,
-  autenticate: () => ['autenticate'] as const
+  autenticate: () => ['autenticate'] as const,
+  updateEmail: () => ['updateEmail'] as const,
 };

+ 11 - 9
src/screens/InAppScreens/MessagesScreen/ChatScreen/index.tsx

@@ -259,12 +259,8 @@ const ChatScreen = ({ route }: { route: any }) => {
     [replyMessage]
   );
 
-  const toBase64 = (str: string) => {
-    return global.btoa(unescape(encodeURIComponent(str)));
-  };
-
   const onSendLocation = useCallback(
-    (coords: { latitude: number; longitude: number }) => {
+    async (coords: { latitude: number; longitude: number }) => {
       const tempMessage: CustomMessage = {
         _id: Date.now() + Math.random(),
         text: '',
@@ -293,9 +289,11 @@ const ChatScreen = ({ route }: { route: any }) => {
       setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [tempMessage]));
 
       const locationData = JSON.stringify({ lat: coords.latitude, lng: coords.longitude });
+      const fileUri = FileSystem.documentDirectory + 'location.json';
+      await FileSystem.writeAsStringAsync(fileUri, locationData);
 
       const locationFile = {
-        uri: `data:application/json;base64,${toBase64(locationData)}`,
+        uri: fileUri,
         type: 'application/json',
         name: 'location.json'
       };
@@ -309,7 +307,7 @@ const ChatScreen = ({ route }: { route: any }) => {
       };
 
       sendMessage(messageData, {
-        onSuccess: (res) => {
+        onSuccess: async (res) => {
           const { attachment, message_id } = res;
 
           const newMessage = {
@@ -326,6 +324,10 @@ const ChatScreen = ({ route }: { route: any }) => {
           );
 
           sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
+          await FileSystem.deleteAsync(fileUri);
+        },
+        onError: async (err) => {
+          await FileSystem.deleteAsync(fileUri);
         }
       });
 
@@ -1254,7 +1256,7 @@ const ChatScreen = ({ route }: { route: any }) => {
                     !selectedMessage.currentMessage.image &&
                     !selectedMessage.currentMessage.video
                   ? renderMessageFile(selectedMessage)
-                  : null
+                  : renderReplyMessageView(selectedMessage)
             }
           />
         </ScrollView>
@@ -1592,7 +1594,7 @@ const ChatScreen = ({ route }: { route: any }) => {
               ? renderMessageLocation(props)
               : currentMessage.attachment && !currentMessage.image && !currentMessage.video
                 ? renderMessageFile(props)
-                : null
+                : renderReplyMessageView(props)
           }
         />
       </View>

+ 2 - 2
src/screens/InAppScreens/MessagesScreen/Components/AttachmentsModal.tsx

@@ -185,10 +185,10 @@ const AttachmentsModal = () => {
             <Text style={styles.optionLabel}>Location</Text>
           </TouchableOpacity>
 
-          <TouchableOpacity style={styles.optionItem} onPress={handleShareLiveLocation}>
+          {/* <TouchableOpacity style={styles.optionItem} onPress={handleShareLiveLocation}>
             <MaterialCommunityIcons name="navigation" size={36} color={Colors.ORANGE} />
             <Text style={styles.optionLabel}>Live</Text>
-          </TouchableOpacity>
+          </TouchableOpacity> */}
 
           <TouchableOpacity style={styles.optionItem} onPress={handleSendFile}>
             <MaterialCommunityIcons name="file" size={36} color={Colors.ORANGE} />

+ 8 - 3
src/screens/InAppScreens/MessagesScreen/Components/ReplyMessageBar.tsx

@@ -2,12 +2,12 @@ import React from 'react';
 import { Text, StyleSheet, View, TouchableOpacity } from 'react-native';
 import { MaterialCommunityIcons } from '@expo/vector-icons';
 import { Colors } from 'src/theme';
-import { IMessage } from 'react-native-gifted-chat';
 import Animated, { FadeInDown, FadeOutDown } from 'react-native-reanimated';
+import { CustomMessage } from '../types';
 
 type ReplyMessageBarProps = {
   clearReply: () => void;
-  message: IMessage | null;
+  message: CustomMessage | null;
 };
 
 const replyMessageBarHeight = 50;
@@ -15,6 +15,11 @@ const replyMessageBarHeight = 50;
 const ReplyMessageBar = ({ clearReply, message }: ReplyMessageBarProps) => {
   if (!message) return null;
 
+  const text =
+    message?.attachment && message?.attachment?.filename
+      ? message.attachment.filename
+      : message.text;
+
   return (
     <Animated.View
       entering={FadeInDown}
@@ -49,7 +54,7 @@ const ReplyMessageBar = ({ clearReply, message }: ReplyMessageBarProps) => {
           {message.user.name}
         </Text>
         <Text style={{ color: Colors.DARK_BLUE, paddingLeft: 10, paddingTop: 5 }} numberOfLines={1}>
-          {message.text}
+          {text}
         </Text>
       </View>
 

+ 35 - 24
src/screens/InAppScreens/MessagesScreen/Components/RouteB.tsx

@@ -29,6 +29,7 @@ const RouteB = () => {
   } | null>(null);
   const [loading, setLoading] = useState(true);
   const cameraRef = useRef<MapLibreRN.CameraRef | null>(null);
+  const [mapDimensions, setMapDimensions] = useState({ width: 0, height: 0, x: 0, y: 0 });
 
   useEffect(() => {
     const getLocation = async () => {
@@ -98,28 +99,41 @@ const RouteB = () => {
 
   return (
     <View style={[styles.container, { paddingBottom: 8 + insetsBottom }]}>
-      <MapLibreRN.MapView
-        style={{ flex: 1 }}
-        mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps.json'}
-        compassEnabled={false}
-        rotateEnabled={false}
-        attributionEnabled={false}
-        onRegionDidChange={handleRegionChange}
+      <View
+        style={styles.mapContainer}
+        onLayout={(event) => {
+          const { x, y, width, height } = event.nativeEvent.layout;
+          setMapDimensions({ x, y, width, height });
+        }}
       >
-        <MapLibreRN.Camera ref={cameraRef} />
-        {currentLocation && (
-          <MapLibreRN.PointAnnotation
-            id="currentLocation"
-            coordinate={[currentLocation.longitude, currentLocation.latitude]}
-          >
-            <View style={styles.currentLocationMarker} />
-          </MapLibreRN.PointAnnotation>
-        )}
+        <MapLibreRN.MapView
+          style={{ flex: 1 }}
+          mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps.json'}
+          compassEnabled={false}
+          rotateEnabled={false}
+          attributionEnabled={false}
+          onRegionDidChange={handleRegionChange}
+        >
+          <MapLibreRN.Camera ref={cameraRef} />
+          {currentLocation && (
+            <MapLibreRN.PointAnnotation
+              id="currentLocation"
+              coordinate={[currentLocation.longitude, currentLocation.latitude]}
+            >
+              <View style={styles.currentLocationMarker} />
+            </MapLibreRN.PointAnnotation>
+          )}
+        </MapLibreRN.MapView>
+      </View>
 
-        <View style={styles.centerMarker}>
-          <View style={styles.selectedLocationMarker} />
-        </View>
-      </MapLibreRN.MapView>
+      <View
+        style={[
+          styles.centerMarker,
+          { left: mapDimensions.width / 2 - 12, top: mapDimensions.height / 2 - 12 }
+        ]}
+      >
+        <View style={styles.selectedLocationMarker} />
+      </View>
 
       <View style={styles.mapActions}>
         <Button
@@ -147,6 +161,7 @@ const styles = StyleSheet.create({
     minHeight: 600,
     gap: 16
   },
+  mapContainer: { flex: 1 },
   mapActions: {
     flexDirection: 'column',
     gap: 8,
@@ -170,10 +185,6 @@ const styles = StyleSheet.create({
   },
   centerMarker: {
     position: 'absolute',
-    top: '50%',
-    left: '50%',
-    marginLeft: -12,
-    marginTop: -12,
     zIndex: 1000
   }
 });

+ 29 - 30
src/screens/InAppScreens/MessagesScreen/FullMapScreen/index.tsx

@@ -29,43 +29,16 @@ const FullMapScreen = ({ route }: { route: any }) => {
           defaultSettings={{ centerCoordinate: [lng, lat], zoomLevel: 12 }}
         />
         <MapLibreRN.MarkerView coordinate={[lng, lat]}>
-          <View
-            style={{
-              width: 20,
-              height: 20,
-              borderRadius: 10,
-              backgroundColor: Colors.ORANGE,
-              borderWidth: 2,
-              borderColor: Colors.WHITE
-            }}
-          />
+          <View style={styles.marker} />
         </MapLibreRN.MarkerView>
       </MapLibreRN.MapView>
       <TouchableOpacity
         onPress={() => {
           navigation.goBack();
         }}
-        style={{
-          position: 'absolute',
-          width: 50,
-          height: 50,
-          top: 50,
-          left: 10,
-          justifyContent: 'center',
-          alignItems: 'center',
-          zIndex: 2
-        }}
+        style={styles.backButtonContainer}
       >
-        <View
-          style={{
-            width: 42,
-            height: 42,
-            borderRadius: 21,
-            justifyContent: 'center',
-            alignItems: 'center',
-            backgroundColor: 'rgba(0, 0, 0, 0.3)'
-          }}
-        >
+        <View style={styles.backButton}>
           <ChevronLeft fill={Colors.WHITE} />
         </View>
       </TouchableOpacity>
@@ -76,6 +49,32 @@ const FullMapScreen = ({ route }: { route: any }) => {
 const styles = StyleSheet.create({
   map: {
     ...StyleSheet.absoluteFillObject
+  },
+  backButtonContainer: {
+    position: 'absolute',
+    width: 50,
+    height: 50,
+    top: 50,
+    left: 10,
+    justifyContent: 'center',
+    alignItems: 'center',
+    zIndex: 2
+  },
+  backButton: {
+    width: 42,
+    height: 42,
+    borderRadius: 21,
+    justifyContent: 'center',
+    alignItems: 'center',
+    backgroundColor: 'rgba(0, 0, 0, 0.3)'
+  },
+  marker: {
+    width: 20,
+    height: 20,
+    borderRadius: 10,
+    backgroundColor: Colors.ORANGE,
+    borderWidth: 2,
+    borderColor: Colors.WHITE
   }
 });
 

+ 79 - 4
src/screens/InAppScreens/ProfileScreen/Profile/edit-personal-info.tsx

@@ -1,5 +1,5 @@
 import React, { useState } from 'react';
-import { ScrollView, View } from 'react-native';
+import { ScrollView, View, Text, Linking, StyleSheet } from 'react-native';
 import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
 import { Formik } from 'formik';
 import * as yup from 'yup';
@@ -10,6 +10,7 @@ import { Image } from 'expo-image';
 import {
   usePostGetProfileQuery,
   usePostSetProfileMutation,
+  usePostUpdateEmailMutation,
   userQueryKeys,
   type PostSetProfileData
 } from '@api/user';
@@ -46,6 +47,7 @@ import { useDeleteUserMutation } from '@api/app';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import { useAvatarStore } from 'src/stores/avatarVersionStore';
 import { useFriendsNotificationsStore } from 'src/stores/friendsNotificationsStore';
+import { getFontSize } from 'src/utils';
 
 const ProfileSchema = yup.object({
   username: yup.string().optional(),
@@ -61,7 +63,8 @@ const ProfileSchema = yup.object({
   i: yup.string().optional(),
   y: yup.string().optional(),
   www: yup.string().optional(),
-  other: yup.string().optional()
+  other: yup.string().optional(),
+  new_email: yup.string().email().optional()
 });
 
 export const EditPersonalInfo = () => {
@@ -70,6 +73,7 @@ export const EditPersonalInfo = () => {
   const { mutate: deleteUser } = useDeleteUserMutation();
 
   const { mutate: updateProfile, data: updateResponse, reset } = usePostSetProfileMutation();
+  const { mutate: updateEmail } = usePostUpdateEmailMutation();
 
   const navigation = useNavigation();
   const queryClient = useQueryClient();
@@ -80,6 +84,7 @@ export const EditPersonalInfo = () => {
   );
   const updateUnreadMessagesCount = useMessagesStore((state) => state.updateUnreadMessagesCount);
   const [isSubmitting, setIsSubmitting] = useState(false);
+  const [isNewEmailSubmitted, setIsNewEmailSubmitted] = useState(false);
   const { incrementAvatarVersion } = useAvatarStore();
 
   const regions = useGetRegionsWithFlagQuery(true);
@@ -128,10 +133,26 @@ export const EditPersonalInfo = () => {
     deleteUser({ token }, { onSuccess: handleLogout });
   };
 
+  const handleVerifyEmailChange = async (newEmail: string) => {
+    setIsSubmitting(true);
+    await updateEmail(
+      { token, email: newEmail },
+      {
+        onSuccess: () => {
+          setIsSubmitting(false);
+          setIsNewEmailSubmitted(true);
+        },
+        onError: () => {
+          setIsSubmitting(false);
+        }
+      }
+    );
+  };
+
   return (
     <PageWrapper>
+      <Header label={'Edit Personal Info'} />
       <ScrollView showsVerticalScrollIndicator={false}>
-        <Header label={'Edit Personal Info'} />
         <KeyboardAwareScrollView>
           <Formik
             validationSchema={ProfileSchema}
@@ -154,7 +175,8 @@ export const EditPersonalInfo = () => {
                 type: '',
                 uri: '',
                 name: ''
-              }
+              },
+              new_email: ''
             }}
             onSubmit={async (values) => {
               setIsSubmitting(true);
@@ -244,6 +266,51 @@ export const EditPersonalInfo = () => {
                   onBlur={props.handleBlur('email')}
                   formikError={props.touched.email && props.errors.email}
                 />
+                <Input
+                  editable={!isNewEmailSubmitted}
+                  header={'Change email address'}
+                  placeholder={'new_email@address.com'}
+                  inputMode={'email'}
+                  onChange={props.handleChange('new_email')}
+                  value={props.values.new_email}
+                  onBlur={props.handleBlur('new_email')}
+                  formikError={props.touched.new_email && props.errors.new_email}
+                />
+                {!props.errors.new_email && props.values.new_email && !isNewEmailSubmitted ? (
+                  <Button
+                    onPress={() => handleVerifyEmailChange(props.values.new_email)}
+                    disabled={isSubmitting}
+                    variant={ButtonVariants.FILL}
+                  >
+                    Verify email change
+                  </Button>
+                ) : (
+                  <Button onPress={() => {}} disabled={true} variant={ButtonVariants.OPACITY}>
+                    Verify email change
+                  </Button>
+                )}
+
+                {isNewEmailSubmitted ? (
+                  <Text style={styles.text}>
+                    Confirmation message sent. Please check your old email inbox.
+                  </Text>
+                ) : (
+                  <Text style={styles.text}>
+                    We will send a confirmation message to your old email address. Once you click
+                    the link in that message, your new email will become active. If you don't have
+                    access to your old email mailbox, please contact us{' '}
+                    <Text
+                      style={{
+                        color: Colors.ORANGE
+                      }}
+                      onPress={() => Linking.openURL('https://nomadmania.com/contact/')}
+                    >
+                      here
+                    </Text>
+                    .
+                  </Text>
+                )}
+
                 <BigText>General Info</BigText>
                 <Input
                   header={'First name'}
@@ -390,3 +457,11 @@ export const EditPersonalInfo = () => {
     </PageWrapper>
   );
 };
+
+const styles = StyleSheet.create({
+  text: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(12),
+    fontFamily: 'redhat-600'
+  }
+});

+ 2 - 0
src/types/api.ts

@@ -161,6 +161,7 @@ export enum API_ENDPOINT {
   AUTHENTICATE = 'authenticate',
   REPORT_CONVERSATION = 'report-conversation',
   GET_USERS_COUNT = 'get-users-on-map-count',
+  UPDATE_EMAIL = 'update-email',
 }
 
 export enum API {
@@ -295,6 +296,7 @@ export enum API {
   AUTHENTICATE = `${API_ROUTE.USER}/${API_ENDPOINT.AUTHENTICATE}`,
   REPORT_CONVERSATION = `${API_ROUTE.CHAT}/${API_ENDPOINT.REPORT_CONVERSATION}`,
   GET_USERS_COUNT = `${API_ROUTE.LOCATION}/${API_ENDPOINT.GET_USERS_COUNT}`,
+  UPDATE_EMAIL = `${API_ROUTE.USER}/${API_ENDPOINT.UPDATE_EMAIL}`,
 }
 
 export type BaseAxiosError = AxiosError;