tooot/src/components/mediaSelector.ts

221 lines
6.1 KiB
TypeScript
Raw Normal View History

2021-05-09 21:59:03 +02:00
import analytics from '@components/analytics'
2021-05-13 02:03:24 +02:00
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
2022-06-05 17:58:18 +02:00
import { store } from '@root/store'
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
2022-06-09 21:33:10 +02:00
import { manipulateAsync, SaveFormat } from 'expo-image-manipulator'
2022-06-05 17:58:18 +02:00
import * as ExpoImagePicker from 'expo-image-picker'
2021-05-13 02:03:24 +02:00
import i18next from 'i18next'
2022-06-09 21:33:10 +02:00
import { Alert, Linking, Platform } from 'react-native'
2022-06-05 17:58:18 +02:00
import ImagePicker, {
Image,
ImageOrVideo
} from 'react-native-image-crop-picker'
2021-05-09 21:59:03 +02:00
export interface Props {
2022-06-05 17:58:18 +02:00
mediaType?: 'photo' | 'video'
resize?: { width?: number; height?: number }
maximum?: number
indicateMaximum?: boolean
2021-05-09 21:59:03 +02:00
showActionSheetWithOptions: (
options: ActionSheetOptions,
2021-11-15 22:34:43 +01:00
callback: (i?: number | undefined) => void | Promise<void>
2021-05-09 21:59:03 +02:00
) => void
}
const mediaSelector = async ({
2022-06-05 17:58:18 +02:00
mediaType,
2021-05-13 02:03:24 +02:00
resize,
2022-06-05 17:58:18 +02:00
maximum,
indicateMaximum = false,
2021-05-09 21:59:03 +02:00
showActionSheetWithOptions
2022-06-09 21:33:10 +02:00
}: Props): Promise<({ uri: string } & Omit<ImageOrVideo, 'path'>)[]> => {
2022-06-05 17:58:18 +02:00
const checkLibraryPermission = async (): Promise<boolean> => {
const { status } =
await ExpoImagePicker.requestMediaLibraryPermissionsAsync()
if (status !== 'granted') {
Alert.alert(
i18next.t('componentMediaSelector:library.alert.title'),
i18next.t('componentMediaSelector:library.alert.message'),
[
{
text: i18next.t('common:buttons.cancel'),
style: 'cancel',
onPress: () =>
analytics('mediaSelector_nopermission', {
action: 'cancel'
})
},
{
text: i18next.t(
'componentMediaSelector:library.alert.buttons.settings'
),
style: 'default',
onPress: () => {
analytics('mediaSelector_nopermission', {
action: 'settings'
})
Linking.openURL('app-settings:')
}
2021-05-13 02:03:24 +02:00
}
2022-06-05 17:58:18 +02:00
]
)
return false
} else {
return true
}
}
const _maximum =
maximum ||
getInstanceConfigurationStatusMaxAttachments(store.getState()) ||
4
const options = () => {
switch (mediaType) {
case 'photo':
return [
i18next.t(
'componentMediaSelector:options.image',
indicateMaximum ? { context: 'max', max: _maximum } : undefined
),
i18next.t('common:buttons.cancel')
]
case 'video':
return [
i18next.t(
'componentMediaSelector:options.video',
indicateMaximum ? { context: 'max', max: 1 } : undefined
),
i18next.t('common:buttons.cancel')
]
default:
return [
i18next.t(
'componentMediaSelector:options.image',
indicateMaximum ? { context: 'max', max: _maximum } : undefined
),
i18next.t(
'componentMediaSelector:options.video',
indicateMaximum ? { context: 'max', max: 1 } : undefined
),
i18next.t('common:buttons.cancel')
]
}
}
return new Promise((resolve, reject) => {
const selectImage = async () => {
const images = await ImagePicker.openPicker({
mediaType: 'photo',
includeExif: false,
multiple: true,
minFiles: 1,
2022-06-05 18:52:33 +02:00
maxFiles: _maximum,
2022-06-09 21:33:10 +02:00
smartAlbums: ['UserLibrary'],
writeTempFile: false,
loadingLabelText: ''
2022-06-05 17:58:18 +02:00
}).catch(() => {})
if (!images) {
return reject()
}
2022-06-09 21:33:10 +02:00
// 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'
}
}
}
}
2022-06-05 17:58:18 +02:00
if (!resize) {
2022-06-09 21:33:10 +02:00
return resolve(
images.map(image => ({
...image,
uri: image.sourceURL || `file://${image.path}`
}))
)
2021-05-13 02:03:24 +02:00
} else {
2022-06-05 17:58:18 +02:00
const croppedImages: Image[] = []
for (const image of images) {
const croppedImage = await ImagePicker.openCropper({
mediaType: 'photo',
path: image.path,
width: resize.width,
2022-06-05 18:52:33 +02:00
height: resize.height,
cropperChooseText: i18next.t('common:buttons.apply'),
cropperCancelText: i18next.t('common:buttons.cancel'),
hideBottomControls: true
2022-06-05 17:58:18 +02:00
}).catch(() => {})
croppedImage && croppedImages.push(croppedImage)
}
2022-06-09 21:33:10 +02:00
return resolve(
croppedImages.map(image => ({
...image,
uri: `file://${image.path}`
}))
)
2021-05-13 02:03:24 +02:00
}
}
2022-06-05 17:58:18 +02:00
const selectVideo = async () => {
const video = await ImagePicker.openPicker({
mediaType: 'video',
2022-06-05 18:52:33 +02:00
includeExif: false,
loadingLabelText: ''
2022-06-05 17:58:18 +02:00
}).catch(() => {})
2021-05-13 02:03:24 +02:00
2022-06-05 17:58:18 +02:00
if (video) {
2022-06-09 21:33:10 +02:00
return resolve([
{ ...video, uri: video.sourceURL || `file://${video.path}` }
])
2022-06-05 17:58:18 +02:00
} else {
return reject()
}
}
2021-05-13 02:03:24 +02:00
showActionSheetWithOptions(
{
title: i18next.t('componentMediaSelector:title'),
2022-06-05 17:58:18 +02:00
options: options(),
cancelButtonIndex: mediaType ? 1 : 2
2021-05-13 02:03:24 +02:00
},
async buttonIndex => {
2022-06-05 17:58:18 +02:00
if (!(await checkLibraryPermission())) {
return reject()
}
2021-05-09 21:59:03 +02:00
2022-06-05 17:58:18 +02:00
switch (mediaType) {
case 'photo':
if (buttonIndex === 0) {
await selectImage()
2021-05-09 21:59:03 +02:00
}
2022-06-05 17:58:18 +02:00
break
case 'video':
if (buttonIndex === 0) {
await selectVideo()
2021-05-09 21:59:03 +02:00
}
2022-06-05 17:58:18 +02:00
break
default:
if (buttonIndex === 0) {
await selectImage()
} else if (buttonIndex === 1) {
await selectVideo()
}
break
2021-05-09 21:59:03 +02:00
}
}
2021-05-13 02:03:24 +02:00
)
})
2021-05-09 21:59:03 +02:00
}
export default mediaSelector