Using proper image transformation

This commit is contained in:
Zhiyuan Zheng 2022-06-09 21:33:10 +02:00
parent bd5a0948cf
commit b5a5ce01a3
13 changed files with 104 additions and 44 deletions

View File

@ -4,6 +4,8 @@ PODS:
- DoubleConversion (1.1.6)
- EASClient (0.2.1):
- ExpoModulesCore
- EXApplication (4.1.0):
- ExpoModulesCore
- EXAV (11.2.3):
- ExpoModulesCore
- React-runtimeexecutor
@ -23,6 +25,8 @@ PODS:
- EXFirebaseCore (5.0.0):
- ExpoModulesCore
- Firebase/Core (= 8.14.0)
- EXFont (10.1.0):
- ExpoModulesCore
- EXImageLoader (3.2.0):
- ExpoModulesCore
- React-Core
@ -37,8 +41,13 @@ PODS:
- ExpoModulesCore
- ExpoHaptics (11.2.0):
- ExpoModulesCore
- ExpoImageManipulator (10.3.1):
- EXImageLoader
- ExpoModulesCore
- ExpoImagePicker (13.1.1):
- ExpoModulesCore
- ExpoKeepAwake (10.1.1):
- ExpoModulesCore
- ExpoLocalization (13.0.0):
- ExpoModulesCore
- ExpoModulesCore (0.9.2):
@ -590,6 +599,7 @@ DEPENDENCIES:
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EASClient (from `../node_modules/expo-eas-client/ios`)
- EXApplication (from `../node_modules/expo-application/ios`)
- EXAV (from `../node_modules/expo-av/ios`)
- EXConstants (from `../node_modules/expo-constants/ios`)
- EXDevice (from `../node_modules/expo-device/ios`)
@ -597,6 +607,7 @@ DEPENDENCIES:
- EXFileSystem (from `../node_modules/expo-file-system/ios`)
- EXFirebaseAnalytics (from `../node_modules/expo-firebase-analytics/ios`)
- EXFirebaseCore (from `../node_modules/expo-firebase-core/ios`)
- EXFont (from `../node_modules/expo-font/ios`)
- EXImageLoader (from `../node_modules/expo-image-loader/ios`)
- EXJSONUtils (from `../node_modules/expo-json-utils/ios`)
- EXManifests (from `../node_modules/expo-manifests/ios`)
@ -604,7 +615,9 @@ DEPENDENCIES:
- Expo (from `../node_modules/expo/ios`)
- ExpoCrypto (from `../node_modules/expo-crypto/ios`)
- ExpoHaptics (from `../node_modules/expo-haptics/ios`)
- ExpoImageManipulator (from `../node_modules/expo-image-manipulator/ios`)
- ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoLocalization (from `../node_modules/expo-localization/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core/ios`)
- ExpoRandom (from `../node_modules/expo-random/ios`)
@ -701,6 +714,8 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
EASClient:
:path: "../node_modules/expo-eas-client/ios"
EXApplication:
:path: "../node_modules/expo-application/ios"
EXAV:
:path: "../node_modules/expo-av/ios"
EXConstants:
@ -715,6 +730,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-firebase-analytics/ios"
EXFirebaseCore:
:path: "../node_modules/expo-firebase-core/ios"
EXFont:
:path: "../node_modules/expo-font/ios"
EXImageLoader:
:path: "../node_modules/expo-image-loader/ios"
EXJSONUtils:
@ -729,8 +746,12 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-crypto/ios"
ExpoHaptics:
:path: "../node_modules/expo-haptics/ios"
ExpoImageManipulator:
:path: "../node_modules/expo-image-manipulator/ios"
ExpoImagePicker:
:path: "../node_modules/expo-image-picker/ios"
ExpoKeepAwake:
:path: "../node_modules/expo-keep-awake/ios"
ExpoLocalization:
:path: "../node_modules/expo-localization/ios"
ExpoModulesCore:
@ -859,6 +880,7 @@ SPEC CHECKSUMS:
boost: a7c83b31436843459a1961bfd74b96033dc77234
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
EASClient: 93565f4d024559b75eac62bc7d50acaa354614f6
EXApplication: d6562af1204162e0ac46d341a7d4e5dc720b33de
EXAV: 88f61c5af8415715b7ee51f084c1020235b85c56
EXConstants: fdbe52259365b6a6faaa5e99a3b82cfa6bc2eb61
EXDevice: 0115b360059ccd32c1701744e374e3259ffbdd3c
@ -866,6 +888,7 @@ SPEC CHECKSUMS:
EXFileSystem: 2aa2d9289f84bca9532b9ccbd81504fa31eb1ded
EXFirebaseAnalytics: aeefc63f92277313c3ee86da6a7ecf892f345ed1
EXFirebaseCore: bdfa87df74fa1b74a6b38957561456aabad28a4f
EXFont: 04235cc22e6fef86028feb67db452978dc6f240f
EXImageLoader: b88e053d760f85a82405b1db2de4abf11978fc9f
EXJSONUtils: 2a74b8f40f1523cc3f92af99c91aa78201737a77
EXManifests: 0c6134b7b6f3236a93a778c3f44ba1cfb3f9fa3d
@ -873,7 +896,9 @@ SPEC CHECKSUMS:
Expo: b9fff0a1eac0f424fc68ea49b4347fb308e52e17
ExpoCrypto: d0d0f3e20875dc450b4ec88f0fb608da5c2c6c17
ExpoHaptics: ad58ec96a25e57579c14a47c7d71f0de0de8656a
ExpoImageManipulator: b55580bbc7b10099c7707949903e7176a8542ee8
ExpoImagePicker: d9d6b4f29db437fc7796f13cee5f133f5b4b5f7c
ExpoKeepAwake: c0c494b442ecd8122974c13b93ccfb57bd408e88
ExpoLocalization: 8f619bb6eec64575cd5220bfabbd7b4e2d6f33f8
ExpoModulesCore: e4278a668e8c13c0269ed8b8a4200989deea2973
ExpoRandom: 14df0976aa363a71a730ceb7655250f3047c0e42

View File

@ -56,6 +56,7 @@
"expo-file-system": "14.0.0",
"expo-firebase-analytics": "7.0.0",
"expo-haptics": "11.2.0",
"expo-image-manipulator": "^10.3.1",
"expo-image-picker": "13.1.1",
"expo-linking": "3.1.0",
"expo-localization": "13.0.0",
@ -153,4 +154,4 @@
}
}
}
}
}

View File

@ -178,11 +178,11 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
}
let text: string | undefined = undefined
let media: { path: string; mime: string }[] = []
let media: { uri: string; mime: string }[] = []
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
const typesVideo = ['mp4', 'm4v', 'mov', 'webm', 'mpeg']
const filterMedia = ({ path, mime }: { path: string; mime: string }) => {
const filterMedia = ({ uri, mime }: { uri: string; mime: string }) => {
if (mime.startsWith('image/')) {
if (!typesImage.includes(mime.split('/')[1])) {
console.warn('Image type not supported:', mime.split('/')[1])
@ -195,7 +195,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
})
return
}
media.push({ path, mime })
media.push({ uri, mime })
} else if (mime.startsWith('video/')) {
if (!typesVideo.includes(mime.split('/')[1])) {
console.warn('Video type not supported:', mime.split('/')[1])
@ -208,17 +208,17 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
})
return
}
media.push({ path, mime })
media.push({ uri, mime })
} else {
if (typesImage.includes(path.split('.').pop() || '')) {
media.push({ path: path, mime: 'image/jpg' })
if (typesImage.includes(uri.split('.').pop() || '')) {
media.push({ uri, mime: 'image/jpg' })
return
}
if (typesVideo.includes(path.split('.').pop() || '')) {
media.push({ path: path, mime: 'video/mp4' })
if (typesVideo.includes(uri.split('.').pop() || '')) {
media.push({ uri, mime: 'video/mp4' })
return
}
text = !text ? path : text.concat(text, `\n${path}`)
text = !text ? uri : text.concat(text, `\n${uri}`)
}
}
@ -230,7 +230,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
for (const d of item.data) {
if (typeof d !== 'string') {
filterMedia({ path: d.data, mime: d.mimeType })
filterMedia({ uri: d.data, mime: d.mimeType })
}
}
break
@ -245,7 +245,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
tempData = item.data
}
for (const d of item.data) {
filterMedia({ path: d, mime: item.mimeType })
filterMedia({ uri: d, mime: item.mimeType })
}
break
}

View File

@ -2,9 +2,10 @@ import analytics from '@components/analytics'
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
import { store } from '@root/store'
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
import { manipulateAsync, SaveFormat } from 'expo-image-manipulator'
import * as ExpoImagePicker from 'expo-image-picker'
import i18next from 'i18next'
import { Alert, Linking } from 'react-native'
import { Alert, Linking, Platform } from 'react-native'
import ImagePicker, {
Image,
ImageOrVideo
@ -27,7 +28,7 @@ const mediaSelector = async ({
maximum,
indicateMaximum = false,
showActionSheetWithOptions
}: Props): Promise<ImageOrVideo[]> => {
}: Props): Promise<({ uri: string } & Omit<ImageOrVideo, 'path'>)[]> => {
const checkLibraryPermission = async (): Promise<boolean> => {
const { status } =
await ExpoImagePicker.requestMediaLibraryPermissionsAsync()
@ -110,17 +111,40 @@ const mediaSelector = async ({
multiple: true,
minFiles: 1,
maxFiles: _maximum,
loadingLabelText: '',
compressImageMaxWidth: 4096,
compressImageMaxHeight: 4096
smartAlbums: ['UserLibrary'],
writeTempFile: false,
loadingLabelText: ''
}).catch(() => {})
if (!images) {
return reject()
}
// react-native-image-crop-picker may return HEIC as JPG that causes upload failure
if (Platform.OS === 'ios') {
for (const [index, image] of images.entries()) {
if (image.mime === 'image/heic') {
const converted = await manipulateAsync(image.sourceURL!, [], {
base64: false,
compress: 0.8,
format: SaveFormat.JPEG
})
images[index] = {
...images[index],
sourceURL: converted.uri,
mime: 'image/jpeg'
}
}
}
}
if (!resize) {
return resolve(images)
return resolve(
images.map(image => ({
...image,
uri: image.sourceURL || `file://${image.path}`
}))
)
} else {
const croppedImages: Image[] = []
for (const image of images) {
@ -135,7 +159,12 @@ const mediaSelector = async ({
}).catch(() => {})
croppedImage && croppedImages.push(croppedImage)
}
return resolve(croppedImages)
return resolve(
croppedImages.map(image => ({
...image,
uri: `file://${image.path}`
}))
)
}
}
const selectVideo = async () => {
@ -146,7 +175,9 @@ const mediaSelector = async ({
}).catch(() => {})
if (video) {
return resolve([video])
return resolve([
{ ...video, uri: video.sourceURL || `file://${video.path}` }
])
} else {
return reject()
}

View File

@ -128,8 +128,8 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
height: imageDimensionis.height
}}
source={{
uri: theAttachmentLocal?.path
? `file://${theAttachmentLocal?.path}`
uri: theAttachmentLocal?.uri
? theAttachmentLocal.uri
: theAttachmentRemote?.preview_url
}}
/>

View File

@ -34,7 +34,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => {
video={
video.local
? ({
url: `file://${video.local.path}`,
url: video.local.uri,
preview_url: video.local.local_thumbnail,
blurhash: video.remote?.blurhash
} as Mastodon.AttachmentVideo)

View File

@ -22,28 +22,25 @@ export const uploadAttachment = async ({
media
}: {
composeDispatch: Dispatch<ComposeAction>
media: Pick<ImageOrVideo, 'path' | 'mime' | 'width' | 'height'>
media: { uri: string } & Pick<ImageOrVideo, 'mime' | 'width' | 'height'>
}) => {
const hash = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
media.path + Math.random()
media.uri + Math.random()
)
let attachmentType: string
switch (media.mime.split('/')[0]) {
case 'image':
attachmentType = `image/${media.path.split('.')[1]}`
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: { ...media, type: 'image', local_thumbnail: media.path, hash },
local: { ...media, type: 'image', local_thumbnail: media.uri, hash },
uploading: true
}
})
break
case 'video':
attachmentType = `video/${media.path.split('.')[1]}`
VideoThumbnails.getThumbnailAsync(media.path)
VideoThumbnails.getThumbnailAsync(media.uri)
.then(({ uri, width, height }) =>
composeDispatch({
type: 'attachment/upload/start',
@ -71,7 +68,6 @@ export const uploadAttachment = async ({
)
break
default:
attachmentType = 'unknown'
composeDispatch({
type: 'attachment/upload/start',
payload: {
@ -105,9 +101,9 @@ export const uploadAttachment = async ({
const formData = new FormData()
formData.append('file', {
uri: `file://${media.path}`,
name: attachmentType,
type: attachmentType
uri: media.uri,
name: media.uri.match(new RegExp(/.*\/(.*)/))?.[1] || 'file.jpg',
type: media.mime
} as any)
return apiInstance<Mastodon.Attachment>({

View File

@ -90,7 +90,7 @@ const ComposeTextInput: React.FC = () => {
uploadAttachment({
composeDispatch,
media: {
path: file.uri,
uri: file.uri,
mime: file.type,
width: 100,
height: 100

View File

@ -58,7 +58,7 @@ const composeReducer = (
attachments: {
...state.attachments,
uploads: state.attachments.uploads.map(upload =>
upload.local?.path === action.payload.local?.path
upload.local?.uri === action.payload.local?.uri
? { ...upload, remote: action.payload.remote, uploading: false }
: upload
)

View File

@ -2,11 +2,11 @@ import { ImageOrVideo } from 'react-native-image-crop-picker'
export type ExtendedAttachment = {
remote?: Mastodon.Attachment
local?: Pick<ImageOrVideo, 'path' | 'width' | 'height' | 'mime'> & {
type: 'image' | 'video' | 'unknown'
local_thumbnail?: string
hash?: string
}
local?: { uri: string } & Pick<ImageOrVideo, 'width' | 'height' | 'mime'> & {
type: 'image' | 'video' | 'unknown'
local_thumbnail?: string
hash?: string
}
uploading?: boolean
}
@ -123,7 +123,7 @@ export type ComposeAction =
type: 'attachment/upload/end'
payload: {
remote: Mastodon.Attachment
local: Pick<ImageOrVideo, 'path' | 'width' | 'height' | 'mime'>
local: { uri: string } & Pick<ImageOrVideo, 'width' | 'height' | 'mime'>
}
}
| {

View File

@ -46,7 +46,7 @@ const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
failed: true
},
type,
data: image[0].path
data: image[0].uri
})
}}
/>

View File

@ -42,7 +42,7 @@ export type RootStackParamList = {
| {
type: 'share'
text?: string
media?: { path: string; mime: string }[]
media?: { uri: string; mime: string }[]
}
| undefined
'Screen-ImagesViewer': {

View File

@ -4428,6 +4428,13 @@ expo-image-loader@~3.2.0:
resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-3.2.0.tgz#d98b021660edef7243f7c5ec011b8d0545626d41"
integrity sha512-LU3Q2prn64/HxdToDmxgMIRXS1ZvD9Q3iCxRVTZn1fPQNNDciIQFE5okaa74Ogx20DFHs90r6WoUd7w9Af1OGQ==
expo-image-manipulator@^10.3.1:
version "10.3.1"
resolved "https://registry.yarnpkg.com/expo-image-manipulator/-/expo-image-manipulator-10.3.1.tgz#e16dd76a550c7f5d653a2a666f26429eba311a6b"
integrity sha512-D08dMD6MerxBu23DmCIhurySQih+eLP7VxKvY5mWqYz9payjDOS1cAGs3HvXPrEDusPQFQ0uIfqc+oqeMNFBIA==
dependencies:
expo-image-loader "~3.2.0"
expo-image-picker@13.1.1:
version "13.1.1"
resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-13.1.1.tgz#e039bf9748ccb7b89370ff2969c3ef07cc949192"