diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 50f174fd..a5c95d9c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -35,6 +35,10 @@ PODS: - React-Core - UMCore - UMImageLoaderInterface + - EXImageManipulator (9.1.0): + - UMCore + - UMFileSystemInterface + - UMImageLoaderInterface - EXImagePicker (10.1.4): - UMCore - UMFileSystemInterface @@ -530,6 +534,7 @@ DEPENDENCIES: - EXFont (from `../node_modules/expo-font/ios`) - EXHaptics (from `../node_modules/expo-haptics/ios`) - EXImageLoader (from `../node_modules/expo-image-loader/ios`) + - EXImageManipulator (from `../node_modules/expo-image-manipulator/ios`) - EXImagePicker (from `../node_modules/expo-image-picker/ios`) - EXKeepAwake (from `../node_modules/expo-keep-awake/ios`) - EXLocalization (from `../node_modules/expo-localization/ios`) @@ -651,6 +656,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-haptics/ios" EXImageLoader: :path: "../node_modules/expo-image-loader/ios" + EXImageManipulator: + :path: "../node_modules/expo-image-manipulator/ios" EXImagePicker: :path: "../node_modules/expo-image-picker/ios" EXKeepAwake: @@ -803,6 +810,7 @@ SPEC CHECKSUMS: EXFont: d6fb79f9863120f0d0b26b0c2d1453bc9511e9df EXHaptics: 2de40c5f50a9e78da92c209db06db5134d8cac0b EXImageLoader: da941c9399e01ec28f2d5b270bdd21f2c8ca596c + EXImageManipulator: a099e4694070c7cb86aa0b0b1afa3ea184153a7d EXImagePicker: dd05b8a5cb782c79d07d1d72e5850c6acc2b9a37 EXKeepAwake: d4e4a3ed8c1c4fd940dd62fc5a8be2a190371fd4 EXLocalization: f139efe4a06be1041815879959346e3d437a6e93 diff --git a/package.json b/package.json index 94741d54..48a858d2 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "expo-crypto": "~9.1.0", "expo-firebase-analytics": "~4.0.2", "expo-haptics": "~10.0.0", + "expo-image-manipulator": "~9.1.0", "expo-image-picker": "~10.1.4", "expo-linking": "~2.2.3", "expo-localization": "~10.1.0", @@ -89,8 +90,8 @@ "valid-url": "^1.0.9" }, "devDependencies": { - "@babel/core": "~7.14.0", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/core": "~7.14.2", + "@babel/plugin-proposal-optional-chaining": "^7.14.2", "@babel/preset-typescript": "^7.13.0", "@expo/config": "^3.3.40", "@jest/types": "^26.6.2", diff --git a/src/@types/react-navigation.d.ts b/src/@types/react-navigation.d.ts index 0d4ec833..5f522ea1 100644 --- a/src/@types/react-navigation.d.ts +++ b/src/@types/react-navigation.d.ts @@ -1,6 +1,4 @@ declare namespace Nav { - import { QueryKeyTimeline } from '@utils/queryHooks/timeline' - type RootStackParamList = { 'Screen-Tabs': undefined 'Screen-Actions': diff --git a/src/components/mediaSelector.ts b/src/components/mediaSelector.ts index 682c6549..c3c73b18 100644 --- a/src/components/mediaSelector.ts +++ b/src/components/mediaSelector.ts @@ -1,13 +1,14 @@ -import * as ImagePicker from 'expo-image-picker' -import { Alert, Linking } from 'react-native' -import { ActionSheetOptions } from '@expo/react-native-action-sheet' -import i18next from 'i18next' 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 } from 'expo-image-picker/build/ImagePicker.types' +import i18next from 'i18next' +import { Alert, Linking } from 'react-native' export interface Props { mediaTypes?: ImagePicker.MediaTypeOptions - uploader: (imageInfo: ImageInfo) => void + resize?: { width?: number; height?: number } // Resize mode contain showActionSheetWithOptions: ( options: ActionSheetOptions, callback: (i: number) => void @@ -16,118 +17,134 @@ export interface Props { const mediaSelector = async ({ mediaTypes = ImagePicker.MediaTypeOptions.All, - uploader, + resize, showActionSheetWithOptions -}: Props): Promise => { - showActionSheetWithOptions( - { - title: i18next.t('componentMediaSelector:title'), - options: [ - i18next.t('componentMediaSelector:options.library'), - i18next.t('componentMediaSelector:options.photo'), - i18next.t('componentMediaSelector:options.cancel') - ], - cancelButtonIndex: 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 - }) - - if (!result.cancelled) { - // https://github.com/expo/expo/issues/11214 - const fixResult = { - ...result, - uri: result.uri.replace('file:/data', 'file:///data') - } - uploader(fixResult) - return +}: Props): Promise => { + 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 } } + ]) } - } - } 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 - }) + newResult = await ImageManipulator.manipulateAsync(result.uri, [ + { resize } + ]) + } + resolve(newResult) + } else { + resolve(result) + } + } - if (!result.cancelled) { - // https://github.com/expo/expo/issues/11214 - const fixResult = { - ...result, - uri: result.uri.replace('file:/data', 'file:///data') + showActionSheetWithOptions( + { + title: i18next.t('componentMediaSelector:title'), + options: [ + i18next.t('componentMediaSelector:options.library'), + i18next.t('componentMediaSelector:options.photo'), + i18next.t('componentMediaSelector:options.cancel') + ], + cancelButtonIndex: 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 + }) + + if (!result.cancelled) { + await resolveResult(result) + } + } + } 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) } - uploader(fixResult) - return } } } - } - ) + ) + }) } export default mediaSelector diff --git a/src/screens/Compose/Root/Footer/addAttachment.ts b/src/screens/Compose/Root/Footer/addAttachment.ts index fa2b4f22..72d99aee 100644 --- a/src/screens/Compose/Root/Footer/addAttachment.ts +++ b/src/screens/Compose/Root/Footer/addAttachment.ts @@ -123,7 +123,8 @@ const addAttachment = async ({ }) } - mediaSelector({ uploader, showActionSheetWithOptions }) + const result = await mediaSelector({ showActionSheetWithOptions }) + await uploader(result) } export default addAttachment diff --git a/src/screens/Tabs/Me/Profile/Root.tsx b/src/screens/Tabs/Me/Profile/Root.tsx index f91a3b06..ae735671 100644 --- a/src/screens/Tabs/Me/Profile/Root.tsx +++ b/src/screens/Tabs/Me/Profile/Root.tsx @@ -1,7 +1,10 @@ +import GracefullyImage from '@components/GracefullyImage' +import mediaSelector from '@components/mediaSelector' import { MenuContainer, MenuRow } from '@components/Menu' import { useActionSheet } from '@expo/react-native-action-sheet' import { StackScreenProps } from '@react-navigation/stack' import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile' +import * as ImagePicker from 'expo-image-picker' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { ScrollView } from 'react-native-gesture-handler' @@ -87,30 +90,48 @@ const TabMeProfileRoot: React.FC - // } - // loading={isLoading} - // iconBack='ChevronRight' + content={ + + } + loading={isLoading} + iconBack='ChevronRight' + onPress={async () => { + const image = await mediaSelector({ + showActionSheetWithOptions, + mediaTypes: ImagePicker.MediaTypeOptions.Images, + resize: { width: 400, height: 400 } + }) + mutate({ type: 'avatar', data: image.uri }) + }} /> - // } - // loading={isLoading} - // iconBack='ChevronRight' + content={ + + } + loading={isLoading} + iconBack='ChevronRight' + onPress={async () => { + const image = await mediaSelector({ + showActionSheetWithOptions, + mediaTypes: ImagePicker.MediaTypeOptions.Images, + resize: { width: 1500, height: 500 } + }) + mutate({ type: 'header', data: image.uri }) + }} /> { - navigation.navigate('Tab-Me-Profile-Note', { - note: data?.source?.note || '' - }) + data && + navigation.navigate('Tab-Me-Profile-Note', { + note: data.source?.note + }) }} />