mirror of
https://github.com/tooot-app/app
synced 2025-02-16 20:00:53 +01:00
Fixed #108
This commit is contained in:
parent
d6768c0f6f
commit
fc8fdec12f
@ -41,9 +41,6 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- ExpoHaptics (11.2.0):
|
||||
- ExpoModulesCore
|
||||
- ExpoImageManipulator (10.3.1):
|
||||
- EXImageLoader
|
||||
- ExpoModulesCore
|
||||
- ExpoImagePicker (13.1.1):
|
||||
- ExpoModulesCore
|
||||
- ExpoKeepAwake (10.1.1):
|
||||
@ -531,6 +528,15 @@ PODS:
|
||||
- SDWebImageWebPCoder (~> 0.8.4)
|
||||
- RNGestureHandler (2.4.2):
|
||||
- React-Core
|
||||
- RNImageCropPicker (0.37.3):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- RNImageCropPicker/QBImagePickerController (= 0.37.3)
|
||||
- TOCropViewController
|
||||
- RNImageCropPicker/QBImagePickerController (0.37.3):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- TOCropViewController
|
||||
- RNReanimated (2.8.0):
|
||||
- DoubleConversion
|
||||
- FBLazyVector
|
||||
@ -577,6 +583,7 @@ PODS:
|
||||
- Sentry (7.11.0):
|
||||
- Sentry/Core (= 7.11.0)
|
||||
- Sentry/Core (7.11.0)
|
||||
- TOCropViewController (2.6.1)
|
||||
- Yoga (1.14.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
@ -599,7 +606,6 @@ 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`)
|
||||
@ -658,6 +664,7 @@ DEPENDENCIES:
|
||||
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
|
||||
- RNFastImage (from `../node_modules/react-native-fast-image`)
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
|
||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||
- RNScreens (from `../node_modules/react-native-screens`)
|
||||
- "RNSentry (from `../node_modules/@sentry/react-native`)"
|
||||
@ -685,6 +692,7 @@ SPEC REPOS:
|
||||
- SDWebImage
|
||||
- SDWebImageWebPCoder
|
||||
- Sentry
|
||||
- TOCropViewController
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
boost:
|
||||
@ -725,8 +733,6 @@ 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:
|
||||
@ -835,6 +841,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-fast-image"
|
||||
RNGestureHandler:
|
||||
:path: "../node_modules/react-native-gesture-handler"
|
||||
RNImageCropPicker:
|
||||
:path: "../node_modules/react-native-image-crop-picker"
|
||||
RNReanimated:
|
||||
:path: "../node_modules/react-native-reanimated"
|
||||
RNScreens:
|
||||
@ -869,7 +877,6 @@ SPEC CHECKSUMS:
|
||||
Expo: 64d52669fa3b9342919b5b44b2b4f15f19b0cf76
|
||||
ExpoCrypto: d0d0f3e20875dc450b4ec88f0fb608da5c2c6c17
|
||||
ExpoHaptics: ad58ec96a25e57579c14a47c7d71f0de0de8656a
|
||||
ExpoImageManipulator: b55580bbc7b10099c7707949903e7176a8542ee8
|
||||
ExpoImagePicker: d9d6b4f29db437fc7796f13cee5f133f5b4b5f7c
|
||||
ExpoKeepAwake: c0c494b442ecd8122974c13b93ccfb57bd408e88
|
||||
ExpoLocalization: 8f619bb6eec64575cd5220bfabbd7b4e2d6f33f8
|
||||
@ -938,6 +945,7 @@ SPEC CHECKSUMS:
|
||||
RNCAsyncStorage: 466b9df1a14bccda91da86e0b7d9a345d78e1673
|
||||
RNFastImage: 945abf54742505d790d9024d230c69b1e866bc88
|
||||
RNGestureHandler: 61628a2c859172551aa2100d3e73d1e57878392f
|
||||
RNImageCropPicker: 44e2807bc410741f35d4c45b6586aedfe3da39d2
|
||||
RNReanimated: 64573e25e078ae6bec03b891586d50b9ec284393
|
||||
RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19
|
||||
RNSentry: 85f6525b5fe8d2ada065858026b338605b3c09da
|
||||
@ -946,6 +954,7 @@ SPEC CHECKSUMS:
|
||||
SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e
|
||||
SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815
|
||||
Sentry: 0c5cd63d714187b4a39c331c1f0eb04ba7868341
|
||||
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
|
||||
Yoga: 99652481fcd320aefa4a7ef90095b95acd181952
|
||||
|
||||
PODFILE CHECKSUM: d6d20fa7c51228cebc309aed987ed7d8f4274844
|
||||
|
@ -6,6 +6,5 @@
|
||||
|
||||
*/
|
||||
|
||||
"NSCameraUsageDescription" = "Allow tooot to capture photo or video and attach it to your toot";
|
||||
"NSPhotoLibraryAddUsageDescription" = "Allow tooot to save an image to your camera roll";
|
||||
"NSPhotoLibraryUsageDescription" = "Allow tooot to access your camera roll to attach photos or videos to your toot";
|
||||
|
@ -386,13 +386,17 @@
|
||||
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-resources.sh",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
|
@ -55,8 +55,6 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to capture photo or video and attach it to your toot</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>NSMainNibFile</key>
|
||||
|
@ -6,6 +6,5 @@
|
||||
|
||||
*/
|
||||
|
||||
"NSCameraUsageDescription" = "允许tooot拍摄图片或视频,以添加嘟文附件";
|
||||
"NSPhotoLibraryAddUsageDescription" = "允许tooot保存图片至相册";
|
||||
"NSPhotoLibraryUsageDescription" = "允许tooot读取相册图片或视频,以添加嘟文附件";
|
||||
|
@ -55,7 +55,6 @@
|
||||
"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",
|
||||
@ -84,6 +83,7 @@
|
||||
"react-native-flash-message": "0.2.1",
|
||||
"react-native-gesture-handler": "2.4.2",
|
||||
"react-native-htmlview": "0.16.0",
|
||||
"react-native-image-crop-picker": "^0.37.3",
|
||||
"react-native-image-keyboard": "^2.2.0",
|
||||
"react-native-pager-view": "5.4.11",
|
||||
"react-native-reanimated": "2.8.0",
|
||||
|
7
src/@types/app.d.ts
vendored
7
src/@types/app.d.ts
vendored
@ -13,11 +13,4 @@ declare namespace App {
|
||||
| 'Conversations'
|
||||
| 'Bookmarks'
|
||||
| 'Favourites'
|
||||
|
||||
interface IImageInfo {
|
||||
uri: string
|
||||
width: number
|
||||
height: number
|
||||
type?: 'image' | 'video'
|
||||
}
|
||||
}
|
||||
|
156
src/Screens.tsx
156
src/Screens.tsx
@ -178,8 +178,49 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
}
|
||||
|
||||
let text: string | undefined = undefined
|
||||
let images: { type: string; uri: string }[] = []
|
||||
let video: { type: string; uri: string } | undefined = undefined
|
||||
let media: { path: string; mime: string }[] = []
|
||||
|
||||
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
|
||||
const typesVideo = ['mp4', 'm4v', 'mov', 'webm', 'mpeg']
|
||||
const filterMedia = ({ path, mime }: { path: string; mime: string }) => {
|
||||
if (mime.startsWith('image/')) {
|
||||
if (!typesImage.includes(mime.split('/')[1])) {
|
||||
console.warn('Image type not supported:', mime.split('/')[1])
|
||||
displayMessage({
|
||||
message: t('shareError.imageNotSupported', {
|
||||
type: mime.split('/')[1]
|
||||
}),
|
||||
type: 'error',
|
||||
theme
|
||||
})
|
||||
return
|
||||
}
|
||||
media.push({ path, mime })
|
||||
} else if (mime.startsWith('video/')) {
|
||||
if (!typesVideo.includes(mime.split('/')[1])) {
|
||||
console.warn('Video type not supported:', mime.split('/')[1])
|
||||
displayMessage({
|
||||
message: t('shareError.videoNotSupported', {
|
||||
type: mime.split('/')[1]
|
||||
}),
|
||||
type: 'error',
|
||||
theme
|
||||
})
|
||||
return
|
||||
}
|
||||
media.push({ path, mime })
|
||||
} else {
|
||||
if (typesImage.includes(path.split('.').pop() || '')) {
|
||||
media.push({ path: path, mime: 'image/jpg' })
|
||||
return
|
||||
}
|
||||
if (typesVideo.includes(path.split('.').pop() || '')) {
|
||||
media.push({ path: path, mime: 'video/mp4' })
|
||||
return
|
||||
}
|
||||
text = !text ? path : text.concat(text, `\n${path}`)
|
||||
}
|
||||
}
|
||||
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
@ -187,55 +228,11 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
return
|
||||
}
|
||||
|
||||
item.data.forEach(d => {
|
||||
if (typeof d === 'string') return
|
||||
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
|
||||
const typesVideo = ['mp4', 'm4v', 'mov', 'webm']
|
||||
const { mimeType, data } = d
|
||||
if (mimeType.startsWith('image/')) {
|
||||
if (!typesImage.includes(mimeType.split('/')[1])) {
|
||||
console.warn(
|
||||
'Image type not supported:',
|
||||
mimeType.split('/')[1]
|
||||
)
|
||||
displayMessage({
|
||||
message: t('shareError.imageNotSupported', {
|
||||
type: mimeType.split('/')[1]
|
||||
}),
|
||||
type: 'error',
|
||||
theme
|
||||
})
|
||||
return
|
||||
}
|
||||
images.push({ type: mimeType.split('/')[1], uri: data })
|
||||
} else if (mimeType.startsWith('video/')) {
|
||||
if (!typesVideo.includes(mimeType.split('/')[1])) {
|
||||
console.warn(
|
||||
'Video type not supported:',
|
||||
mimeType.split('/')[1]
|
||||
)
|
||||
displayMessage({
|
||||
message: t('shareError.videoNotSupported', {
|
||||
type: mimeType.split('/')[1]
|
||||
}),
|
||||
type: 'error',
|
||||
theme
|
||||
})
|
||||
return
|
||||
}
|
||||
video = { type: mimeType.split('/')[1], uri: data }
|
||||
} else {
|
||||
if (typesImage.includes(data.split('.').pop() || '')) {
|
||||
images.push({ type: data.split('.').pop()!, uri: data })
|
||||
return
|
||||
}
|
||||
if (typesVideo.includes(data.split('.').pop() || '')) {
|
||||
video = { type: data.split('.').pop()!, uri: data }
|
||||
return
|
||||
}
|
||||
text = !text ? data : text.concat(text, `\n${data}`)
|
||||
for (const d of item.data) {
|
||||
if (typeof d !== 'string') {
|
||||
filterMedia({ path: d.data, mime: d.mimeType })
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'android':
|
||||
if (!item.mimeType) {
|
||||
@ -247,65 +244,16 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
} else {
|
||||
tempData = item.data
|
||||
}
|
||||
tempData.forEach(d => {
|
||||
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
|
||||
const typesVideo = ['mp4', 'm4v', 'mov', 'webm', 'mpeg']
|
||||
if (item.mimeType!.startsWith('image/')) {
|
||||
if (!typesImage.includes(item.mimeType.split('/')[1])) {
|
||||
console.warn(
|
||||
'Image type not supported:',
|
||||
item.mimeType.split('/')[1]
|
||||
)
|
||||
displayMessage({
|
||||
message: t('shareError.imageNotSupported', {
|
||||
type: item.mimeType.split('/')[1]
|
||||
}),
|
||||
type: 'error',
|
||||
theme
|
||||
})
|
||||
return
|
||||
}
|
||||
images.push({ type: item.mimeType.split('/')[1], uri: d })
|
||||
} else if (item.mimeType.startsWith('video/')) {
|
||||
if (!typesVideo.includes(item.mimeType.split('/')[1])) {
|
||||
console.warn(
|
||||
'Video type not supported:',
|
||||
item.mimeType.split('/')[1]
|
||||
)
|
||||
displayMessage({
|
||||
message: t('shareError.videoNotSupported', {
|
||||
type: item.mimeType.split('/')[1]
|
||||
}),
|
||||
type: 'error',
|
||||
theme
|
||||
})
|
||||
return
|
||||
}
|
||||
video = { type: item.mimeType.split('/')[1], uri: d }
|
||||
} else {
|
||||
if (typesImage.includes(d.split('.').pop() || '')) {
|
||||
images.push({ type: d.split('.').pop()!, uri: d })
|
||||
return
|
||||
}
|
||||
if (typesVideo.includes(d.split('.').pop() || '')) {
|
||||
video = { type: d.split('.').pop()!, uri: d }
|
||||
return
|
||||
}
|
||||
text = !text ? d : text.concat(text, `\n${d}`)
|
||||
}
|
||||
})
|
||||
for (const d of item.data) {
|
||||
filterMedia({ path: d, mime: item.mimeType })
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!text && (!images || !images.length) && !video) {
|
||||
if (!text && !media.length) {
|
||||
return
|
||||
} else {
|
||||
navigationRef.navigate('Screen-Compose', {
|
||||
type: 'share',
|
||||
text,
|
||||
images,
|
||||
video
|
||||
})
|
||||
navigationRef.navigate('Screen-Compose', { type: 'share', text, media })
|
||||
}
|
||||
},
|
||||
[instanceActive]
|
||||
|
@ -1,17 +1,20 @@
|
||||
import analytics from '@components/analytics'
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import * as ImageManipulator from 'expo-image-manipulator'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
import {
|
||||
ImageInfo,
|
||||
UIImagePickerPresentationStyle
|
||||
} from 'expo-image-picker/build/ImagePicker.types'
|
||||
import { store } from '@root/store'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import * as ExpoImagePicker from 'expo-image-picker'
|
||||
import i18next from 'i18next'
|
||||
import { Alert, Linking, Platform } from 'react-native'
|
||||
import { Alert, Linking } from 'react-native'
|
||||
import ImagePicker, {
|
||||
Image,
|
||||
ImageOrVideo
|
||||
} from 'react-native-image-crop-picker'
|
||||
|
||||
export interface Props {
|
||||
mediaTypes?: ImagePicker.MediaTypeOptions
|
||||
resize?: { width?: number; height?: number } // Resize mode contain
|
||||
mediaType?: 'photo' | 'video'
|
||||
resize?: { width?: number; height?: number }
|
||||
maximum?: number
|
||||
indicateMaximum?: boolean
|
||||
showActionSheetWithOptions: (
|
||||
options: ActionSheetOptions,
|
||||
callback: (i?: number | undefined) => void | Promise<void>
|
||||
@ -19,134 +22,157 @@ export interface Props {
|
||||
}
|
||||
|
||||
const mediaSelector = async ({
|
||||
mediaTypes = ImagePicker.MediaTypeOptions.All,
|
||||
mediaType,
|
||||
resize,
|
||||
maximum,
|
||||
indicateMaximum = false,
|
||||
showActionSheetWithOptions
|
||||
}: Props): Promise<ImageInfo> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const resolveResult = async (result: ImageInfo) => {
|
||||
if (resize && result.type === 'image') {
|
||||
let newResult: ImageManipulator.ImageResult
|
||||
if (resize.width && resize.height) {
|
||||
if (resize.width / resize.height > result.width / result.height) {
|
||||
newResult = await ImageManipulator.manipulateAsync(result.uri, [
|
||||
{ resize: { width: resize.width } }
|
||||
])
|
||||
} else {
|
||||
newResult = await ImageManipulator.manipulateAsync(result.uri, [
|
||||
{ resize: { height: resize.height } }
|
||||
])
|
||||
}: Props): Promise<ImageOrVideo[]> => {
|
||||
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:')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newResult = await ImageManipulator.manipulateAsync(result.uri, [
|
||||
{ resize }
|
||||
])
|
||||
}
|
||||
resolve({ ...newResult, cancelled: false })
|
||||
]
|
||||
)
|
||||
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,
|
||||
maxFiles: _maximum
|
||||
}).catch(() => {})
|
||||
|
||||
if (!images) {
|
||||
return reject()
|
||||
}
|
||||
|
||||
if (!resize) {
|
||||
return resolve(images)
|
||||
} else {
|
||||
resolve(result)
|
||||
const croppedImages: Image[] = []
|
||||
for (const image of images) {
|
||||
const croppedImage = await ImagePicker.openCropper({
|
||||
mediaType: 'photo',
|
||||
path: image.path,
|
||||
width: resize.width,
|
||||
height: resize.height
|
||||
}).catch(() => {})
|
||||
croppedImage && croppedImages.push(croppedImage)
|
||||
}
|
||||
return resolve(croppedImages)
|
||||
}
|
||||
}
|
||||
const selectVideo = async () => {
|
||||
const video = await ImagePicker.openPicker({
|
||||
mediaType: 'video',
|
||||
includeExif: false
|
||||
}).catch(() => {})
|
||||
|
||||
if (video) {
|
||||
return resolve([video])
|
||||
} else {
|
||||
return reject()
|
||||
}
|
||||
}
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
title: i18next.t('componentMediaSelector:title'),
|
||||
options: [
|
||||
i18next.t('componentMediaSelector:options.library'),
|
||||
i18next.t('componentMediaSelector:options.photo'),
|
||||
i18next.t('componentMediaSelector:options.cancel')
|
||||
],
|
||||
cancelButtonIndex: 2
|
||||
options: options(),
|
||||
cancelButtonIndex: mediaType ? 1 : 2
|
||||
},
|
||||
async buttonIndex => {
|
||||
if (buttonIndex === 0) {
|
||||
const { status } =
|
||||
await ImagePicker.requestMediaLibraryPermissionsAsync()
|
||||
if (status !== 'granted') {
|
||||
Alert.alert(
|
||||
i18next.t('componentMediaSelector:library.alert.title'),
|
||||
i18next.t('componentMediaSelector:library.alert.message'),
|
||||
[
|
||||
{
|
||||
text: i18next.t(
|
||||
'componentMediaSelector:library.alert.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:')
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
} else {
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes,
|
||||
exif: false,
|
||||
presentationStyle:
|
||||
Platform.OS === 'ios' && parseInt(Platform.Version) < 13
|
||||
? UIImagePickerPresentationStyle.FULL_SCREEN
|
||||
: UIImagePickerPresentationStyle.AUTOMATIC
|
||||
})
|
||||
if (!(await checkLibraryPermission())) {
|
||||
return reject()
|
||||
}
|
||||
|
||||
if (!result.cancelled) {
|
||||
await resolveResult(result)
|
||||
switch (mediaType) {
|
||||
case 'photo':
|
||||
if (buttonIndex === 0) {
|
||||
await selectImage()
|
||||
}
|
||||
}
|
||||
} else if (buttonIndex === 1) {
|
||||
const { status } = await ImagePicker.requestCameraPermissionsAsync()
|
||||
if (status !== 'granted') {
|
||||
Alert.alert(
|
||||
i18next.t('componentMediaSelector:photo.alert.title'),
|
||||
i18next.t('componentMediaSelector:photo.alert.message'),
|
||||
[
|
||||
{
|
||||
text: i18next.t(
|
||||
'componentMediaSelector:photo.alert.buttons.cancel'
|
||||
),
|
||||
style: 'cancel',
|
||||
onPress: () => {
|
||||
analytics('compose_addattachment_camera_nopermission', {
|
||||
action: 'cancel'
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
text: i18next.t(
|
||||
'componentMediaSelector:photo.alert.buttons.settings'
|
||||
),
|
||||
style: 'default',
|
||||
onPress: () => {
|
||||
analytics('compose_addattachment_camera_nopermission', {
|
||||
action: 'settings'
|
||||
})
|
||||
Linking.openURL('app-settings:')
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
} else {
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
mediaTypes,
|
||||
exif: false
|
||||
})
|
||||
|
||||
if (!result.cancelled) {
|
||||
await resolveResult(result)
|
||||
break
|
||||
case 'video':
|
||||
if (buttonIndex === 0) {
|
||||
await selectVideo()
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (buttonIndex === 0) {
|
||||
await selectImage()
|
||||
} else if (buttonIndex === 1) {
|
||||
await selectVideo()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -1,27 +1,17 @@
|
||||
{
|
||||
"title": "Select media source",
|
||||
"options": {
|
||||
"library": "Upload from library",
|
||||
"photo": "Take a photo",
|
||||
"cancel": "$t(common:buttons.cancel)"
|
||||
"image": "Upload photos",
|
||||
"image_max": "Upload photos (max {{max}})",
|
||||
"video": "Upload video",
|
||||
"video_max": "Upload video (max {{max}})"
|
||||
},
|
||||
"library": {
|
||||
"alert": {
|
||||
"title": "No permission",
|
||||
"message": "Require photo library read permission to upload",
|
||||
"buttons": {
|
||||
"settings": "Update setting",
|
||||
"cancel": "$t(common:buttons.cancel)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"photo": {
|
||||
"alert": {
|
||||
"title": "No permission",
|
||||
"message": "Require camera usage permission to upload",
|
||||
"buttons": {
|
||||
"settings": "Update setting",
|
||||
"cancel": "$t(common:buttons.cancel)"
|
||||
"settings": "Update setting"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,18 +136,6 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
const uploadImage = async ({
|
||||
type,
|
||||
uri
|
||||
}: {
|
||||
type: 'image' | 'video'
|
||||
uri: string
|
||||
}) => {
|
||||
await uploadAttachment({
|
||||
composeDispatch,
|
||||
imageInfo: { type, uri, width: 100, height: 100 }
|
||||
})
|
||||
}
|
||||
switch (params?.type) {
|
||||
case 'share':
|
||||
if (params.text) {
|
||||
@ -158,12 +146,13 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
disableDebounce: true
|
||||
})
|
||||
}
|
||||
if (params.images?.length) {
|
||||
params.images.forEach(image => {
|
||||
uploadImage({ type: 'image', uri: image.uri })
|
||||
})
|
||||
} else if (params.video) {
|
||||
uploadImage({ type: 'video', uri: params.video.uri })
|
||||
if (params.media?.length) {
|
||||
for (const m of params.media) {
|
||||
uploadAttachment({
|
||||
composeDispatch,
|
||||
media: { ...m, width: 100, height: 100 }
|
||||
})
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'edit':
|
||||
|
@ -1,5 +1,4 @@
|
||||
import * as Crypto from 'expo-crypto'
|
||||
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
|
||||
import * as VideoThumbnails from 'expo-video-thumbnails'
|
||||
import { Dispatch } from 'react'
|
||||
import { Alert } from 'react-native'
|
||||
@ -8,6 +7,7 @@ import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import i18next from 'i18next'
|
||||
import apiInstance from '@api/instance'
|
||||
import mediaSelector from '@components/mediaSelector'
|
||||
import { ImageOrVideo } from 'react-native-image-crop-picker'
|
||||
|
||||
export interface Props {
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
@ -19,39 +19,38 @@ export interface Props {
|
||||
|
||||
export const uploadAttachment = async ({
|
||||
composeDispatch,
|
||||
imageInfo
|
||||
media
|
||||
}: {
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
imageInfo: Pick<ImageInfo, 'type' | 'uri' | 'width' | 'height'>
|
||||
media: Pick<ImageOrVideo, 'path' | 'mime' | 'width' | 'height'>
|
||||
}) => {
|
||||
const hash = await Crypto.digestStringAsync(
|
||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||
imageInfo.uri + Math.random()
|
||||
media.path + Math.random()
|
||||
)
|
||||
|
||||
let attachmentType: string
|
||||
|
||||
switch (imageInfo.type) {
|
||||
switch (media.mime.split('/')[0]) {
|
||||
case 'image':
|
||||
console.log('uri', imageInfo.uri)
|
||||
attachmentType = `image/${imageInfo.uri.split('.')[1]}`
|
||||
attachmentType = `image/${media.path.split('.')[1]}`
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
local: { ...imageInfo, local_thumbnail: imageInfo.uri, hash },
|
||||
local: { ...media, type: 'image', local_thumbnail: media.path, hash },
|
||||
uploading: true
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'video':
|
||||
attachmentType = `video/${imageInfo.uri.split('.')[1]}`
|
||||
VideoThumbnails.getThumbnailAsync(imageInfo.uri)
|
||||
attachmentType = `video/${media.path.split('.')[1]}`
|
||||
VideoThumbnails.getThumbnailAsync(media.path)
|
||||
.then(({ uri, width, height }) =>
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
local: {
|
||||
...imageInfo,
|
||||
...media,
|
||||
type: 'video',
|
||||
local_thumbnail: uri,
|
||||
hash,
|
||||
width,
|
||||
@ -65,7 +64,7 @@ export const uploadAttachment = async ({
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
local: { ...imageInfo, hash },
|
||||
local: { ...media, type: 'video', hash },
|
||||
uploading: true
|
||||
}
|
||||
})
|
||||
@ -76,7 +75,7 @@ export const uploadAttachment = async ({
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
local: { ...imageInfo, hash },
|
||||
local: { ...media, type: 'unknown', hash },
|
||||
uploading: true
|
||||
}
|
||||
})
|
||||
@ -106,14 +105,14 @@ export const uploadAttachment = async ({
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('file', {
|
||||
// @ts-ignore
|
||||
uri: imageInfo.uri,
|
||||
uri: `file://${media.path}`,
|
||||
name: attachmentType,
|
||||
type: attachmentType
|
||||
})
|
||||
} as any)
|
||||
|
||||
return apiInstance<Mastodon.Attachment>({
|
||||
method: 'post',
|
||||
version: 'v2',
|
||||
url: 'media',
|
||||
body: formData
|
||||
})
|
||||
@ -121,7 +120,7 @@ export const uploadAttachment = async ({
|
||||
if (res.body.id) {
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/end',
|
||||
payload: { remote: res.body, local: imageInfo }
|
||||
payload: { remote: res.body, local: media }
|
||||
})
|
||||
} else {
|
||||
uploadFailed()
|
||||
@ -136,8 +135,13 @@ const chooseAndUploadAttachment = async ({
|
||||
composeDispatch,
|
||||
showActionSheetWithOptions
|
||||
}: Props): Promise<any> => {
|
||||
const result = await mediaSelector({ showActionSheetWithOptions })
|
||||
await uploadAttachment({ composeDispatch, imageInfo: result })
|
||||
const result = await mediaSelector({
|
||||
indicateMaximum: true,
|
||||
showActionSheetWithOptions
|
||||
})
|
||||
for (const media of result) {
|
||||
uploadAttachment({ composeDispatch, media })
|
||||
}
|
||||
}
|
||||
|
||||
export default chooseAndUploadAttachment
|
||||
|
@ -83,9 +83,9 @@ const ComposeTextInput: React.FC = () => {
|
||||
if (nativeEvent.linkUri) {
|
||||
uploadAttachment({
|
||||
composeDispatch,
|
||||
imageInfo: {
|
||||
uri: nativeEvent.linkUri,
|
||||
type: 'image',
|
||||
media: {
|
||||
path: nativeEvent.linkUri,
|
||||
mime: 'image/gif',
|
||||
width: 100,
|
||||
height: 100
|
||||
}
|
||||
|
8
src/screens/Compose/utils/types.d.ts
vendored
8
src/screens/Compose/utils/types.d.ts
vendored
@ -1,6 +1,12 @@
|
||||
import { ImageOrVideo } from 'react-native-image-crop-picker'
|
||||
|
||||
export type ExtendedAttachment = {
|
||||
remote?: Mastodon.Attachment
|
||||
local?: App.IImageInfo & { local_thumbnail?: string; hash?: string }
|
||||
local?: Pick<ImageOrVideo, 'path' | 'width' | 'height'> & {
|
||||
type: 'image' | 'video' | 'unknown'
|
||||
local_thumbnail?: string
|
||||
hash?: string
|
||||
}
|
||||
uploading?: boolean
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ import { MenuRow } from '@components/Menu'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
import React, { RefObject } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import FlashMessage from 'react-native-flash-message'
|
||||
@ -30,9 +29,13 @@ const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
|
||||
iconBack='ChevronRight'
|
||||
onPress={async () => {
|
||||
const image = await mediaSelector({
|
||||
mediaType: 'photo',
|
||||
maximum: 1,
|
||||
showActionSheetWithOptions,
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||
resize: { width: 400, height: 400 }
|
||||
resize:
|
||||
type === 'avatar'
|
||||
? { width: 400, height: 400 }
|
||||
: { width: 1500, height: 500 }
|
||||
})
|
||||
mutation.mutate({
|
||||
theme,
|
||||
@ -43,7 +46,7 @@ const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
|
||||
failed: true
|
||||
},
|
||||
type,
|
||||
data: image.uri
|
||||
data: image[0].path
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
@ -52,8 +52,7 @@ export type RootStackParamList = {
|
||||
| {
|
||||
type: 'share'
|
||||
text?: string
|
||||
images?: { type: string; uri: string }[]
|
||||
video?: { type: string; uri: string }
|
||||
media?: { path: string; mime: string }[]
|
||||
}
|
||||
| undefined
|
||||
'Screen-ImagesViewer': {
|
||||
|
@ -71,11 +71,10 @@ const mutationFunction = async ({ type, data }: MutationVarsProfile) => {
|
||||
})
|
||||
} else if (type === 'avatar' || type === 'header') {
|
||||
formData.append(type, {
|
||||
// @ts-ignore
|
||||
uri: data,
|
||||
uri: `file://${data}`,
|
||||
name: 'image/jpeg',
|
||||
type: 'image/jpeg'
|
||||
})
|
||||
} as any)
|
||||
} else {
|
||||
// @ts-ignore
|
||||
formData.append(type, data)
|
||||
|
12
yarn.lock
12
yarn.lock
@ -4421,13 +4421,6 @@ 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"
|
||||
@ -7517,6 +7510,11 @@ react-native-htmlview@0.16.0:
|
||||
entities "^1.1.1"
|
||||
htmlparser2-without-node-native "^3.9.2"
|
||||
|
||||
react-native-image-crop-picker@^0.37.3:
|
||||
version "0.37.3"
|
||||
resolved "https://registry.yarnpkg.com/react-native-image-crop-picker/-/react-native-image-crop-picker-0.37.3.tgz#f260e40b6a6ba8e98f4db3dde25a8f09e0936385"
|
||||
integrity sha512-ih+0pWWRUNEFQyaHwGbH9rqJNOb7EBYMwKJhTY0VmsKIA9E+usfwMmQXAFIfOnee7fTn0A2vOXkBCPQZwyvnQw==
|
||||
|
||||
react-native-image-keyboard@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-image-keyboard/-/react-native-image-keyboard-2.2.0.tgz#dc3f90aaaac20a79315015a330e62e85547e0674"
|
||||
|
Loading…
x
Reference in New Issue
Block a user