aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-06-23 17:49:46 -0400
committerGitHub <noreply@github.com>2021-06-23 17:49:46 -0400
commit981051448fee6197544383e535fea7a72827d41d (patch)
tree10015427c4c62a1dbf73cad4dfd0ee2dd43b110c
parent8c2b915678b852f597c38ab00d18c22bf62d2051 (diff)
parentf9a3acb40dd224591f8e7039e84e428d4363a841 (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.tsx60
-rw-r--r--src/components/moments/MomentPostContent.tsx39
-rw-r--r--src/routes/main/MainStackNavigator.tsx9
-rw-r--r--src/screens/main/NotificationsScreen.tsx5
-rw-r--r--src/screens/moments/TagFriendsScreen.tsx118
-rw-r--r--src/screens/onboarding/BasicInfoOnboarding.tsx5
-rw-r--r--src/screens/profile/CaptionScreen.tsx117
-rw-r--r--src/services/MomentService.ts5
-rw-r--r--src/types/types.ts1
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;
};