diff options
author | Ivan Chen <ivan@tagg.id> | 2021-06-23 17:49:46 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-23 17:49:46 -0400 |
commit | 981051448fee6197544383e535fea7a72827d41d (patch) | |
tree | 10015427c4c62a1dbf73cad4dfd0ee2dd43b110c | |
parent | 8c2b915678b852f597c38ab00d18c22bf62d2051 (diff) | |
parent | f9a3acb40dd224591f8e7039e84e428d4363a841 (diff) |
Merge pull request #474 from IvanIFChen/tma944-video-moment-userflow
[TMA-944] Share Video Moment User Flow
-rw-r--r-- | src/components/moments/Moment.tsx | 60 | ||||
-rw-r--r-- | src/components/moments/MomentPostContent.tsx | 39 | ||||
-rw-r--r-- | src/routes/main/MainStackNavigator.tsx | 9 | ||||
-rw-r--r-- | src/screens/main/NotificationsScreen.tsx | 5 | ||||
-rw-r--r-- | src/screens/moments/TagFriendsScreen.tsx | 118 | ||||
-rw-r--r-- | src/screens/onboarding/BasicInfoOnboarding.tsx | 5 | ||||
-rw-r--r-- | src/screens/profile/CaptionScreen.tsx | 117 | ||||
-rw-r--r-- | src/services/MomentService.ts | 5 | ||||
-rw-r--r-- | src/types/types.ts | 1 |
9 files changed, 192 insertions, 167 deletions
diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx index e4acebdf..eab4b7e3 100644 --- a/src/components/moments/Moment.tsx +++ b/src/components/moments/Moment.tsx @@ -5,7 +5,6 @@ import {Text} from 'react-native-animatable'; import {ScrollView, TouchableOpacity} from 'react-native-gesture-handler'; import ImagePicker from 'react-native-image-crop-picker'; import LinearGradient from 'react-native-linear-gradient'; -import {useDispatch, useSelector} from 'react-redux'; import DeleteIcon from '../../assets/icons/delete-logo.svg'; import DownIcon from '../../assets/icons/down_icon.svg'; import BigPlusIcon from '../../assets/icons/plus-icon-white.svg'; @@ -13,12 +12,6 @@ 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 {loadUserMoments} from '../../store/actions'; -import {RootState} from '../../store/rootReducer'; import {MomentType, ScreenType} from '../../types'; import {normalize, SCREEN_WIDTH} from '../../utils'; import MomentTile from './MomentTile'; @@ -49,22 +42,18 @@ const Moment: React.FC<MomentProps> = ({ externalStyles, }) => { const navigation = useNavigation(); - const dispatch = useDispatch(); - const { - user: {userId}, - } = useSelector((state: RootState) => state.user); - const uploadVideo = async (filePath: string) => { + const navigateToCaptionScreenForVideo = (uri: string) => { const randHash = Math.random().toString(36).substring(7); - const filename = `poc_${randHash}.mov`; - const presignedURL = await handlePresignedURL(filename, title); - if (presignedURL) { - console.log('presigned' + JSON.stringify(presignedURL)); - Alert.alert('Upload begin in background...'); - await handleVideoUpload(filename, filePath, presignedURL); - Alert.alert('Finish uploading, refreshing moments...'); - dispatch(loadUserMoments(userId)); - } + navigation.navigate('CaptionScreen', { + screenType, + title, + media: { + filename: `poc_${randHash}.mov`, + uri, + isVideo: true, + }, + }); }; /** * This function opens the ImagePicker, only lets you select video files, @@ -75,23 +64,12 @@ const Moment: React.FC<MomentProps> = ({ */ const navigateToVideoPicker = () => { ImagePicker.openPicker({ - smartAlbums: [ - 'Favorites', - 'RecentlyAdded', - 'SelfPortraits', - 'Screenshots', - 'UserLibrary', - ], - cropperToolbarTitle: 'select a video', mediaType: 'video', }) .then(async (vid) => { - if ('path' in vid) { - console.log(vid); - // vid.path is compressed mp4, vid.sourceURL is uncompressed original - if (vid.path) { - uploadVideo(vid.path); - } + console.log(vid); + if (vid.path) { + navigateToCaptionScreenForVideo(vid.path); } }) .catch((err) => { @@ -117,11 +95,15 @@ const Moment: React.FC<MomentProps> = ({ mediaType: 'photo', }) .then((picture) => { - if ('path' in picture) { + if (picture.path && picture.filename) { navigation.navigate('CaptionScreen', { screenType, - title: title, - image: picture, + title, + media: { + filename: picture.filename, + uri: picture.path, + isVideo: false, + }, }); } }) @@ -182,7 +164,7 @@ const Moment: React.FC<MomentProps> = ({ }).then((vid) => { console.log(vid); if (vid.path) { - uploadVideo(vid.path); + navigateToCaptionScreenForVideo(vid.path); } }), }, diff --git a/src/components/moments/MomentPostContent.tsx b/src/components/moments/MomentPostContent.tsx index 27a68e47..4d2554d8 100644 --- a/src/components/moments/MomentPostContent.tsx +++ b/src/components/moments/MomentPostContent.tsx @@ -82,7 +82,6 @@ const MomentPostContent: React.FC<MomentPostContentProps> = ({ setHideText(false); } }, [keyboardVisible, hideText]); - return ( <View style={[styles.container, style]}> <TouchableWithoutFeedback @@ -91,23 +90,25 @@ const MomentPostContent: React.FC<MomentPostContentProps> = ({ setFadeValue(new Animated.Value(0)); }}> {isVideo ? ( - <Video - // ref={imageRef} - source={{ - uri: moment.moment_url, - }} - // HLS m3u8 version - // source={{ - // uri: 'https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8', - // }} - // mp4 version - // source={{ - // uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', - // }} - volume={1} - style={styles.image} - // style={styles.video} - /> + <View ref={imageRef}> + <Video + // ref={imageRef} + source={{ + uri: moment.moment_url, + }} + // HLS m3u8 version + // source={{ + // uri: 'https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8', + // }} + // mp4 version + // source={{ + // uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', + // }} + volume={1} + style={styles.image} + repeat={true} + /> + </View> ) : ( <Image ref={imageRef} @@ -123,7 +124,7 @@ const MomentPostContent: React.FC<MomentPostContentProps> = ({ /> )} </TouchableWithoutFeedback> - {visible && !isVideo && ( + {visible && ( <Animated.View style={[styles.tapTag, {opacity: fadeValue}]}> <MomentTags editing={false} diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx index 8fce5e2f..522a18dd 100644 --- a/src/routes/main/MainStackNavigator.tsx +++ b/src/routes/main/MainStackNavigator.tsx @@ -2,7 +2,6 @@ * Note the name userXId here, it refers to the id of the user being visited */ import {createStackNavigator} from '@react-navigation/stack'; -import {Image} from 'react-native-image-crop-picker'; import { CommentBaseType, MomentTagType, @@ -38,13 +37,17 @@ export type MainStackParams = { }; CaptionScreen: { title?: string; - image?: Image; + media?: {filename: string; uri: string; isVideo: boolean}; screenType: ScreenType; selectedTags?: MomentTagType[]; moment?: MomentType; }; TagFriendsScreen: { - imagePath: string; + media: { + filename: string; + uri: string; + isVideo: boolean; + }; selectedTags?: MomentTagType[]; }; TagSelectionScreen: { diff --git a/src/screens/main/NotificationsScreen.tsx b/src/screens/main/NotificationsScreen.tsx index 03842b0a..84c15f66 100644 --- a/src/screens/main/NotificationsScreen.tsx +++ b/src/screens/main/NotificationsScreen.tsx @@ -36,8 +36,9 @@ const NotificationsScreen: React.FC = () => { ); const [refreshing, setRefreshing] = useState(false); // used for figuring out which ones are unread - const [lastViewed, setLastViewed] = - useState<moment.Moment | undefined>(undefined); + const [lastViewed, setLastViewed] = useState<moment.Moment | undefined>( + undefined, + ); const {notifications} = useSelector( (state: RootState) => state.notifications, ); diff --git a/src/screens/moments/TagFriendsScreen.tsx b/src/screens/moments/TagFriendsScreen.tsx index 570c3776..bda38651 100644 --- a/src/screens/moments/TagFriendsScreen.tsx +++ b/src/screens/moments/TagFriendsScreen.tsx @@ -1,16 +1,9 @@ import {RouteProp} from '@react-navigation/core'; import {useNavigation} from '@react-navigation/native'; import React, {useEffect, useRef, useState} from 'react'; -import { - Image, - Keyboard, - KeyboardAvoidingView, - Platform, - StyleSheet, - TouchableWithoutFeedback, - View, -} from 'react-native'; +import {Image, StyleSheet, TouchableWithoutFeedback, View} from 'react-native'; import {Button} from 'react-native-elements'; +import Video from 'react-native-video'; import {MainStackParams} from 'src/routes'; import { CaptionScreenHeader, @@ -30,7 +23,7 @@ interface TagFriendsScreenProps { route: TagFriendsScreenRouteProps; } const TagFriendsScreen: React.FC<TagFriendsScreenProps> = ({route}) => { - const {imagePath, selectedTags} = route.params; + const {media, selectedTags} = route.params; const navigation = useNavigation(); const imageRef = useRef(null); const [tags, setTags] = useState<MomentTagType[]>([]); @@ -54,58 +47,62 @@ const TagFriendsScreen: React.FC<TagFriendsScreenProps> = ({route}) => { return ( <SearchBackground> - <TouchableWithoutFeedback onPress={Keyboard.dismiss}> - <KeyboardAvoidingView - behavior={Platform.OS === 'ios' ? 'padding' : 'height'} - style={styles.flex}> - <View style={styles.contentContainer}> - <View style={styles.buttonsContainer}> - <Button - title="Cancel" - buttonStyle={styles.button} - onPress={() => navigation.goBack()} - /> - <Button - title="Done" - titleStyle={styles.shareButtonTitle} - buttonStyle={styles.button} - onPress={handleDone} + <View style={styles.contentContainer}> + <View style={styles.buttonsContainer}> + <Button + title="Cancel" + buttonStyle={styles.button} + onPress={() => navigation.goBack()} + /> + <Button + title="Done" + titleStyle={styles.shareButtonTitle} + buttonStyle={styles.button} + onPress={handleDone} + /> + </View> + <CaptionScreenHeader + style={styles.header} + title={'Tap on photo to tag friends!'} + /> + <TouchableWithoutFeedback + onPress={() => + navigation.navigate('TagSelectionScreen', { + selectedTags: tags, + }) + }> + {media.isVideo ? ( + <View style={styles.media} ref={imageRef}> + <Video + style={styles.media} + source={{uri: media.uri}} + repeat={true} /> </View> - <CaptionScreenHeader - style={styles.header} - title={'Tap on photo to tag friends!'} + ) : ( + <Image + ref={imageRef} + style={styles.media} + source={{uri: media.uri}} + resizeMode={'cover'} /> - <TouchableWithoutFeedback - onPress={() => - navigation.navigate('TagSelectionScreen', { - selectedTags: tags, - }) - }> - <Image - ref={imageRef} - style={styles.image} - source={{uri: imagePath}} - resizeMode={'cover'} - /> - </TouchableWithoutFeedback> - {tags.length !== 0 && ( - <MomentTags - tags={tags} - setTags={setTags} - editing={true} - imageRef={imageRef} - deleteFromList={(user) => - setTags(tags.filter((tag) => tag.user.id !== user.id)) - } - /> - )} - <View style={styles.footerContainer}> - <TagFriendsFooter tags={tags} setTags={setTags} /> - </View> - </View> - </KeyboardAvoidingView> - </TouchableWithoutFeedback> + )} + </TouchableWithoutFeedback> + {tags.length !== 0 && ( + <MomentTags + tags={tags} + setTags={setTags} + editing={true} + imageRef={imageRef} + deleteFromList={(user) => + setTags(tags.filter((tag) => tag.user.id !== user.id)) + } + /> + )} + <View style={styles.footerContainer}> + <TagFriendsFooter tags={tags} setTags={setTags} /> + </View> + </View> </SearchBackground> ); }; @@ -113,7 +110,6 @@ const TagFriendsScreen: React.FC<TagFriendsScreenProps> = ({route}) => { const styles = StyleSheet.create({ contentContainer: { paddingTop: StatusBarHeight, - justifyContent: 'flex-end', }, buttonsContainer: { flexDirection: 'row', @@ -131,7 +127,7 @@ const styles = StyleSheet.create({ header: { marginVertical: 20, }, - image: { + media: { position: 'relative', width: SCREEN_WIDTH, aspectRatio: 1, diff --git a/src/screens/onboarding/BasicInfoOnboarding.tsx b/src/screens/onboarding/BasicInfoOnboarding.tsx index d5998ac1..4c8da021 100644 --- a/src/screens/onboarding/BasicInfoOnboarding.tsx +++ b/src/screens/onboarding/BasicInfoOnboarding.tsx @@ -71,8 +71,9 @@ const BasicInfoOnboarding: React.FC<BasicInfoOnboardingProps> = ({route}) => { const [invalidWithError, setInvalidWithError] = useState( 'Please enter a valid ', ); - const [autoCapitalize, setAutoCap] = - useState<'none' | 'sentences' | 'words' | 'characters' | undefined>('none'); + const [autoCapitalize, setAutoCap] = useState< + 'none' | 'sentences' | 'words' | 'characters' | undefined + >('none'); const [fadeValue, setFadeValue] = useState<Animated.Value<number>>( new Animated.Value(0), ); diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index 9e1b4674..d53570cb 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -15,6 +15,7 @@ import { } from 'react-native'; import {MentionInput} from 'react-native-controlled-mentions'; import {Button, normalize} from 'react-native-elements'; +import Video from 'react-native-video'; import {useDispatch, useSelector} from 'react-redux'; import FrontArrow from '../../assets/icons/front-arrow.svg'; import {SearchBackground} from '../../components'; @@ -27,7 +28,13 @@ import { SUCCESS_PIC_UPLOAD, } from '../../constants/strings'; import {MainStackParams} from '../../routes'; -import {patchMoment, postMoment, postMomentTags} from '../../services'; +import { + handlePresignedURL, + handleVideoUpload, + patchMoment, + postMoment, + postMomentTags, +} from '../../services'; import { loadUserMoments, updateProfileCompletionStage, @@ -51,7 +58,7 @@ interface CaptionScreenProps { } const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { - const {title, image, screenType, selectedTags, moment} = route.params; + const {title, screenType, selectedTags, moment} = route.params; const { user: {userId}, } = useSelector((state: RootState) => state.user); @@ -62,6 +69,17 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { selectedTags ? selectedTags : [], ); const [taggedList, setTaggedList] = useState<string>(''); + const mediaFilename = moment ? undefined : route.params.media!.filename; + const mediaUri = moment ? moment.moment_url : route.params.media!.uri; + // TODO: change this once moment refactor is done + const isMediaAVideo = moment + ? !( + moment.moment_url.endsWith('.jpg') || + moment.moment_url.endsWith('.JPG') || + moment.moment_url.endsWith('.png') || + moment.moment_url.endsWith('.PNG') + ) + : route.params.media?.isVideo ?? false; useEffect(() => { setTags(selectedTags ? selectedTags : []); @@ -120,36 +138,52 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { const handleShare = async () => { setLoading(true); - if (!image?.filename || !title) { - return; - } - const momentResponse = await postMoment( - image.filename, - image.path, - caption, - title, - userId, - ); - if (!momentResponse) { + if (moment || !mediaFilename || !title) { handleFailed(); return; } - const momentTagResponse = await postMomentTags( - momentResponse.moment_id, - formattedTags(), - ); - if (!momentTagResponse) { - handleFailed(); - return; + let profileCompletionStage; + let momentId; + // separate upload logic for image/video + if (isMediaAVideo) { + const presignedURL = await handlePresignedURL(mediaFilename, title); + if (!presignedURL) { + handleFailed(); + return; + } + momentId = presignedURL.moment_id; + // TODO: assume success for now + await handleVideoUpload(mediaFilename, mediaUri, presignedURL); + } else { + const momentResponse = await postMoment( + mediaFilename, + mediaUri, + caption, + title, + userId, + ); + if (!momentResponse) { + handleFailed(); + return; + } + profileCompletionStage = momentResponse.profile_completion_stage; + momentId = momentResponse.moment_id; + } + if (momentId) { + const momentTagResponse = await postMomentTags(momentId, formattedTags()); + if (!momentTagResponse) { + handleFailed(); + return; + } } dispatch(loadUserMoments(userId)); - dispatch( - updateProfileCompletionStage(momentResponse.profile_completion_stage), - ); + if (profileCompletionStage) { + dispatch(updateProfileCompletionStage(profileCompletionStage)); + } handleSuccess(); }; - const handleDone = async () => { + const handleDoneEditing = async () => { setLoading(true); if (moment?.moment_id) { const success = await patchMoment( @@ -186,19 +220,26 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { title={moment ? 'Done' : 'Share'} titleStyle={styles.shareButtonTitle} buttonStyle={styles.button} - onPress={moment ? handleDone : handleShare} + onPress={moment ? handleDoneEditing : handleShare} /> </View> <CaptionScreenHeader style={styles.header} - {...{title: moment ? moment.moment_category : title}} - /> - {/* this is the image we want to center our tags' initial location within */} - <Image - style={styles.image} - source={{uri: moment ? moment.moment_url : image?.path}} - resizeMode={'cover'} + {...{title: moment ? moment.moment_category : title ?? ''}} /> + {isMediaAVideo ? ( + <Video + style={styles.media} + source={{uri: mediaUri}} + repeat={true} + /> + ) : ( + <Image + style={styles.media} + source={{uri: mediaUri}} + resizeMode={'cover'} + /> + )} <MentionInput containerStyle={styles.text} placeholder="Write something....." @@ -210,11 +251,11 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { <TouchableOpacity onPress={() => navigation.navigate('TagFriendsScreen', { - imagePath: moment - ? moment.moment_url - : image - ? image.path - : '', + media: { + filename: mediaFilename ?? '', + uri: mediaUri, + isVideo: isMediaAVideo, + }, selectedTags: tags, }) } @@ -257,7 +298,7 @@ const styles = StyleSheet.create({ header: { marginVertical: 20, }, - image: { + media: { position: 'relative', width: SCREEN_WIDTH, aspectRatio: 1, diff --git a/src/services/MomentService.ts b/src/services/MomentService.ts index da1bfb97..ca32a3f3 100644 --- a/src/services/MomentService.ts +++ b/src/services/MomentService.ts @@ -311,7 +311,7 @@ export const handleVideoUpload = async ( // let data = await response.json(); if (status === 200 || status === 204) { console.log('complete'); - return response; + return true; } else { if (status === 404) { console.log( @@ -323,11 +323,10 @@ export const handleVideoUpload = async ( 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; } + return false; }; diff --git a/src/types/types.ts b/src/types/types.ts index f6f23fc8..416d9146 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -379,4 +379,5 @@ export type PresignedURLResponse = { 'x-amz-signature': string; }; }; + moment_id: string; }; |