diff options
-rw-r--r-- | Makefile | 14 | ||||
-rw-r--r-- | src/components/moments/Moment.tsx | 52 | ||||
-rw-r--r-- | src/constants/api.ts | 1 | ||||
-rw-r--r-- | src/services/MomentService.ts | 129 | ||||
-rw-r--r-- | src/types/types.ts | 15 |
5 files changed, 209 insertions, 2 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..b0022598 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +build: + yarn + cd ios && pod install && cd .. + +clean: + -rm *.lock + -rm ios/*.lock + +deep_clean: + -rm *.lock + -rm ios/*.lock + -rm -rf /Users/ivan/Library/Developer/Xcode/DerivedData + -rm -rf node_modules + yarn cache clean diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx index cde5b2e0..34b2c7ea 100644 --- a/src/components/moments/Moment.tsx +++ b/src/components/moments/Moment.tsx @@ -7,11 +7,15 @@ import ImagePicker from 'react-native-image-crop-picker'; import LinearGradient from 'react-native-linear-gradient'; import DeleteIcon from '../../assets/icons/delete-logo.svg'; import DownIcon from '../../assets/icons/down_icon.svg'; -import PlusIcon from '../../assets/icons/plus-icon.svg'; import BigPlusIcon from '../../assets/icons/plus-icon-white.svg'; +import PlusIcon from '../../assets/icons/plus-icon.svg'; import UpIcon from '../../assets/icons/up_icon.svg'; import {TAGG_LIGHT_BLUE} from '../../constants'; import {ERROR_UPLOAD} from '../../constants/strings'; +import { + handlePresignedURL, + handleVideoUpload, +} from '../../services/MomentService'; import {MomentType, ScreenType} from '../../types'; import {normalize, SCREEN_WIDTH} from '../../utils'; import MomentTile from './MomentTile'; @@ -42,6 +46,45 @@ const Moment: React.FC<MomentProps> = ({ externalStyles, }) => { const navigation = useNavigation(); + /** + * This function opens the ImagePicker, only lets you select video files, + * formats the file extension, then makes a call to the server to get the presigned URL, + * after which it makes a POST request to the returned URL to upload the file directly to S3. + * params: none + * @returns: none + */ + const navigateToVideoPicker = () => { + ImagePicker.openPicker({ + smartAlbums: [ + 'Favorites', + 'RecentlyAdded', + 'SelfPortraits', + 'Screenshots', + 'UserLibrary', + ], + width: 580, + height: 580, + cropping: false, + cropperToolbarTitle: 'select a video', + mediaType: 'video', + }) + .then(async (vid) => { + if ('path' in vid) { + let fileName = vid.filename || ''; + if (fileName.endsWith('.heic') || fileName.endsWith('.HEIC')) { + fileName = fileName.split('.')[0] + '.jpg'; + } + let presignedURL = await handlePresignedURL(fileName, title); + console.log('presigned' + JSON.stringify(presignedURL)); + handleVideoUpload(vid, presignedURL); + } + }) + .catch((err) => { + if (err.code && err.code !== 'E_PICKER_CANCELLED') { + Alert.alert(ERROR_UPLOAD); + } + }); + }; const navigateToImagePicker = () => { ImagePicker.openPicker({ @@ -110,6 +153,13 @@ const Moment: React.FC<MomentProps> = ({ <PlusIcon width={23} height={23} + onPress={() => navigateToVideoPicker()} + color={'black'} + style={styles.horizontalMargin} + /> + <PlusIcon + width={23} + height={23} onPress={() => navigateToImagePicker()} color={TAGG_LIGHT_BLUE} style={styles.horizontalMargin} diff --git a/src/constants/api.ts b/src/constants/api.ts index b55489d9..6dab1153 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -38,6 +38,7 @@ export const VERIFY_INVITATION_CODE_ENDPOUNT: string = API_URL + 'verify-code/'; export const COMMENTS_ENDPOINT: string = API_URL + 'comments/'; export const COMMENT_REACTIONS_ENDPOINT: string = API_URL + 'reaction-comment/'; export const COMMENT_REACTIONS_REPLY_ENDPOINT: string = API_URL + 'reaction-reply/'; +export const PRESIGNED_URL_ENDPOINT: string = API_URL + 'presigned-url/'; export const FRIENDS_ENDPOINT: string = API_URL + 'friends/'; export const ALL_USERS_ENDPOINT: string = API_URL + 'users/'; export const REPORT_ISSUE_ENDPOINT: string = API_URL + 'report/'; diff --git a/src/services/MomentService.ts b/src/services/MomentService.ts index b837585a..d0ed56ab 100644 --- a/src/services/MomentService.ts +++ b/src/services/MomentService.ts @@ -1,12 +1,19 @@ import AsyncStorage from '@react-native-community/async-storage'; +import {Image, Video} from 'react-native-image-crop-picker'; import RNFetchBlob from 'rn-fetch-blob'; import { MOMENTS_ENDPOINT, MOMENTTAG_ENDPOINT, MOMENT_TAGS_ENDPOINT, MOMENT_THUMBNAIL_ENDPOINT, + PRESIGNED_URL_ENDPOINT, + TAGG_CUSTOMER_SUPPORT, } from '../constants'; -import {MomentPostType, MomentTagType} from '../types'; +import { + ERROR_SOMETHING_WENT_WRONG, + ERROR_SOMETHING_WENT_WRONG_REFRESH, +} from '../constants/strings'; +import {MomentPostType, MomentTagType, PresignedURLResponse} from '../types'; import {checkImageUploadStatus} from '../utils'; export const postMoment = async ( @@ -18,6 +25,7 @@ export const postMoment = async ( ) => { try { const request = new FormData(); + //Manipulating filename to end with .jpg instead of .heic if (fileName.endsWith('.heic') || fileName.endsWith('.HEIC')) { fileName = fileName.split('.')[0] + '.jpg'; @@ -208,3 +216,122 @@ export const deleteMomentTag = async (moment_tag_id: string) => { return false; } }; +/** + * This function makes a request to the server in order to provide the client with a presigned URL. + * This is called first, in order for the client to directly upload a file to S3 + * @param value: string | undefined + * @param filename: string | undefined + * @returns a PresignedURLResponse object + */ +export const handlePresignedURL = async ( + filename: string | undefined, + momentCategory: string, +) => { + try { + const token = await AsyncStorage.getItem('token'); + const response = await fetch(PRESIGNED_URL_ENDPOINT, { + method: 'POST', + headers: { + Authorization: 'Token ' + token, + }, + body: JSON.stringify({ + filename, + category: momentCategory, + }), + }); + const status = response.status; + let data: PresignedURLResponse = await response.json(); + if (status === 200) { + console.log('done'); + return data; + } else { + if (status === 404) { + console.log( + `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${TAGG_CUSTOMER_SUPPORT}`, + ); + } else { + console.log(ERROR_SOMETHING_WENT_WRONG_REFRESH); + } + console.log(response); + } + } catch (error) { + console.log(error); + console.log(ERROR_SOMETHING_WENT_WRONG); + } +}; +/** + * This util function takes in the file object and the PresignedURLResponse object, creates form data from the latter, + * and makes a post request to the presigned URL, sending the file object inside of the form data. + * @param file: Video, Image, Undefined + * @param urlObj PresignedURLResponse | undefined + * @returns responseURL or boolean + */ +export const handleVideoUpload = async ( + file: Video | Image | undefined, + urlObj: PresignedURLResponse | undefined, +) => { + try { + let fileName = file?.filename; + if (fileName === null || '') { + console.log('Invalid filename'); + return false; + } + if (urlObj === null || urlObj === undefined) { + console.log('Invalid urlObj'); + return false; + } + //build formData for POST request + // Could not get a forEach to work and could not assign directly, will look into cleaning this series of appends up later. + const form = new FormData(); + form.append('key', urlObj.response_url.fields.key); + form.append( + 'x-amz-algorithm', + urlObj.response_url.fields['x-amz-algorithm'], + ); + form.append( + 'x-amz-credential', + urlObj.response_url.fields['x-amz-credential'], + ); + form.append('x-amz-date', urlObj.response_url.fields['x-amz-date']); + form.append('policy', urlObj.response_url.fields.policy); + form.append( + 'x-amz-signature', + urlObj.response_url.fields['x-amz-signature'], + ); + form.append('file', { + uri: file?.sourceURL, + // other types such as 'quicktime' 'image' etc exist, and we can programmatically type this, but for now sticking with simple 'video' + type: 'video', + name: fileName, + }); + const response = await fetch(urlObj.response_url.url, { + method: 'POST', + headers: { + 'Content-Type': 'multipart/form-data', + }, + body: form, + }); + const status = response.status; + // let data = await response.json(); + if (status === 200 || status === 204) { + console.log('complete'); + return response; + } else { + if (status === 404) { + console.log( + `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${TAGG_CUSTOMER_SUPPORT}`, + ); + } else { + console.log('FFFFFF \n'); + console.log(response); + console.log(ERROR_SOMETHING_WENT_WRONG_REFRESH); + } + console.log(response); + return false; + } + } catch (error) { + console.log(error); + console.log(ERROR_SOMETHING_WENT_WRONG); + return false; + } +}; diff --git a/src/types/types.ts b/src/types/types.ts index 171a9ff3..f6f23fc8 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -365,3 +365,18 @@ export type ReactionType = { id: string; type: ReactionOptionsType; }; +// used to handle direct S3 uploads by packaging presigned_url info into one object +export type PresignedURLResponse = { + response_msg: string; + response_url: { + url: string; + fields: { + key: string | undefined; + 'x-amz-algorithm': string; + 'x-amz-credential': string; + 'x-amz-date': string; + policy: string; + 'x-amz-signature': string; + }; + }; +}; |