···66 copyAsync,
77 deleteAsync,
88 EncodingType,
99+ getInfoAsync,
910 makeDirectoryAsync,
1011 StorageAccessFramework,
1112 writeAsStringAsync,
1213} from 'expo-file-system'
1414+import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
1315import * as MediaLibrary from 'expo-media-library'
1416import * as Sharing from 'expo-sharing'
1515-import ImageResizer from '@bam.tech/react-native-image-resizer'
1617import {Buffer} from 'buffer'
1718import RNFetchBlob from 'rn-fetch-blob'
18192020+import {POST_IMG_MAX} from '#/lib/constants'
1921import {logger} from '#/logger'
2020-import {isAndroid, isIOS} from 'platform/detection'
2222+import {isAndroid, isIOS} from '#/platform/detection'
2123import {Dimensions} from './types'
22242325export async function compressIfNeeded(
···165167}
166168167169async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
170170+ // We need to get the dimensions of the image before we resize it. Previously, the library we used allowed us to enter
171171+ // a "max size", and it would do the "best possible size" calculation for us.
172172+ // Now instead, we have to supply the final dimensions to the manipulation function instead.
173173+ // Performing an "empty" manipulation lets us get the dimensions of the original image. React Native's Image.getSize()
174174+ // does not work for local files...
175175+ const imageRes = await manipulateAsync(localUri, [], {})
176176+ const newDimensions = getResizedDimensions({
177177+ width: imageRes.width,
178178+ height: imageRes.height,
179179+ })
180180+168181 for (let i = 0; i < 9; i++) {
169169- const quality = 100 - i * 10
170170- const resizeRes = await ImageResizer.createResizedImage(
182182+ // nearest 10th
183183+ const quality = Math.round((1 - 0.1 * i) * 10) / 10
184184+ const resizeRes = await manipulateAsync(
171185 localUri,
172172- opts.width,
173173- opts.height,
174174- 'JPEG',
175175- quality,
176176- undefined,
177177- undefined,
178178- undefined,
179179- {mode: opts.mode},
186186+ [{resize: newDimensions}],
187187+ {
188188+ format: SaveFormat.JPEG,
189189+ compress: quality,
190190+ },
180191 )
181181- if (resizeRes.size < opts.maxSize) {
192192+193193+ const fileInfo = await getInfoAsync(resizeRes.uri)
194194+ if (!fileInfo.exists) {
195195+ throw new Error(
196196+ 'The image manipulation library failed to create a new image.',
197197+ )
198198+ }
199199+200200+ if (fileInfo.size < opts.maxSize) {
201201+ safeDeleteAsync(imageRes.uri)
182202 return {
183183- path: normalizePath(resizeRes.path),
203203+ path: normalizePath(resizeRes.uri),
184204 mime: 'image/jpeg',
185185- size: resizeRes.size,
205205+ size: fileInfo.size,
186206 width: resizeRes.width,
187207 height: resizeRes.height,
188208 }
189209 } else {
190190- safeDeleteAsync(resizeRes.path)
210210+ safeDeleteAsync(resizeRes.uri)
191211 }
192212 }
193213 throw new Error(
···311331 safeDeleteAsync(tmpDirUri)
312332 }
313333}
334334+335335+export function getResizedDimensions(originalDims: {
336336+ width: number
337337+ height: number
338338+}) {
339339+ if (
340340+ originalDims.width <= POST_IMG_MAX.width &&
341341+ originalDims.height <= POST_IMG_MAX.height
342342+ ) {
343343+ return originalDims
344344+ }
345345+346346+ const ratio = Math.min(
347347+ POST_IMG_MAX.width / originalDims.width,
348348+ POST_IMG_MAX.height / originalDims.height,
349349+ )
350350+351351+ return {
352352+ width: Math.round(originalDims.width * ratio),
353353+ height: Math.round(originalDims.height * ratio),
354354+ }
355355+}
+13-13
src/view/com/composer/useExternalLinkFetch.ts
···22import {msg} from '@lingui/macro'
33import {useLingui} from '@lingui/react'
4455-import {logger} from '#/logger'
66-import {createComposerImage} from '#/state/gallery'
77-import {useFetchDid} from '#/state/queries/handle'
88-import {useGetPost} from '#/state/queries/post'
99-import {useAgent} from '#/state/session'
1010-import * as apilib from 'lib/api/index'
1111-import {POST_IMG_MAX} from 'lib/constants'
55+import * as apilib from '#/lib/api/index'
66+import {POST_IMG_MAX} from '#/lib/constants'
127import {
138 EmbeddingDisabledError,
149 getFeedAsEmbed,
1510 getListAsEmbed,
1611 getPostAsQuote,
1712 getStarterPackAsEmbed,
1818-} from 'lib/link-meta/bsky'
1919-import {getLinkMeta} from 'lib/link-meta/link-meta'
2020-import {resolveShortLink} from 'lib/link-meta/resolve-short-link'
2121-import {downloadAndResize} from 'lib/media/manip'
1313+} from '#/lib/link-meta/bsky'
1414+import {getLinkMeta} from '#/lib/link-meta/link-meta'
1515+import {resolveShortLink} from '#/lib/link-meta/resolve-short-link'
1616+import {downloadAndResize} from '#/lib/media/manip'
2217import {
2318 isBskyCustomFeedUrl,
2419 isBskyListUrl,
···2621 isBskyStarterPackUrl,
2722 isBskyStartUrl,
2823 isShortLink,
2929-} from 'lib/strings/url-helpers'
3030-import {ComposerOpts} from 'state/shell/composer'
2424+} from '#/lib/strings/url-helpers'
2525+import {logger} from '#/logger'
2626+import {createComposerImage} from '#/state/gallery'
2727+import {useFetchDid} from '#/state/queries/handle'
2828+import {useGetPost} from '#/state/queries/post'
2929+import {useAgent} from '#/state/session'
3030+import {ComposerOpts} from '#/state/shell/composer'
31313232export function useExternalLinkFetch({
3333 setQuote,