Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix useChatMessage to optimistic insert local messages to avoid downl… #252

Merged
merged 6 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/mobile/polyfills/OdinBlob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ class Blob {
}
}

export const getExtensionForMimeType = (mimeType: string | undefined | null) => {
const getExtensionForMimeType = (mimeType: string | undefined | null) => {
if (!mimeType) return 'bin';
return mimeType === 'audio/mpeg'
? 'mp3'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ChannelDefinitionVm } from '../../../hooks/feed/channels/useChannels';
import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
import { useDarkMode } from '../../../hooks/useDarkMode';
import { Backdrop } from '../../ui/Modal/Backdrop';
import { BottomSheetModal, BottomSheetScrollView, BottomSheetView } from '@gorhom/bottom-sheet';
import { BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet';
import { Colors } from '../../../app/Colors';
import { Platform } from 'react-native';
import { OwnerActions } from '../Meta/OwnerAction';
Expand Down
30 changes: 15 additions & 15 deletions packages/mobile/src/components/ui/OdinImage/OdinImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const OdinImage = memo(
if (enableZoom) {
return (
<ZoomableImage
key={`${fileId || globalTransitId}_${fileKey}_${imageData ? '1' : '0'}`}
uri={uri}
imageSize={imageSize}
alt={alt || title}
Expand All @@ -123,21 +124,20 @@ export const OdinImage = memo(
}

return (
<>
<InnerImage
uri={uri}
contentType={imageData?.type || previewThumbnail?.contentType}
style={style}
alt={alt || title}
imageMeta={imageMeta}
imageSize={imageSize}
fit={fit}
blurRadius={!imageData ? 2 : 0}
onPress={onClick}
onLongPress={onLongPress}
gestureRefs={gestureRefs}
/>
</>
<InnerImage
key={`${fileId || globalTransitId}_${fileKey}_${imageData ? '1' : '0'}`}
uri={uri}
contentType={imageData?.type || previewThumbnail?.contentType}
style={style}
alt={alt || title}
imageMeta={imageMeta}
imageSize={imageSize}
fit={fit}
blurRadius={!imageData ? 2 : 0}
onPress={onClick}
onLongPress={onLongPress}
gestureRefs={gestureRefs}
/>
);
}
);
Expand Down
75 changes: 44 additions & 31 deletions packages/mobile/src/components/ui/OdinImage/hooks/useImage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { QueryClient, useQuery, useQueryClient } from '@tanstack/react-query';

import { ImageSize, TargetDrive, ImageContentType, SystemFileType } from '@homebase-id/js-lib/core';
import { exists } from 'react-native-fs';
Expand All @@ -10,12 +10,41 @@ import {
getDecryptedMediaUrlOverPeer,
} from '../../../../provider/image/RNExternalMediaProvider';

interface ImageData {
export interface ImageData {
url: string;
naturalSize?: ImageSize;
type?: ImageContentType;
}

const roundToNearest25 = (value: number) => Math.round(value / 25) * 25;
const queryKeyBuilder = (
odinId: string | undefined,
imageFileId: string | undefined,
imageFileKey: string | undefined,
imageDrive: TargetDrive | undefined,
size?: ImageSize,
lastModified?: number
) => {
const queryKey = [
'image',
odinId || '',
imageDrive?.alias,
imageFileId?.replaceAll('-', ''),
imageFileKey,
];

if (size) {
// We round the size to the nearest 25 to avoid having too many different sizes in cache
queryKey.push(`${roundToNearest25(size.pixelHeight)}x${roundToNearest25(size?.pixelWidth)}`);
}

if (lastModified) {
queryKey.push(lastModified + '');
}

return queryKey;
};

const useImage = (props?: {
odinId?: string;
imageFileId?: string | undefined;
Expand Down Expand Up @@ -45,35 +74,6 @@ const useImage = (props?: {

const localHost = dotYouClient.getIdentity(); // This is the identity of the user

const roundToNearest25 = (value: number) => Math.round(value / 25) * 25;
const queryKeyBuilder = (
odinId: string | undefined,
imageFileId: string | undefined,
imageFileKey: string | undefined,
imageDrive: TargetDrive | undefined,
size?: ImageSize,
lastModified?: number
) => {
const queryKey = [
'image',
odinId || '',
imageDrive?.alias,
imageFileId?.replaceAll('-', ''),
imageFileKey,
];

if (size) {
// We round the size to the nearest 25 to avoid having too many different sizes in cache
queryKey.push(`${roundToNearest25(size.pixelHeight)}x${roundToNearest25(size?.pixelWidth)}`);
}

if (lastModified) {
queryKey.push(lastModified + '');
}

return queryKey;
};

const getCachedImages = (
odinId: string | undefined,
imageFileId: string,
Expand Down Expand Up @@ -367,4 +367,17 @@ const useImage = (props?: {
};
};

export const insertImageIntoCache = (
queryClient: QueryClient,
odinId: string | undefined,
imageFileId: string,
imageFileKey: string,
imageDrive: TargetDrive,
size: ImageSize | undefined,
imageData: ImageData
) => {
const queryKey = queryKeyBuilder(odinId, imageFileId, imageFileKey, imageDrive, size);
queryClient.setQueryData<ImageData>(queryKey, imageData);
};

export default useImage;
138 changes: 94 additions & 44 deletions packages/mobile/src/hooks/chat/useChatMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {

import {
HomebaseFile,
ImageContentType,
NewHomebaseFile,
NewPayloadDescriptor,
RichText,
Expand All @@ -26,6 +27,7 @@ import {
} from '../../provider/chat/ChatProvider';
import { ImageSource } from '../../provider/image/RNImageProvider';
import {
ChatDrive,
ConversationWithYourselfId,
UnifiedConversation,
} from '../../provider/chat/ConversationProvider';
Expand All @@ -34,6 +36,8 @@ import { getSynchronousDotYouClient } from './getSynchronousDotYouClient';
import { useErrors, addError, generateClientError } from '../errors/useErrors';
import { LinkPreview } from '@homebase-id/js-lib/media';
import { insertNewMessage } from './useChatMessages';
import { insertImageIntoCache } from '../../components/ui/OdinImage/hooks/useImage';
import { copyFileIntoCache } from '../../utils/utils';
import { addLogs } from '../../provider/log/logger';

const sendMessage = async ({
Expand Down Expand Up @@ -107,15 +111,15 @@ const sendMessage = async ({
newChat.fileMetadata.appData.uniqueId
)
? ({
...msg,
fileMetadata: {
...msg?.fileMetadata,
payloads: (msg?.fileMetadata.payloads?.map((payload) => ({
...payload,
uploadProgress: { phase, progress },
})) || []) as NewPayloadDescriptor,
},
} as HomebaseFile<ChatMessage>)
...msg,
fileMetadata: {
...msg?.fileMetadata,
payloads: (msg?.fileMetadata.payloads?.map((payload) => ({
...payload,
uploadProgress: { phase, progress },
})) || []) as NewPayloadDescriptor,
},
} as HomebaseFile<ChatMessage>)
: msg
),
})),
Expand All @@ -127,11 +131,22 @@ const sendMessage = async ({
);
};

const pendingFiles = await Promise.all(
(files || [])?.map(async (file) => {
const newFilePath = await copyFileIntoCache(
file.uri || (file.filepath as string),
file.type || undefined
);

return { ...file, filepath: newFilePath, uri: newFilePath };
})
);

const uploadResult = await uploadChatMessage(
dotYouClient,
newChat,
recipients,
files,
pendingFiles,
linkPreviews,
recipients.length > 1
? conversationContent.title
Expand All @@ -148,17 +163,52 @@ const sendMessage = async ({
newChat.fileMetadata.appData.previewThumbnail = uploadResult.previewThumbnail;
newChat.fileMetadata.appData.content.deliveryStatus =
uploadResult.chatDeliveryStatus || ChatDeliveryStatus.Sent;
newChat.fileMetadata.payloads = files?.map((file, index) => ({
key: `chat_mbl${index}`,
contentType: file.type || undefined,
pendingFile:
file.filepath || file.uri
? (new OdinBlob((file.uri || file.filepath) as string, {
type: file.type || undefined,
}) as unknown as Blob)
: undefined,
}));

// Insert images into useImage cache:
const fileMetadataPayloads: NewPayloadDescriptor[] = await Promise.all(
(files || [])?.map(async (file, index) => {
const key = `chat_mbl${index}`;

try {
if (file.type?.startsWith('image/')) {
const cachedImagePath = await copyFileIntoCache(
file.uri || file.filepath || '',
file.type
);

insertImageIntoCache(
queryClient,
undefined,
uploadResult.file.fileId,
key,
ChatDrive,
undefined,
{
url: cachedImagePath,
type: (file.type as ImageContentType) || undefined,
naturalSize: {
pixelWidth: file.width,
pixelHeight: file.height,
},
}
);
}
} catch {}

return {
key,
contentType: file.type || undefined,
pendingFile:
file.filepath || file.uri
? (new OdinBlob((file.uri || file.filepath) as string, {
type: file.type || undefined,
}) as unknown as Blob)
: undefined,
};
})
);

newChat.fileMetadata.payloads = fileMetadataPayloads;
return newChat;
};

Expand Down Expand Up @@ -201,11 +251,11 @@ export const getSendChatMessageMutationOptions: (queryClient: QueryClient) => Us
previewThumbnail:
files && files.length === 1
? {
contentType: files[0].type as string,
content: files[0].uri || files[0].filepath || '',
pixelWidth: files[0].width,
pixelHeight: files[0].height,
}
contentType: files[0].type as string,
content: files[0].uri || files[0].filepath || '',
pixelWidth: files[0].width,
pixelHeight: files[0].height,
}
: undefined,
},
payloads: files?.map((file, index) => ({
Expand All @@ -214,8 +264,8 @@ export const getSendChatMessageMutationOptions: (queryClient: QueryClient) => Us
pendingFile:
file.filepath || file.uri
? (new OdinBlob((file.uri || file.filepath) as string, {
type: file.type || undefined,
}) as unknown as Blob)
type: file.type || undefined,
}) as unknown as Blob)
: undefined,
})),
},
Expand Down Expand Up @@ -270,16 +320,16 @@ export const getUpdateChatMessageMutationOptions: (queryClient: QueryClient) =>
},
{
extistingMessages:
| InfiniteData<
{
searchResults: (HomebaseFile<ChatMessage> | null)[];
cursorState: string;
queryTime: number;
includeMetadataHeader: boolean;
},
unknown
>
| undefined;
| InfiniteData<
{
searchResults: (HomebaseFile<ChatMessage> | null)[];
cursorState: string;
queryTime: number;
includeMetadataHeader: boolean;
},
unknown
>
| undefined;
existingMessage: HomebaseFile<ChatMessage> | undefined;
}
> = (queryClient) => ({
Expand Down Expand Up @@ -354,13 +404,13 @@ export const useChatMessage = (props?: {
const getMessageByUniqueId = async (conversationId: string | undefined, messageId: string) => {
const extistingMessages = conversationId
? queryClient.getQueryData<
InfiniteData<{
searchResults: (HomebaseFile<ChatMessage> | null)[];
cursorState: string;
queryTime: number;
includeMetadataHeader: boolean;
}>
>(['chat-messages', conversationId])
InfiniteData<{
searchResults: (HomebaseFile<ChatMessage> | null)[];
cursorState: string;
queryTime: number;
includeMetadataHeader: boolean;
}>
>(['chat-messages', conversationId])
: undefined;

if (extistingMessages) {
Expand Down
7 changes: 3 additions & 4 deletions packages/mobile/src/provider/image/RNThumbnailProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { Platform } from 'react-native';
import { CachesDirectoryPath, copyFile, readFile, stat, unlink } from 'react-native-fs';
import ImageResizer, { ResizeFormat } from '@bam.tech/react-native-image-resizer';
import { ImageSource } from './RNImageProvider';
import { getExtensionForMimeType, OdinBlob } from '../../../polyfills/OdinBlob';
import { isBase64ImageURI } from '../../utils/utils';
import { OdinBlob } from '../../../polyfills/OdinBlob';
import { isBase64ImageURI, getExtensionForMimeType } from '../../utils/utils';

export const baseThumbSizes: ThumbnailInstruction[] = [
{ quality: 75, width: 250, height: 250 },
Expand Down Expand Up @@ -51,8 +51,7 @@ export const createThumbnails = async (
additionalThumbnails: ThumbnailFile[];
}> => {
if (!photo.filepath && !photo.uri) throw new Error('No filepath found in image source');
let copyOfSourcePath = photo.filepath || photo.uri as string;

let copyOfSourcePath = photo.filepath || (photo.uri as string);

if (!isBase64ImageURI(copyOfSourcePath)) {
// We take a copy of the file, as it can be a virtual file that is not accessible by the native code; Eg: ImageResizer
Expand Down
Loading
Loading