From 142c84c7c45411b9badf7da3182c9e4bd0e96e38 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Tue, 20 Jul 2021 17:35:20 -0400 Subject: Add gradient progress bar --- src/store/initialStates.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/store') diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts index 92a1e456..5ae62838 100644 --- a/src/store/initialStates.ts +++ b/src/store/initialStates.ts @@ -10,6 +10,7 @@ import { import { CommentThreadType, MomentPostType, + MomentUploadStatusType, UniversityType, } from './../types/types'; @@ -48,6 +49,7 @@ export const NO_USER_DATA = { profile: NO_PROFILE, avatar: undefined, cover: undefined, + momentUploadStatus: MomentUploadStatusType.Empty, isOnboardedUser: false, newVersionAvailable: false, newNotificationReceived: false, -- cgit v1.2.3-70-g09d2 From 9b94f60df0b62a9d3762a1963ec7dac024658a51 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Wed, 21 Jul 2021 19:11:32 -0400 Subject: Update progress bar type --- src/components/moments/MomentUploadProgressBar.tsx | 16 +++++++++++----- src/constants/api.ts | 1 + src/store/initialStates.ts | 3 ++- src/types/types.ts | 7 ++++++- 4 files changed, 20 insertions(+), 7 deletions(-) (limited to 'src/store') diff --git a/src/components/moments/MomentUploadProgressBar.tsx b/src/components/moments/MomentUploadProgressBar.tsx index 285f4e84..07d876e8 100644 --- a/src/components/moments/MomentUploadProgressBar.tsx +++ b/src/components/moments/MomentUploadProgressBar.tsx @@ -13,17 +13,23 @@ interface MomentUploadProgressBarProps {} const MomentUploadProgressBar: React.FC = ({}) => { - const {momentUploadStatus} = useSelector((state: RootState) => state.user); - const progress = useSharedValue(0); + const {momentUploadProgressBar} = useSelector( + (state: RootState) => state.user, + ); + const progress = useSharedValue(0.001); useEffect(() => { - if (momentUploadStatus === MomentUploadStatusType.Uploading) { + if ( + momentUploadProgressBar?.status === MomentUploadStatusType.Uploading + ) { progress.value = withTiming(1, { - duration: 30 * 1000, + duration: momentUploadProgressBar.originalVideoDuration * 1000, easing: Easing.out(Easing.quad), }); } - }, [momentUploadStatus]); + }, [momentUploadProgressBar?.status]); + + useEffect(() => {}, []); return ( diff --git a/src/constants/api.ts b/src/constants/api.ts index 6dab1153..ec2f0897 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -39,6 +39,7 @@ 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 CHECK_MOMENT_UPLOAD_FINISHED_ENDPOINT: string = API_URL + 'moments/check_upload_finished/'; 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/store/initialStates.ts b/src/store/initialStates.ts index 5ae62838..ddfdf5d2 100644 --- a/src/store/initialStates.ts +++ b/src/store/initialStates.ts @@ -10,6 +10,7 @@ import { import { CommentThreadType, MomentPostType, + MomentUploadProgressBarType, MomentUploadStatusType, UniversityType, } from './../types/types'; @@ -49,7 +50,7 @@ export const NO_USER_DATA = { profile: NO_PROFILE, avatar: undefined, cover: undefined, - momentUploadStatus: MomentUploadStatusType.Empty, + momentUploadProgressBar: undefined, isOnboardedUser: false, newVersionAvailable: false, newNotificationReceived: false, diff --git a/src/types/types.ts b/src/types/types.ts index 930f833b..2001426a 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -61,8 +61,13 @@ export interface ProfileInfoType { is_private: boolean; } +export interface MomentUploadProgressBarType { + status: MomentUploadStatusType; + originalVideoDuration: number; + momentId: string; +} + export enum MomentUploadStatusType { - Empty = 'Empty', Uploading = 'Uploading', Done = 'Done', Error = 'Error', -- cgit v1.2.3-70-g09d2 From bc82aaf481949d690af814b9dd4a0e9cab387011 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Thu, 22 Jul 2021 15:09:39 -0400 Subject: Rename util function, Pass video duration to caption screen --- src/components/camera/GalleryIcon.tsx | 4 ++-- src/components/moments/TrimmerPlayer.tsx | 2 +- src/routes/main/MainStackNavigator.tsx | 2 +- src/screens/upload/EditMedia.tsx | 14 ++++++++++---- src/store/reducers/userReducer.ts | 5 +++++ src/types/types.ts | 2 +- src/utils/camera.ts | 2 +- 7 files changed, 21 insertions(+), 10 deletions(-) (limited to 'src/store') diff --git a/src/components/camera/GalleryIcon.tsx b/src/components/camera/GalleryIcon.tsx index ca2d2559..44297d6d 100644 --- a/src/components/camera/GalleryIcon.tsx +++ b/src/components/camera/GalleryIcon.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {Image, Text, TouchableOpacity, View} from 'react-native'; -import {navigateToImagePicker} from '../../utils/camera'; +import {navigateToMediaPicker} from '../../utils/camera'; import {ImageOrVideo} from 'react-native-image-crop-picker'; import {styles} from './styles'; @@ -19,7 +19,7 @@ export const GalleryIcon: React.FC = ({ }) => { return ( navigateToImagePicker(callback)} + onPress={() => navigateToMediaPicker(callback)} style={styles.saveButton}> {mostRecentPhotoUri !== '' ? ( = ({ repeat={true} onLoad={(payload) => { setEnd(payload.duration); - handleLoad(payload.naturalSize); + handleLoad(payload.naturalSize, payload.duration); }} onProgress={(e) => { if (!paused) { diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx index 11e9d08d..585980b5 100644 --- a/src/routes/main/MainStackNavigator.tsx +++ b/src/routes/main/MainStackNavigator.tsx @@ -46,7 +46,7 @@ export type MainStackParams = { }; CaptionScreen: { screenType: ScreenType; - media?: {uri: string; isVideo: boolean}; + media?: {uri: string; isVideo: boolean; videoDuration: number | undefined}; selectedCategory?: string; selectedTags?: MomentTagType[]; moment?: MomentType; diff --git a/src/screens/upload/EditMedia.tsx b/src/screens/upload/EditMedia.tsx index 1dc408ee..38450337 100644 --- a/src/screens/upload/EditMedia.tsx +++ b/src/screens/upload/EditMedia.tsx @@ -43,6 +43,7 @@ export const EditMedia: React.FC = ({route, navigation}) => { const vidRef = useRef(null); const [cropLoading, setCropLoading] = useState(false); const [hideTrimmer, setHideTrimmer] = useState(true); + const [videoDuration, setVideoDuration] = useState(); // Stores the coordinates of the cropped image const [x0, setX0] = useState(); @@ -139,7 +140,7 @@ export const EditMedia: React.FC = ({route, navigation}) => { mediaUri, (croppedURL: string) => { setCropLoading(false); - // Pass the trimmed/cropped video + // Pass the cropped video callback(croppedURL); }, videoCrop, @@ -334,8 +335,12 @@ export const EditMedia: React.FC = ({route, navigation}) => { height: SCREEN_WIDTH / aspectRatio, }, ]} - handleLoad={(response: {width: number; height: number}) => { + handleLoad={( + response: {width: number; height: number}, + duration: number, + ) => { const {width, height} = response; + setVideoDuration(duration); setOrigDimensions([width, height]); setAspectRatio(width / height); }} @@ -383,8 +388,9 @@ export const EditMedia: React.FC = ({route, navigation}) => { navigation.navigate('CaptionScreen', { screenType, media: { - uri: uri, - isVideo: isVideo, + uri, + isVideo, + videoDuration, }, selectedCategory, }), diff --git a/src/store/reducers/userReducer.ts b/src/store/reducers/userReducer.ts index 4692c5d3..617c60be 100644 --- a/src/store/reducers/userReducer.ts +++ b/src/store/reducers/userReducer.ts @@ -85,6 +85,10 @@ const userDataSlice = createSlice({ state.avatar = ''; state.cover = ''; }, + + setMomentUploadProgressBar: (state, action) => { + state.momentUploadProgressBar = action.payload.momentUploadProgressBar; + }, }, }); @@ -102,5 +106,6 @@ export const { clearHeaderAndProfileImages, profileBadgesUpdated, profileBadgeRemoved, + setMomentUploadProgressBar, } = userDataSlice.actions; export const userDataReducer = userDataSlice.reducer; diff --git a/src/types/types.ts b/src/types/types.ts index 2001426a..34bf73ac 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -63,8 +63,8 @@ export interface ProfileInfoType { export interface MomentUploadProgressBarType { status: MomentUploadStatusType; - originalVideoDuration: number; momentId: string; + originalVideoDuration: number | undefined; } export enum MomentUploadStatusType { diff --git a/src/utils/camera.ts b/src/utils/camera.ts index 9d7ff67f..97592fe5 100644 --- a/src/utils/camera.ts +++ b/src/utils/camera.ts @@ -57,7 +57,7 @@ export const saveImageToGallery = ( .catch((_err) => Alert.alert('Failed to save to device!')); }; -export const navigateToImagePicker = ( +export const navigateToMediaPicker = ( callback: (media: ImageOrVideo) => void, ) => { ImagePicker.openPicker({ -- cgit v1.2.3-70-g09d2 From 848204d684f6dfb8ada0856a50487d00ad2c77df Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Thu, 22 Jul 2021 16:08:45 -0400 Subject: Add action for handling all video uploads --- src/screens/profile/CaptionScreen.tsx | 39 ++++++++------ src/store/actions/user.ts | 98 ++++++++++++++++++++++++++++++++++- src/store/initialStates.ts | 1 - src/types/types.ts | 3 +- 4 files changed, 120 insertions(+), 21 deletions(-) (limited to 'src/store') diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index 7f77bdca..88ff0ecc 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -42,6 +42,7 @@ import { postMomentTags, } from '../../services'; import { + handleVideoMomentUpload, loadUserMoments, updateProfileCompletionStage, } from '../../store/actions'; @@ -76,6 +77,8 @@ const CaptionScreen: React.FC = ({route, navigation}) => { const [tags, setTags] = useState([]); const [taggedUsersText, setTaggedUsersText] = useState(''); const [momentCategory, setMomentCategory] = useState(); + // only used for upload purposes, undefined for editing is fine + const videoDuration = moment ? undefined : route.params.media!.videoDuration; const mediaUri = moment ? moment.moment_url : route.params.media!.uri; // TODO: change this once moment refactor is done const isMediaAVideo = moment @@ -166,18 +169,17 @@ const CaptionScreen: React.FC = ({route, navigation}) => { return; } let profileCompletionStage; - let momentId; // separate upload logic for image/video if (isMediaAVideo) { - const presignedURLResponse = await handlePresignedURL(momentCategory); - if (!presignedURLResponse) { - handleFailed(); - return; - } - momentId = presignedURLResponse.moment_id; - const fileHash = presignedURLResponse.response_url.fields.key; - if (fileHash !== null && fileHash !== '' && fileHash !== undefined) { - await handleVideoUpload(mediaUri, presignedURLResponse); + if (videoDuration) { + dispatch( + handleVideoMomentUpload( + mediaUri, + videoDuration, + momentCategory, + formattedTags(), + ), + ); } else { handleFailed(); } @@ -193,13 +195,16 @@ const CaptionScreen: React.FC = ({route, navigation}) => { return; } profileCompletionStage = momentResponse.profile_completion_stage; - momentId = momentResponse.moment_id; - } - if (momentId) { - const momentTagResponse = await postMomentTags(momentId, formattedTags()); - if (!momentTagResponse) { - handleFailed(); - return; + const momentId = momentResponse.moment_id; + if (momentId) { + const momentTagResponse = await postMomentTags( + momentId, + formattedTags(), + ); + if (!momentTagResponse) { + handleFailed(); + return; + } } } if (!isMediaAVideo) { diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts index b1cb8719..1acbb519 100644 --- a/src/store/actions/user.ts +++ b/src/store/actions/user.ts @@ -1,13 +1,21 @@ import AsyncStorage from '@react-native-community/async-storage'; -import {StreamChat} from 'stream-chat'; import {Action, ThunkAction} from '@reduxjs/toolkit'; +import {StreamChat} from 'stream-chat'; import { getProfilePic, + handlePresignedURL, + handleVideoUpload, loadProfileInfo, + postMomentTags, removeBadgesService, sendSuggestedPeopleLinked, } from '../../services'; -import {UniversityBadge, UserType} from '../../types/types'; +import { + MomentUploadProgressBarType, + MomentUploadStatusType, + UniversityBadge, + UserType, +} from '../../types/types'; import {getTokenOrLogout} from '../../utils'; import { clearHeaderAndProfileImages, @@ -15,6 +23,7 @@ import { profileBadgesUpdated, profileCompletionStageUpdated, setIsOnboardedUser, + setMomentUploadProgressBar, setNewNotificationReceived, setNewVersionAvailable, setReplyPosted, @@ -275,3 +284,88 @@ export const suggestedPeopleAnimatedTutorialFinished = ); } }; + +/** + * state is now UploadingToS3: + * - get presigned url (backend creates the moment object) + * - upload moment tags + * - upload video to s3 + * state is now WaitingForDoneProcessing + */ +export const handleVideoMomentUpload = + ( + videoUri: string, + videoLength: number, + momentCategory: string, + formattedTags: { + x: number; + y: number; + z: number; + user_id: string; + }[], + ): ThunkAction, RootState, unknown, Action> => + async (dispatch) => { + try { + const handleError = (reason: string) => { + console.error('Moment video upload failed,', reason); + dispatch({ + type: setMomentUploadProgressBar.type, + payload: { + momentUploadProgressBar: { + ...momentUploadProgressBar, + status: MomentUploadStatusType.Error, + }, + }, + }); + }; + let momentUploadProgressBar: MomentUploadProgressBarType = { + status: MomentUploadStatusType.UploadingToS3, + momentId: '', + originalVideoDuration: videoLength, + }; + // set progress bar as loading + dispatch({ + type: setMomentUploadProgressBar.type, + payload: {momentUploadProgressBar}, + }); + // get a presigned url for the video + const presignedURLResponse = await handlePresignedURL(momentCategory); + if (!presignedURLResponse) { + handleError('Presigned URL failed'); + return; + } + const momentId = presignedURLResponse.moment_id; + const fileHash = presignedURLResponse.response_url.fields.key; + // upload moment tags, now that we have a moment id + const momentTagResponse = await postMomentTags(momentId, formattedTags); + if (!momentTagResponse) { + handleError('Upload moment tags failed'); + return; + } + if (!fileHash) { + handleError('Unable to parse file hash from presigned response'); + return; + } + // upload video to s3 + const videoUploadResponse = await handleVideoUpload( + videoUri, + presignedURLResponse, + ); + if (!videoUploadResponse) { + handleError('Video upload failed'); + return; + } + dispatch({ + type: setMomentUploadProgressBar.type, + payload: { + momentUploadProgressBar: { + ...momentUploadProgressBar, + status: MomentUploadStatusType.WaitingForDoneProcessing, + momentId, + }, + }, + }); + } catch (error) { + console.log(error); + } + }; diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts index ddfdf5d2..7d8cf439 100644 --- a/src/store/initialStates.ts +++ b/src/store/initialStates.ts @@ -11,7 +11,6 @@ import { CommentThreadType, MomentPostType, MomentUploadProgressBarType, - MomentUploadStatusType, UniversityType, } from './../types/types'; diff --git a/src/types/types.ts b/src/types/types.ts index 34bf73ac..685e3784 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -68,7 +68,8 @@ export interface MomentUploadProgressBarType { } export enum MomentUploadStatusType { - Uploading = 'Uploading', + UploadingToS3 = 'UploadingToS3', + WaitingForDoneProcessing = 'WaitingForDoneProcessing', Done = 'Done', Error = 'Error', } -- cgit v1.2.3-70-g09d2