Bläddra i källkod

fixes + image compressor

Viktoriia 1 vecka sedan
förälder
incheckning
fcde9b967e

+ 3 - 1
app.config.ts

@@ -8,6 +8,7 @@ const env = process.env;
 
 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;
+const NM_API_HOST = env.NM_API_HOST;
 
 const GOOGLE_MAP_PLACES_APIKEY = env.GOOGLE_MAP_PLACES_APIKEY;
 const WEBSOCKET_URL =
@@ -211,6 +212,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
         isIosBackgroundLocationEnabled: true,
         isAndroidBackgroundLocationEnabled: false
       }
-    ]
+    ],
+    'react-native-compressor'
   ]
 });

+ 3 - 0
package.json

@@ -73,6 +73,7 @@
     "react-native-actions-sheet": "^0.9.7",
     "react-native-animated-pagination-dot": "^0.4.0",
     "react-native-collapsible-tab-view": "^8.0.1",
+    "react-native-compressor": "^1.16.0",
     "react-native-device-detection": "^0.2.1",
     "react-native-emoji-selector": "^0.2.0",
     "react-native-file-viewer": "^2.1.5",
@@ -118,8 +119,10 @@
     "@react-native-community/cli": "latest",
     "@types/react": "~19.1.10",
     "babel-preset-expo": "^54.0.2",
+    "baseline-browser-mapping": "^2.9.7",
     "metro-react-native-babel-transformer": "^0.77.0",
     "prettier": "^3.1.0",
+    "react-dom": "19.1.0",
     "react-native-svg-transformer": "^1.5.0",
     "typescript": "~5.9.2"
   },

+ 104 - 57
src/screens/InAppScreens/MessagesScreen/ChatScreen/index.tsx

@@ -55,7 +55,12 @@ import { SheetManager } from 'react-native-actions-sheet';
 import { NAVIGATION_PAGES } from 'src/types';
 import { usePushNotification } from 'src/contexts/PushNotificationContext';
 import ReactionsListModal from '../Components/ReactionsListModal';
-import { dismissChatNotifications, isMessageEdited } from '../utils';
+import {
+  compressImageWithProgress,
+  compressVideoWithProgress,
+  dismissChatNotifications,
+  isMessageEdited
+} from '../utils';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import FileViewer from 'react-native-file-viewer';
 import * as FileSystem from 'expo-file-system/legacy';
@@ -130,7 +135,8 @@ const ChatScreen = ({ route }: { route: any }) => {
   const {
     data: chatData,
     refetch,
-    isFetching
+    isFetching,
+    isFetched
   } = usePostGetChatWithQuery(token, id, 50, prevThenMessageId, true);
 
   const swipeableRowRef = useRef<Swipeable | null>(null);
@@ -378,22 +384,17 @@ const ChatScreen = ({ route }: { route: any }) => {
 
   const onSendMedia = useCallback(
     async (files: { uri: string; type: 'image' | 'video' }[]) => {
-      for (const file of files) {
-        // if (file.type === 'image') {
-        //   const compressedUri = await compressImage(file.uri);
-        //   file.uri = compressedUri;
-        // }
-
-        const formatedReply = replyMessage
-          ? {
-              text: replyMessage.text,
-              id: replyMessage._id,
-              name: replyMessage.user._id === id ? userName : 'Me',
-              sender: replyMessage.user._id
-            }
-          : null;
+      const formatedReply = replyMessage
+        ? {
+            text: replyMessage.text,
+            id: replyMessage._id,
+            name: replyMessage.user._id === id ? userName : 'Me',
+            sender: replyMessage.user._id
+          }
+        : null;
 
-        await createOptimisticMessage({
+      for (const file of files) {
+        const optimisticId = await createOptimisticMessage({
           chatUid: id,
           currentUserId: +currentUserId,
           uiAttachment: {
@@ -409,12 +410,52 @@ const ChatScreen = ({ route }: { route: any }) => {
             type: file.type,
             name: file.uri.split('/').pop()
           },
-          replyMessage: formatedReply
+          replyMessage: formatedReply,
+          shouldAddDirty: false
         });
+
+        const updateProgress = async (progress: number) => {};
+
+        let compressedUri = file.uri;
+
+        if (file.type === 'image') {
+          compressedUri = await compressImageWithProgress(file.uri, updateProgress);
+        }
+
+        if (file.type === 'video') {
+          compressedUri = await compressVideoWithProgress(file.uri, updateProgress);
+        }
+
+        const optimisticMessage = await findMsgRecord(optimisticId, id);
+
+        if (optimisticMessage) {
+          await database.write(async () => {
+            optimisticMessage.update((m) => {
+              m.attachment = JSON.stringify({
+                ...JSON.parse(m.attachment ?? '{}'),
+                local_uri: compressedUri
+              });
+
+              addMessageDirtyAction(optimisticMessage, {
+                type: 'send',
+                value: {
+                  text,
+                  currentUid: currentUserId,
+                  attachment: {
+                    uri: compressedUri,
+                    type: file.type,
+                    name: file.uri.split('/').pop()
+                  },
+                  reply_to_id: formatedReply ? formatedReply.id : -1,
+                  replyMessage: formatedReply
+                }
+              });
+            });
+          });
+        }
       }
 
       clearReplyMessage();
-
       await triggerMessagePush(token, sendWsEvent);
     },
     [replyMessage]
@@ -1041,7 +1082,10 @@ const ChatScreen = ({ route }: { route: any }) => {
   }, []);
 
   useEffect(() => {
-    if (!chatData?.messages?.length) return;
+    if (!chatData?.messages?.length) {
+      setHasMoreMessages(false);
+      return;
+    }
 
     upsertMessagesIntoDB({ chatUid: id, apiMessages: chatData.messages });
 
@@ -1055,7 +1099,7 @@ const ChatScreen = ({ route }: { route: any }) => {
   }, [chatData]);
 
   useEffect(() => {
-    if (isFetching) return;
+    if (!isFetched || isFetching) return;
     if (
       giftedMessages?.length === 0 &&
       !modalInfo.visible &&
@@ -1065,7 +1109,7 @@ const ChatScreen = ({ route }: { route: any }) => {
         textInputRef.current?.focus();
       }, 500);
     }
-  }, [modalInfo, isFetching]);
+  }, [isFetched]);
 
   const loadEarlierMessages = async () => {
     if (isLoadingEarlier || !hasMoreMessages || !giftedMessages) return;
@@ -1108,47 +1152,50 @@ const ChatScreen = ({ route }: { route: any }) => {
 
   const sentToServer = useRef<Set<number>>(new Set());
 
-  const handleViewableItemsChanged = async ({ viewableItems }: { viewableItems: any[] }) => {
-    const newViewableUnreadMessages = viewableItems
-      .filter(
-        (item) =>
-          !item.item.received &&
-          !item.item.deleted &&
-          !item.item.system &&
-          item.item.user._id === id &&
-          !sentToServer.current.has(item.item._id)
-      )
-      .map((item) => item.item._id);
-
-    if (newViewableUnreadMessages.length > 0) {
-      const messagesToUpdate = await database
-        .get<Message>('messages')
-        .query(
-          Q.where('chat_key', 'u:' + id),
-          Q.where('message_id', Q.oneOf(newViewableUnreadMessages))
+  const handleViewableItemsChanged = _.throttle(
+    async ({ viewableItems }: { viewableItems: any[] }) => {
+      const newViewableUnreadMessages = viewableItems
+        .filter(
+          (item) =>
+            !item.item.received &&
+            !item.item.deleted &&
+            !item.item.system &&
+            item.item.user._id === id &&
+            !sentToServer.current.has(item.item._id)
         )
-        .fetch();
-
-      if (messagesToUpdate.length > 0) {
-        await database.write(async () => {
-          messagesToUpdate.forEach((msg: Message) => {
-            msg.update((r) => {
-              r.status = 3;
-              addMessageDirtyAction(r, {
-                type: 'read',
-                value: { messagesIds: [msg.messageId] }
+        .map((item) => item.item._id);
+
+      if (newViewableUnreadMessages.length > 0) {
+        const messagesToUpdate = await database
+          .get<Message>('messages')
+          .query(
+            Q.where('chat_key', 'u:' + id),
+            Q.where('message_id', Q.oneOf(newViewableUnreadMessages))
+          )
+          .fetch();
+
+        if (messagesToUpdate.length > 0) {
+          await database.write(async () => {
+            messagesToUpdate.forEach((msg: Message) => {
+              msg.update((r) => {
+                r.status = 3;
+                addMessageDirtyAction(r, {
+                  type: 'read',
+                  value: { messagesIds: [msg.messageId] }
+                });
               });
+              sentToServer.current.add(msg.messageId as number);
             });
-            sentToServer.current.add(msg.messageId as number);
           });
-        });
 
-        await triggerMessagePush(token, sendWsEvent);
-      }
+          await triggerMessagePush(token, sendWsEvent);
+        }
 
-      sendWebSocketMessage('messages_read', null, null, newViewableUnreadMessages);
-    }
-  };
+        sendWebSocketMessage('messages_read', null, null, newViewableUnreadMessages);
+      }
+    },
+    1000
+  );
 
   const renderSystemMessage = (props: any) => {
     if (props.currentMessage._id === 'unreadMarker') {

+ 1 - 1
src/screens/InAppScreens/MessagesScreen/ChatScreen/styles.tsx

@@ -97,7 +97,7 @@ export const styles = StyleSheet.create({
     borderColor: Colors.FILL_LIGHT
   },
   emptyChat: {
-    height: '95%',
+    height: 80,
     alignItems: 'center',
     justifyContent: 'center',
     transform: Platform.OS === 'ios' ? [{ scaleY: -1 }] : [{ scaleY: -1 }, { scaleX: -1 }]

+ 11 - 1
src/screens/InAppScreens/MessagesScreen/Components/RenderMessageImage.tsx

@@ -67,10 +67,20 @@ const RenderMessageImage = ({
   return (
     <TouchableOpacity
       onPress={() => {
-        if (!currentMessage.attachment.attachment_full_url?.startsWith('/')) {
+        if (
+          currentMessage.attachment.attachment_full_url &&
+          !currentMessage.attachment.attachment_full_url?.startsWith('/')
+        ) {
           setSelectedMedia(currentMessage.attachment.attachment_full_url);
           return;
         }
+        if (
+          !currentMessage.attachment.attachment_full_url?.startsWith('/') &&
+          currentMessage.attachment?.local_uri
+        ) {
+          setSelectedMedia(currentMessage.attachment.local_uri);
+          return;
+        }
         handleOpenImage(
           API_HOST + currentMessage.attachment.attachment_full_url,
           currentMessage.attachment?.filename

+ 91 - 42
src/screens/InAppScreens/MessagesScreen/GroupChatScreen/index.tsx

@@ -66,7 +66,12 @@ import { SheetManager } from 'react-native-actions-sheet';
 import { NAVIGATION_PAGES } from 'src/types';
 import { usePushNotification } from 'src/contexts/PushNotificationContext';
 import ReactionsListModal from '../Components/ReactionsListModal';
-import { dismissChatNotifications, isMessageEdited } from '../utils';
+import {
+  compressImageWithProgress,
+  compressVideoWithProgress,
+  dismissChatNotifications,
+  isMessageEdited
+} from '../utils';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import FileViewer from 'react-native-file-viewer';
 import * as FileSystem from 'expo-file-system/legacy';
@@ -147,7 +152,8 @@ const GroupChatScreen = ({ route }: { route: any }) => {
   const {
     data: chatData,
     refetch: refetch,
-    isFetching: isFetching
+    isFetching: isFetching,
+    isFetched
   } = usePostGetGroupChatQuery(token, group_token, 50, prevThenMessageId, true);
   const [canSeeMembers, setCanSeeMembers] = useState(false);
 
@@ -385,7 +391,7 @@ const GroupChatScreen = ({ route }: { route: any }) => {
         : null;
 
       for (const file of files) {
-        await createOptimisticMessage({
+        const optimisticId = await createOptimisticMessage({
           groupToken: group_token,
           currentUserId: +currentUserId,
           uiAttachment: {
@@ -401,12 +407,52 @@ const GroupChatScreen = ({ route }: { route: any }) => {
             type: file.type,
             name: file.uri.split('/').pop()
           },
-          replyMessage: formatedReply
+          replyMessage: formatedReply,
+          shouldAddDirty: false
         });
+
+        const updateProgress = async (progress: number) => {};
+
+        let compressedUri = file.uri;
+
+        if (file.type === 'image') {
+          compressedUri = await compressImageWithProgress(file.uri, updateProgress);
+        }
+
+        if (file.type === 'video') {
+          compressedUri = await compressVideoWithProgress(file.uri, updateProgress);
+        }
+
+        const optimisticMessage = await findGroupMsgRecord(optimisticId, group_token);
+
+        if (optimisticMessage) {
+          await database.write(async () => {
+            optimisticMessage.update((m) => {
+              m.attachment = JSON.stringify({
+                ...JSON.parse(m.attachment ?? '{}'),
+                local_uri: compressedUri
+              });
+
+              addMessageDirtyAction(optimisticMessage, {
+                type: 'send',
+                value: {
+                  text,
+                  currentUid: currentUserId,
+                  attachment: {
+                    uri: compressedUri,
+                    type: file.type,
+                    name: file.uri.split('/').pop()
+                  },
+                  reply_to_id: formatedReply ? formatedReply.id : -1,
+                  replyMessage: formatedReply
+                }
+              });
+            });
+          });
+        }
       }
 
       clearReplyMessage();
-
       await triggerMessagePush(token, sendWsEvent);
     },
     [replyMessage]
@@ -1076,13 +1122,13 @@ const GroupChatScreen = ({ route }: { route: any }) => {
   }, [giftedMessages]);
 
   useEffect(() => {
-    if (isFetching) return;
+    if (!isFetched || isFetching) return;
     if (giftedMessages?.length === 0 && !modalInfo.visible) {
       setTimeout(() => {
         textInputRef.current?.focus();
       }, 500);
     }
-  }, [modalInfo, isFetching]);
+  }, [isFetched]);
 
   const loadEarlierMessages = async () => {
     if ((isLoadingEarlier && !isSearchingMessage) || !hasMoreMessages || !giftedMessages) return;
@@ -1125,47 +1171,50 @@ const GroupChatScreen = ({ route }: { route: any }) => {
 
   const sentToServer = useRef<Set<number>>(new Set());
 
-  const handleViewableItemsChanged = async ({ viewableItems }: { viewableItems: any[] }) => {
-    const newViewableUnreadMessages = viewableItems
-      .filter(
-        (item) =>
-          !item.item.received &&
-          !item.item.deleted &&
-          item.item._id !== 'unreadMarker' &&
-          item.item.user._id !== +currentUserId &&
-          !sentToServer.current.has(item.item._id)
-      )
-      .map((item) => item.item._id);
-
-    if (newViewableUnreadMessages.length > 0) {
-      const messagesToUpdate = await database
-        .get<Message>('messages')
-        .query(
-          Q.where('chat_key', 'g:' + group_token),
-          Q.where('message_id', Q.oneOf(newViewableUnreadMessages))
+  const handleViewableItemsChanged = _.throttle(
+    async ({ viewableItems }: { viewableItems: any[] }) => {
+      const newViewableUnreadMessages = viewableItems
+        .filter(
+          (item) =>
+            !item.item.received &&
+            !item.item.deleted &&
+            item.item._id !== 'unreadMarker' &&
+            item.item.user._id !== +currentUserId &&
+            !sentToServer.current.has(item.item._id)
         )
-        .fetch();
-
-      if (messagesToUpdate.length > 0) {
-        await database.write(async () => {
-          messagesToUpdate.forEach((msg: Message) => {
-            msg.update((r) => {
-              r.status = 3;
-              addMessageDirtyAction(r, {
-                type: 'read',
-                value: { messagesIds: [msg.messageId] }
+        .map((item) => item.item._id);
+
+      if (newViewableUnreadMessages.length > 0) {
+        const messagesToUpdate = await database
+          .get<Message>('messages')
+          .query(
+            Q.where('chat_key', 'g:' + group_token),
+            Q.where('message_id', Q.oneOf(newViewableUnreadMessages))
+          )
+          .fetch();
+
+        if (messagesToUpdate.length > 0) {
+          await database.write(async () => {
+            messagesToUpdate.forEach((msg: Message) => {
+              msg.update((r) => {
+                r.status = 3;
+                addMessageDirtyAction(r, {
+                  type: 'read',
+                  value: { messagesIds: [msg.messageId] }
+                });
               });
+              sentToServer.current.add(msg.messageId as number);
             });
-            sentToServer.current.add(msg.messageId as number);
           });
-        });
 
-        await triggerMessagePush(token, sendWsEvent);
-      }
+          await triggerMessagePush(token, sendWsEvent);
+        }
 
-      sendWebSocketMessage('messages_read', null, null, newViewableUnreadMessages);
-    }
-  };
+        sendWebSocketMessage('messages_read', null, null, newViewableUnreadMessages);
+      }
+    },
+    1000
+  );
 
   const renderSystemMessage = (props: any) => {
     if (props.currentMessage._id === 'unreadMarker') {

+ 23 - 0
src/screens/InAppScreens/MessagesScreen/utils.ts

@@ -5,6 +5,7 @@ import { NAVIGATION_PAGES } from 'src/types';
 import { usePushNotification } from 'src/contexts/PushNotificationContext';
 import { CommonActions } from '@react-navigation/native';
 import { storage, StoreType } from 'src/storage';
+import { Image as ImageCompressor, Video as VideoCompressor } from 'react-native-compressor';
 
 export const formatDate = (dateString: Date): string => {
   const inputDate = moment.utc(dateString).local();
@@ -136,3 +137,25 @@ export const isMessageEdited = (edits: string) => {
     return false;
   }
 };
+
+export const compressImageWithProgress = (
+  uri: string,
+  onProgress: (p: number) => void
+): Promise<string> => {
+  return ImageCompressor.compress(uri, {
+    compressionMethod: 'auto',
+    progressDivider: 10,
+    downloadProgress: (progress) => {
+      onProgress(progress);
+    }
+  });
+};
+
+export const compressVideoWithProgress = (
+  uri: string,
+  onProgress: (p: number) => void
+): Promise<string> => {
+  return VideoCompressor.compress(uri, {}, (progress) => {
+    onProgress(progress);
+  });
+};

+ 15 - 11
src/watermelondb/features/chat/data/createOptimisticMessage.ts

@@ -11,6 +11,7 @@ type CreateOptimisticParams = {
   uiAttachment?: any | null;
   sendAttachment?: any | null;
   replyMessage?: any | null;
+  shouldAddDirty?: boolean;
 };
 
 export async function createOptimisticMessage({
@@ -20,7 +21,8 @@ export async function createOptimisticMessage({
   text = '',
   uiAttachment = null,
   sendAttachment = null,
-  replyMessage = null
+  replyMessage = null,
+  shouldAddDirty = true
 }: CreateOptimisticParams) {
   const tempId = Date.now() * -1;
   const chatKey = makeChatKey({ chatUid, groupChatToken: groupToken });
@@ -64,16 +66,18 @@ export async function createOptimisticMessage({
           r.replyTo = null;
         }
 
-        addMessageDirtyAction(r, {
-          type: 'send',
-          value: {
-            text,
-            currentUid: currentUserId,
-            attachment: sendAttachment ?? -1,
-            reply_to_id: replyMessage ? replyMessage.id : -1,
-            replyMessage
-          }
-        });
+        if (shouldAddDirty) {
+          addMessageDirtyAction(r, {
+            type: 'send',
+            value: {
+              text,
+              currentUid: currentUserId,
+              attachment: sendAttachment ?? -1,
+              reply_to_id: replyMessage ? replyMessage.id : -1,
+              replyMessage
+            }
+          });
+        }
       })
     );