aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/camera/GalleryIcon.tsx4
-rw-r--r--src/components/common/GradientProgressBar.tsx48
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/moments/MomentPost.tsx28
-rw-r--r--src/components/moments/MomentUploadProgressBar.tsx221
-rw-r--r--src/components/moments/TrimmerPlayer.tsx7
-rw-r--r--src/components/moments/index.ts1
-rw-r--r--src/components/profile/Content.tsx2
-rw-r--r--src/components/profile/ProfileBadges.tsx8
9 files changed, 310 insertions, 10 deletions
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<GalleryIconProps> = ({
}) => {
return (
<TouchableOpacity
- onPress={() => navigateToImagePicker(callback)}
+ onPress={() => navigateToMediaPicker(callback)}
style={styles.saveButton}>
{mostRecentPhotoUri !== '' ? (
<Image
diff --git a/src/components/common/GradientProgressBar.tsx b/src/components/common/GradientProgressBar.tsx
new file mode 100644
index 00000000..fc62bd3c
--- /dev/null
+++ b/src/components/common/GradientProgressBar.tsx
@@ -0,0 +1,48 @@
+import React, {FC} from 'react';
+import {StyleSheet, ViewProps, ViewStyle} from 'react-native';
+import LinearGradient from 'react-native-linear-gradient';
+import Animated, {useAnimatedStyle} from 'react-native-reanimated';
+import {
+ TAGG_LIGHT_BLUE_2,
+ TAGG_LIGHT_BLUE_3,
+ TAGG_PURPLE,
+} from '../../constants';
+import {normalize} from '../../utils';
+
+interface GradientProgressBarProps extends ViewProps {
+ progress: Animated.SharedValue<number>;
+}
+
+const GradientProgressBar: FC<GradientProgressBarProps> = ({
+ style,
+ progress,
+}) => {
+ const animatedProgressStyle = useAnimatedStyle<ViewStyle>(() => ({
+ width: `${(1 - progress.value) * 100}%`,
+ }));
+ return (
+ <LinearGradient
+ style={[styles.bar, style]}
+ useAngle={true}
+ colors={[TAGG_PURPLE, TAGG_LIGHT_BLUE_2]}>
+ <Animated.View style={[styles.blank, animatedProgressStyle]} />
+ </LinearGradient>
+ );
+};
+const styles = StyleSheet.create({
+ container: {
+ borderRadius: 6.5,
+ },
+ bar: {
+ height: normalize(10),
+ borderRadius: 6.5,
+ },
+ blank: {
+ alignSelf: 'flex-end',
+ height: normalize(10),
+ width: '80%',
+ backgroundColor: TAGG_LIGHT_BLUE_3,
+ },
+});
+
+export default GradientProgressBar;
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index 4f5c0232..5edbb3ad 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -29,3 +29,4 @@ export {default as TaggUserRowCell} from './TaggUserRowCell';
export {default as LikeButton} from './LikeButton';
export {default as TaggUserSelectionCell} from './TaggUserSelectionCell';
export {default as MomentTags} from './MomentTags';
+export {default as GradientProgressBar} from './GradientProgressBar';
diff --git a/src/components/moments/MomentPost.tsx b/src/components/moments/MomentPost.tsx
index 2e5807f4..f6917784 100644
--- a/src/components/moments/MomentPost.tsx
+++ b/src/components/moments/MomentPost.tsx
@@ -46,12 +46,14 @@ interface MomentPostProps {
moment: MomentPostType;
userXId: string | undefined;
screenType: ScreenType;
+ updateMomentViewCount: () => void;
}
const MomentPost: React.FC<MomentPostProps> = ({
moment,
userXId,
screenType,
+ updateMomentViewCount,
}) => {
const navigation = useNavigation();
const dispatch = useDispatch();
@@ -197,11 +199,18 @@ const MomentPost: React.FC<MomentPostProps> = ({
screenType={screenType}
editable={false}
/>
- <Text style={styles.headerText}>{user.username}</Text>
+ <View style={styles.profilePreviewContainer}>
+ <Text style={styles.headerText}>{user.username}</Text>
+ <Text style={styles.viewCount}>
+ {moment.view_count <= 9999
+ ? `${moment.view_count} Views`
+ : `${(moment.view_count / 1000).toFixed(1)}K Views`}
+ </Text>
+ </View>
</TouchableOpacity>
</View>
),
- [user.username],
+ [user.username, moment.view_count],
);
const momentMedia = isVideo ? (
@@ -227,6 +236,8 @@ const MomentPost: React.FC<MomentPostProps> = ({
setVideoProgress(localProgress);
}
}}
+ onEnd={updateMomentViewCount}
+
/>
</View>
) : (
@@ -432,7 +443,17 @@ const styles = StyleSheet.create({
fontSize: 15,
fontWeight: 'bold',
color: 'white',
- paddingHorizontal: '3%',
+ },
+ viewCount: {
+ height: normalize(12),
+ left: 0,
+ top: '8%',
+ fontSize: 11,
+ fontWeight: '600',
+ lineHeight: 13,
+ letterSpacing: 0.08,
+ textAlign: 'left',
+ color: '#fff',
},
header: {
alignItems: 'center',
@@ -500,6 +521,7 @@ const styles = StyleSheet.create({
position: 'absolute',
top: isIPhoneX() ? 75 : 70,
},
+ profilePreviewContainer: {paddingHorizontal: '3%'},
});
export default MomentPost;
diff --git a/src/components/moments/MomentUploadProgressBar.tsx b/src/components/moments/MomentUploadProgressBar.tsx
new file mode 100644
index 00000000..d56a8337
--- /dev/null
+++ b/src/components/moments/MomentUploadProgressBar.tsx
@@ -0,0 +1,221 @@
+import React, {useEffect} from 'react';
+import {Image, StyleSheet, Text} from 'react-native';
+import {View} from 'react-native-animatable';
+import {
+ cancelAnimation,
+ Easing,
+ useSharedValue,
+ withTiming,
+} from 'react-native-reanimated';
+import {useDispatch, useSelector} from 'react-redux';
+import {checkMomentDoneProcessing} from '../../services';
+import {loadUserMoments} from '../../store/actions';
+import {setMomentUploadProgressBar} from '../../store/reducers';
+import {RootState} from '../../store/rootReducer';
+import {MomentUploadStatusType} from '../../types';
+import {normalize, SCREEN_WIDTH, StatusBarHeight} from '../../utils';
+import {GradientProgressBar} from '../common';
+
+interface MomentUploadProgressBarProps {}
+
+const MomentUploadProgressBar: React.FC<MomentUploadProgressBarProps> =
+ ({}) => {
+ const dispatch = useDispatch();
+ const {userId: loggedInUserId} = useSelector(
+ (state: RootState) => state.user.user,
+ );
+ const {momentUploadProgressBar} = useSelector(
+ (state: RootState) => state.user,
+ );
+ const progress = useSharedValue(0);
+ const showLoading =
+ momentUploadProgressBar?.status ===
+ MomentUploadStatusType.UploadingToS3 ||
+ momentUploadProgressBar?.status ===
+ MomentUploadStatusType.WaitingForDoneProcessing;
+
+ useEffect(() => {
+ let doneProcessing = false;
+ const checkDone = async () => {
+ if (
+ momentUploadProgressBar &&
+ (await checkMomentDoneProcessing(momentUploadProgressBar!.momentId))
+ ) {
+ doneProcessing = true;
+ cancelAnimation(progress);
+ // upload is done, but let's finish the progress bar animation in a velocity of 10%/s
+ const finishProgressBarDuration = (1 - progress.value) * 10 * 1000;
+ progress.value = withTiming(1, {
+ duration: finishProgressBarDuration,
+ easing: Easing.linear,
+ });
+ // change status to Done 1s after the progress bar animation is done
+ setTimeout(() => {
+ dispatch(loadUserMoments(loggedInUserId));
+ dispatch({
+ type: setMomentUploadProgressBar.type,
+ payload: {
+ momentUploadProgressBar: {
+ ...momentUploadProgressBar,
+ status: MomentUploadStatusType.Done,
+ },
+ },
+ });
+ }, finishProgressBarDuration);
+ }
+ };
+ if (
+ momentUploadProgressBar?.status ===
+ MomentUploadStatusType.WaitingForDoneProcessing
+ ) {
+ checkDone();
+ const timer = setInterval(async () => {
+ if (!doneProcessing) {
+ checkDone();
+ }
+ }, 5 * 1000);
+ // timeout if takes longer than 1 minute to process
+ setTimeout(() => {
+ clearInterval(timer);
+ if (!doneProcessing) {
+ console.error('Check for done processing timed out');
+ dispatch({
+ type: setMomentUploadProgressBar.type,
+ payload: {
+ momentUploadProgressBar: {
+ ...momentUploadProgressBar,
+ status: MomentUploadStatusType.Error,
+ },
+ },
+ });
+ }
+ }, 60 * 1000);
+ return () => clearInterval(timer);
+ }
+ }, [momentUploadProgressBar?.status]);
+
+ useEffect(() => {
+ if (
+ momentUploadProgressBar?.status === MomentUploadStatusType.UploadingToS3
+ ) {
+ // e.g. 30s video => 30 * 3 = 60s
+ const videoDuration =
+ momentUploadProgressBar.originalVideoDuration ?? 30;
+ const durationInSeconds = videoDuration * 3;
+ progress.value = withTiming(1, {
+ duration: durationInSeconds * 1000,
+ easing: Easing.out(Easing.quad),
+ });
+ }
+ }, [momentUploadProgressBar?.status]);
+
+ useEffect(() => {
+ if (
+ momentUploadProgressBar?.status === MomentUploadStatusType.Done ||
+ momentUploadProgressBar?.status === MomentUploadStatusType.Error
+ ) {
+ progress.value = 0;
+ // clear this component after a duration
+ setTimeout(() => {
+ dispatch({
+ type: setMomentUploadProgressBar.type,
+ payload: {
+ momentUploadProgressBar: undefined,
+ },
+ });
+ }, 5000);
+ }
+ }, [momentUploadProgressBar?.status]);
+
+ if (!momentUploadProgressBar) {
+ return null;
+ }
+
+ return (
+ <View
+ style={[
+ styles.background,
+ momentUploadProgressBar?.status === MomentUploadStatusType.Error
+ ? styles.redBackground
+ : {},
+ ]}>
+ <View style={styles.container}>
+ {showLoading && (
+ <>
+ <Text style={styles.text}>Uploading Moment...</Text>
+ <GradientProgressBar style={styles.bar} progress={progress} />
+ </>
+ )}
+ {momentUploadProgressBar.status === MomentUploadStatusType.Done && (
+ <View style={styles.row}>
+ <Image
+ source={require('../../assets/images/green-check.png')}
+ style={styles.x}
+ />
+ <Text style={styles.text}>
+ Beautiful, the Moment was uploaded successfully!
+ </Text>
+ </View>
+ )}
+ {momentUploadProgressBar.status === MomentUploadStatusType.Error && (
+ <View style={styles.row}>
+ <Image
+ source={require('../../assets/images/white-x.png')}
+ style={styles.x}
+ />
+ <Text style={styles.whiteText}>
+ Unable to upload Moment. Please retry
+ </Text>
+ </View>
+ )}
+ </View>
+ </View>
+ );
+ };
+
+const styles = StyleSheet.create({
+ background: {
+ position: 'absolute',
+ zIndex: 999,
+ height: StatusBarHeight + normalize(84),
+ backgroundColor: 'white',
+ width: '100%',
+ alignItems: 'center',
+ },
+ container: {
+ justifyContent: 'center',
+ marginTop: StatusBarHeight,
+ height: normalize(84),
+ },
+ text: {
+ fontSize: normalize(14),
+ fontWeight: 'bold',
+ lineHeight: 17,
+ marginVertical: 12,
+ width: '80%',
+ },
+ bar: {
+ width: SCREEN_WIDTH * 0.9,
+ },
+ redBackground: {
+ backgroundColor: '#EA574C',
+ },
+ row: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ whiteText: {
+ color: 'white',
+ fontSize: normalize(14),
+ fontWeight: 'bold',
+ lineHeight: 17,
+ marginVertical: 12,
+ },
+ x: {
+ width: normalize(26),
+ height: normalize(26),
+ marginRight: 10,
+ },
+});
+
+export default MomentUploadProgressBar;
diff --git a/src/components/moments/TrimmerPlayer.tsx b/src/components/moments/TrimmerPlayer.tsx
index 87b3a786..8d1cd156 100644
--- a/src/components/moments/TrimmerPlayer.tsx
+++ b/src/components/moments/TrimmerPlayer.tsx
@@ -73,7 +73,12 @@ const TrimmerPlayer: React.FC<TrimmerPlayerProps> = ({
repeat={true}
onLoad={(payload) => {
setEnd(payload.duration);
- handleLoad(payload.naturalSize);
+ const {width, height} = payload.naturalSize;
+ if (payload.naturalSize.orientation === 'portrait') {
+ handleLoad(height, width, payload.duration);
+ } else {
+ handleLoad(width, height, payload.duration);
+ }
}}
onProgress={(e) => {
if (!paused) {
diff --git a/src/components/moments/index.ts b/src/components/moments/index.ts
index 16c9aed2..3f33ec53 100644
--- a/src/components/moments/index.ts
+++ b/src/components/moments/index.ts
@@ -5,3 +5,4 @@ export {default as TagFriendsFooter} from './TagFriendsFoooter';
export {default as MomentPost} from './MomentPost';
export {default as TaggedUsersDrawer} from './TaggedUsersDrawer';
export {default as TrimmerPlayer} from './TrimmerPlayer';
+export {default as MomentUploadProgressBar} from './MomentUploadProgressBar';
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index 2d1002dd..9edd890d 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -6,6 +6,7 @@ import Animated, {
useSharedValue,
} from 'react-native-reanimated';
import {useDispatch, useSelector, useStore} from 'react-redux';
+import {MomentUploadProgressBar} from '..';
import {
blockUnblockUser,
loadFriendsData,
@@ -140,6 +141,7 @@ const Content: React.FC<ContentProps> = ({userXId, screenType}) => {
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}>
+ {!userXId && <MomentUploadProgressBar />}
<Cover {...{userXId, screenType}} />
<ProfileCutout />
<ProfileHeader
diff --git a/src/components/profile/ProfileBadges.tsx b/src/components/profile/ProfileBadges.tsx
index 8e68dc46..c7d3b5ba 100644
--- a/src/components/profile/ProfileBadges.tsx
+++ b/src/components/profile/ProfileBadges.tsx
@@ -64,8 +64,8 @@ const ProfileBadges: React.FC<ProfileBadgesProps> = ({userXId, screenType}) => {
<PlusIcon />
{Array(BADGE_LIMIT)
.fill(0)
- .map(() => (
- <View style={[styles.grey, styles.circle]} />
+ .map((_item, index) => (
+ <View key={index} style={[styles.grey, styles.circle]} />
))}
</ScrollView>
)}
@@ -85,8 +85,8 @@ const ProfileBadges: React.FC<ProfileBadgesProps> = ({userXId, screenType}) => {
{Array(BADGE_LIMIT + 1)
.fill(0)
.splice(displayBadges.length + 1, BADGE_LIMIT)
- .map(() => (
- <View style={styles.circle} />
+ .map((_item, index) => (
+ <View key={index} style={styles.circle} />
))}
{/* X button */}
{displayBadges.length === BADGE_LIMIT && isOwnProfile && (