···6 copyAsync,
7 deleteAsync,
8 EncodingType,
09 makeDirectoryAsync,
10 StorageAccessFramework,
11 writeAsStringAsync,
12} from 'expo-file-system'
013import * as MediaLibrary from 'expo-media-library'
14import * as Sharing from 'expo-sharing'
15-import ImageResizer from '@bam.tech/react-native-image-resizer'
16import {Buffer} from 'buffer'
17import RNFetchBlob from 'rn-fetch-blob'
18019import {logger} from '#/logger'
20-import {isAndroid, isIOS} from 'platform/detection'
21import {Dimensions} from './types'
2223export async function compressIfNeeded(
···165}
166167async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
00000000000168 for (let i = 0; i < 9; i++) {
169- const quality = 100 - i * 10
170- const resizeRes = await ImageResizer.createResizedImage(
0171 localUri,
172- opts.width,
173- opts.height,
174- 'JPEG',
175- quality,
176- undefined,
177- undefined,
178- undefined,
179- {mode: opts.mode},
180 )
181- if (resizeRes.size < opts.maxSize) {
000000000182 return {
183- path: normalizePath(resizeRes.path),
184 mime: 'image/jpeg',
185- size: resizeRes.size,
186 width: resizeRes.width,
187 height: resizeRes.height,
188 }
189 } else {
190- safeDeleteAsync(resizeRes.path)
191 }
192 }
193 throw new Error(
···311 safeDeleteAsync(tmpDirUri)
312 }
313}
0000000000000000000000
···6 copyAsync,
7 deleteAsync,
8 EncodingType,
9+ getInfoAsync,
10 makeDirectoryAsync,
11 StorageAccessFramework,
12 writeAsStringAsync,
13} from 'expo-file-system'
14+import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
15import * as MediaLibrary from 'expo-media-library'
16import * as Sharing from 'expo-sharing'
017import {Buffer} from 'buffer'
18import RNFetchBlob from 'rn-fetch-blob'
1920+import {POST_IMG_MAX} from '#/lib/constants'
21import {logger} from '#/logger'
22+import {isAndroid, isIOS} from '#/platform/detection'
23import {Dimensions} from './types'
2425export async function compressIfNeeded(
···167}
168169async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
170+ // We need to get the dimensions of the image before we resize it. Previously, the library we used allowed us to enter
171+ // a "max size", and it would do the "best possible size" calculation for us.
172+ // Now instead, we have to supply the final dimensions to the manipulation function instead.
173+ // Performing an "empty" manipulation lets us get the dimensions of the original image. React Native's Image.getSize()
174+ // does not work for local files...
175+ const imageRes = await manipulateAsync(localUri, [], {})
176+ const newDimensions = getResizedDimensions({
177+ width: imageRes.width,
178+ height: imageRes.height,
179+ })
180+181 for (let i = 0; i < 9; i++) {
182+ // nearest 10th
183+ const quality = Math.round((1 - 0.1 * i) * 10) / 10
184+ const resizeRes = await manipulateAsync(
185 localUri,
186+ [{resize: newDimensions}],
187+ {
188+ format: SaveFormat.JPEG,
189+ compress: quality,
190+ },
000191 )
192+193+ const fileInfo = await getInfoAsync(resizeRes.uri)
194+ if (!fileInfo.exists) {
195+ throw new Error(
196+ 'The image manipulation library failed to create a new image.',
197+ )
198+ }
199+200+ if (fileInfo.size < opts.maxSize) {
201+ safeDeleteAsync(imageRes.uri)
202 return {
203+ path: normalizePath(resizeRes.uri),
204 mime: 'image/jpeg',
205+ size: fileInfo.size,
206 width: resizeRes.width,
207 height: resizeRes.height,
208 }
209 } else {
210+ safeDeleteAsync(resizeRes.uri)
211 }
212 }
213 throw new Error(
···331 safeDeleteAsync(tmpDirUri)
332 }
333}
334+335+export function getResizedDimensions(originalDims: {
336+ width: number
337+ height: number
338+}) {
339+ if (
340+ originalDims.width <= POST_IMG_MAX.width &&
341+ originalDims.height <= POST_IMG_MAX.height
342+ ) {
343+ return originalDims
344+ }
345+346+ const ratio = Math.min(
347+ POST_IMG_MAX.width / originalDims.width,
348+ POST_IMG_MAX.height / originalDims.height,
349+ )
350+351+ return {
352+ width: Math.round(originalDims.width * ratio),
353+ height: Math.round(originalDims.height * ratio),
354+ }
355+}
+13-13
src/view/com/composer/useExternalLinkFetch.ts
···2import {msg} from '@lingui/macro'
3import {useLingui} from '@lingui/react'
45-import {logger} from '#/logger'
6-import {createComposerImage} from '#/state/gallery'
7-import {useFetchDid} from '#/state/queries/handle'
8-import {useGetPost} from '#/state/queries/post'
9-import {useAgent} from '#/state/session'
10-import * as apilib from 'lib/api/index'
11-import {POST_IMG_MAX} from 'lib/constants'
12import {
13 EmbeddingDisabledError,
14 getFeedAsEmbed,
15 getListAsEmbed,
16 getPostAsQuote,
17 getStarterPackAsEmbed,
18-} from 'lib/link-meta/bsky'
19-import {getLinkMeta} from 'lib/link-meta/link-meta'
20-import {resolveShortLink} from 'lib/link-meta/resolve-short-link'
21-import {downloadAndResize} from 'lib/media/manip'
22import {
23 isBskyCustomFeedUrl,
24 isBskyListUrl,
···26 isBskyStarterPackUrl,
27 isBskyStartUrl,
28 isShortLink,
29-} from 'lib/strings/url-helpers'
30-import {ComposerOpts} from 'state/shell/composer'
000003132export function useExternalLinkFetch({
33 setQuote,
···2import {msg} from '@lingui/macro'
3import {useLingui} from '@lingui/react'
45+import * as apilib from '#/lib/api/index'
6+import {POST_IMG_MAX} from '#/lib/constants'
000007import {
8 EmbeddingDisabledError,
9 getFeedAsEmbed,
10 getListAsEmbed,
11 getPostAsQuote,
12 getStarterPackAsEmbed,
13+} from '#/lib/link-meta/bsky'
14+import {getLinkMeta} from '#/lib/link-meta/link-meta'
15+import {resolveShortLink} from '#/lib/link-meta/resolve-short-link'
16+import {downloadAndResize} from '#/lib/media/manip'
17import {
18 isBskyCustomFeedUrl,
19 isBskyListUrl,
···21 isBskyStarterPackUrl,
22 isBskyStartUrl,
23 isShortLink,
24+} from '#/lib/strings/url-helpers'
25+import {logger} from '#/logger'
26+import {createComposerImage} from '#/state/gallery'
27+import {useFetchDid} from '#/state/queries/handle'
28+import {useGetPost} from '#/state/queries/post'
29+import {useAgent} from '#/state/session'
30+import {ComposerOpts} from '#/state/shell/composer'
3132export function useExternalLinkFetch({
33 setQuote,