Skip to content
Open
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
13 changes: 13 additions & 0 deletions apps/mobile/src/lib/composerFiles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,9 @@ describe("composer file attachments", () => {
header.writeUInt32BE(3000, 20);
const original = header.toString("base64");
mocks.readBase64.mockResolvedValue(original);
const readBytes = vi.fn().mockResolvedValueOnce(header).mockResolvedValue(new Uint8Array());
const close = vi.fn();
mocks.open.mockReturnValue({ readBytes, close });
const resize = vi.fn();
const renderAsync = vi.fn(async () => ({
width: 4000,
Expand Down Expand Up @@ -620,6 +623,16 @@ describe("composer file attachments", () => {
const result = await pickComposerFiles({ existingCount: 0 });

expect(resize).toHaveBeenCalledWith({ width: 2000, height: 1500 });
expect(mocks.readBase64).toHaveBeenCalledTimes(oversized ? 1 : 0);
expect(readBytes.mock.calls.map((call) => call[0])).toEqual([
256 * 1024,
256 * 1024 - header.length,
]);
expect(close).toHaveBeenCalledExactlyOnceWith();
expect(mocks.delete.mock.calls.map(([uri]) => uri)).toEqual([
...(oversized ? Array.from({ length: 3 }, () => "file:///cache/fitted.png") : []),
"file:///documents/t3-composer-attachments/attachment-id-photo.png",
]);
expect(result).toEqual({
files: [
expect.objectContaining({
Expand Down
48 changes: 48 additions & 0 deletions apps/mobile/src/lib/composerImageFit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,51 @@ export async function fitComposerImage(image: {
return null;
}
}

async function readFileImageDimensions(uri: string) {
const { File, FileMode } = await import("expo-file-system");
const reader = new File(uri).open(FileMode.ReadOnly);
try {
const header = new Uint8Array(IMAGE_DIMENSIONS_HEADER_BYTES);
let length = 0;
while (length < header.length) {
const chunk = await reader.readBytes(header.length - length);
if (chunk.byteLength === 0) break;
header.set(chunk, length);
length += chunk.byteLength;
}
return readImageDimensions(header.subarray(0, length));
} finally {
reader.close();
}
}

/** File imports only need original base64 when it will be kept or a header read is unsupported. */
export async function fitComposerImageFile(image: {
readonly uri: string;
readonly mimeType: string;
}): Promise<{ base64: string; uri: string; fitted: boolean }> {
const { File } = await import("expo-file-system");
const mimeType = downscalableImageMimeType(image.mimeType);
// Some content providers allow a full read but do not expose file handles.
const dimensions =
mimeType === null ? null : await readFileImageDimensions(image.uri).catch(() => null);
if (dimensions === null) {
const base64 = await new File(image.uri).base64();
const fitted = await fitComposerImage({ ...image, base64 });
return fitted === null
? { base64, uri: image.uri, fitted: false }
: { ...fitted, fitted: true };
}
if (fitImageAttachmentSize(dimensions) !== null) {
try {
return {
...(await renderPhoto(image.uri, mimeType === "image/png" ? "png" : "jpeg")),
fitted: true,
};
} catch (error) {
console.warn("[composer-images] could not scale an oversized image", error);
}
}
return { base64: await new File(image.uri).base64(), uri: image.uri, fitted: false };
}
Loading
Loading