diff options
author | Ivan Chen <ivan@thetaggid.com> | 2020-10-22 17:42:29 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-22 17:42:29 -0400 |
commit | 52a3fe743e6122d157eaab3ad7bab0c70a96676b (patch) | |
tree | 564c5df3864a0d0fe09f7c18613d4cf5a1093170 /src/components | |
parent | 08f9aebbaef871629323767c93c9e54cea527bed (diff) |
[TMA-242] Twitter and Facebook Tagg View (#63)
* modified the way we store social media data, initial skeleton
* MVP? Twitter done?
* cleaned up some things
* forgot to lint and cleaned up some more code
* minor change to text display
* fixed some UI bug, linting, and minor adjustment to posts UI
* fixed a couple of things
* added DateLabel, Facebook taggs view, fixed minor stuff
* Some small changes for the PR
* removed unused Feed
Co-authored-by: Ashm Walia <ashmwalia@outlook.com>
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/common/DateLabel.tsx | 58 | ||||
-rw-r--r-- | src/components/common/index.ts | 1 | ||||
-rw-r--r-- | src/components/onboarding/SocialMediaLinker.tsx | 17 | ||||
-rw-r--r-- | src/components/profile/Feed.tsx | 30 | ||||
-rw-r--r-- | src/components/profile/index.ts | 1 | ||||
-rw-r--r-- | src/components/taggs/SocialMediaInfo.tsx | 8 | ||||
-rw-r--r-- | src/components/taggs/Tagg.tsx | 5 | ||||
-rw-r--r-- | src/components/taggs/TaggPost.tsx | 63 | ||||
-rw-r--r-- | src/components/taggs/TaggPostFooter.tsx | 29 | ||||
-rw-r--r-- | src/components/taggs/TaggsBar.tsx | 35 | ||||
-rw-r--r-- | src/components/taggs/TaggsFeed.tsx | 29 | ||||
-rw-r--r-- | src/components/taggs/TwitterTaggPost.tsx | 213 | ||||
-rw-r--r-- | src/components/taggs/index.ts | 2 |
13 files changed, 377 insertions, 114 deletions
diff --git a/src/components/common/DateLabel.tsx b/src/components/common/DateLabel.tsx new file mode 100644 index 00000000..145c614c --- /dev/null +++ b/src/components/common/DateLabel.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import {StyleSheet, Text} from 'react-native'; +import moment from 'moment'; + +interface DateLabelProps { + timestamp: string; + type: 'default' | 'short' | 'small'; + decorate?: (date: string) => string; +} + +const DateLabel: React.FC<DateLabelProps> = ({ + timestamp, + type, + decorate = (date) => `${date}`, +}) => { + let parsedDate = moment(timestamp); + + if (!parsedDate) { + return <React.Fragment />; + } + + switch (type) { + case 'default': + return ( + <Text style={styles.default}> + {decorate(parsedDate.format('h:mm a • MMM D, YYYY'))} + </Text> + ); + + case 'short': + return ( + <Text style={styles.default}> + {decorate(parsedDate.format('MMM D'))} + </Text> + ); + + case 'small': + return ( + <Text style={styles.smallAndBlue}> + {decorate(parsedDate.format('MMM D'))} + </Text> + ); + } +}; + +const styles = StyleSheet.create({ + default: { + fontSize: 15, + color: '#c4c4c4', + }, + smallAndBlue: { + fontSize: 14, + fontWeight: 'bold', + color: '#8FA9C2', + }, +}); + +export default DateLabel; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index cb9d641b..cd72a70b 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -8,4 +8,5 @@ export {default as SocialIcon} from './SocialIcon'; export {default as TabsGradient} from './TabsGradient'; export {default as RecentSearches} from '../search/RecentSearches'; export {default as LoadingIndicator} from './LoadingIndicator'; +export {default as DateLabel} from './DateLabel'; export * from './post'; diff --git a/src/components/onboarding/SocialMediaLinker.tsx b/src/components/onboarding/SocialMediaLinker.tsx index e7f78834..15afb731 100644 --- a/src/components/onboarding/SocialMediaLinker.tsx +++ b/src/components/onboarding/SocialMediaLinker.tsx @@ -28,7 +28,6 @@ interface SocialMediaLinkerProps extends TouchableOpacityProps { const SocialMediaLinker: React.FC<SocialMediaLinkerProps> = ({ social: {label}, }) => { - const [state, setState] = React.useState({ authenticated: false, }); @@ -36,7 +35,7 @@ const SocialMediaLinker: React.FC<SocialMediaLinkerProps> = ({ const integrated_endpoints: {[label: string]: [string, string]} = { Instagram: [LINK_IG_OAUTH, LINK_IG_ENDPOINT], Facebook: [LINK_FB_OAUTH, LINK_FB_ENDPOINT], - Twitter: [LINK_TWITTER_OAUTH, LINK_TWITTER_ENDPOINT] + Twitter: [LINK_TWITTER_OAUTH, LINK_TWITTER_ENDPOINT], }; const registerSocialLink: (token: string) => Promise<boolean> = async ( @@ -71,7 +70,7 @@ const SocialMediaLinker: React.FC<SocialMediaLinkerProps> = ({ Alert.alert('Coming soon!'); return; } - let url = integrated_endpoints[label][0] + let url = integrated_endpoints[label][0]; // We will need to do an extra step for twitter sign-in if (label === 'Twitter') { @@ -79,10 +78,10 @@ const SocialMediaLinker: React.FC<SocialMediaLinkerProps> = ({ const response = await fetch(url, { method: 'GET', headers: { - Authorization: `Token ${user_token}` - } + Authorization: `Token ${user_token}`, + }, }); - url = response.url + url = response.url; } if (isAvailable) { @@ -90,7 +89,7 @@ const SocialMediaLinker: React.FC<SocialMediaLinkerProps> = ({ ephemeralWebSession: true, }) .then(async (response) => { - console.log(response) + console.log(response); if (response.type === 'success' && response.url) { const success = await registerSocialLink(response.url); if (!success) { @@ -107,9 +106,7 @@ const SocialMediaLinker: React.FC<SocialMediaLinkerProps> = ({ }) .catch((error) => { console.log(error); - Alert.alert( - `Something went wrong, we can't link with ${label} 😔`, - ); + Alert.alert(`Something went wrong, we can't link with ${label} 😔`); }); } else { // Okay... to open an external browser and have it link back to diff --git a/src/components/profile/Feed.tsx b/src/components/profile/Feed.tsx deleted file mode 100644 index 3353d25b..00000000 --- a/src/components/profile/Feed.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import {PostType, UserType} from '../../types'; -import {Post} from '../common'; -import {AuthContext} from '../../routes/authentication'; - -interface FeedProps { - user: UserType; -} -const Feed: React.FC<FeedProps> = ({user}) => { - const {instaPosts} = React.useContext(AuthContext); - const posts: Array<PostType> = []; - for (let i = 0; i < 10; i++) { - const testPost: PostType = { - owner: user, - social: 'Instagram', - socialHandle: 'igHandle', - data: instaPosts[i], - }; - posts.push(testPost); - } - return ( - <> - {posts.map((post, index) => ( - <Post key={index} post={post} /> - ))} - </> - ); -}; - -export default Feed; diff --git a/src/components/profile/index.ts b/src/components/profile/index.ts index 4eb435df..e2063e26 100644 --- a/src/components/profile/index.ts +++ b/src/components/profile/index.ts @@ -4,5 +4,4 @@ export {default as Moment} from './Moment'; export {default as ProfileCutout} from './ProfileCutout'; export {default as ProfileBody} from './ProfileBody'; export {default as ProfileHeader} from './ProfileHeader'; -export {default as Feed} from './Feed'; export {default as CaptionScreenHeader} from './CaptionScreenHeader'; diff --git a/src/components/taggs/SocialMediaInfo.tsx b/src/components/taggs/SocialMediaInfo.tsx index 0e93660d..a7ed6fe6 100644 --- a/src/components/taggs/SocialMediaInfo.tsx +++ b/src/components/taggs/SocialMediaInfo.tsx @@ -5,7 +5,7 @@ import {SocialIcon} from '..'; interface SocialMediaInfoProps { fullname: string; type: string; - handle: string; + handle?: string; } const SocialMediaInfo: React.FC<SocialMediaInfoProps> = ({ @@ -15,7 +15,11 @@ const SocialMediaInfo: React.FC<SocialMediaInfoProps> = ({ }) => { return ( <View style={styles.container}> - <Text style={styles.handle}> @{handle} </Text> + {handle && type !== 'Facebook' ? ( + <Text style={styles.handle}> @{handle} </Text> + ) : ( + <></> + )} <View style={styles.row}> <View /> <SocialIcon style={styles.icon} social={type} /> diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx index d6cffee5..9274e0eb 100644 --- a/src/components/taggs/Tagg.tsx +++ b/src/components/taggs/Tagg.tsx @@ -6,17 +6,18 @@ import {TAGGS_GRADIENT} from '../../constants'; interface TaggProps { style: object; + social: string; isProfileView: boolean; } -const Tagg: React.FC<TaggProps> = ({style, isProfileView}) => { +const Tagg: React.FC<TaggProps> = ({style, social, isProfileView}) => { const navigation = useNavigation(); return ( <TouchableOpacity onPress={() => navigation.navigate('SocialMediaTaggs', { - socialMediaType: 'Instagram', + socialMediaType: social, isProfileView: isProfileView, }) }> diff --git a/src/components/taggs/TaggPost.tsx b/src/components/taggs/TaggPost.tsx index 73f15268..0d3aee50 100644 --- a/src/components/taggs/TaggPost.tsx +++ b/src/components/taggs/TaggPost.tsx @@ -1,38 +1,59 @@ -import moment from 'moment'; import React from 'react'; -import {Image, StyleSheet, View} from 'react-native'; -import {PostType} from '../../types'; +import {Image, StyleSheet, Text, View} from 'react-native'; +import {SimplePostType} from '../../types'; import {SCREEN_WIDTH} from '../../utils'; +import {DateLabel} from '../common'; import TaggPostFooter from './TaggPostFooter'; interface TaggPostProps { - post: PostType; + post: SimplePostType; } -const TaggPost: React.FC<TaggPostProps> = ({post: {socialHandle, data}}) => { - const parsedDate = moment(data?.timestamp || ''); - const date = parsedDate.isValid() ? parsedDate.format('MMM d') : ''; - - return ( - <> - <View style={styles.image}> - {data && <Image style={styles.image} source={{uri: data.media_url}} />} +const TaggPost: React.FC<TaggPostProps> = ({post}) => { + if (post.media_type === 'photo') { + // Post with image and footer that shows caption + return ( + <View style={styles.photoContainer}> + <View style={styles.image}> + {post && ( + <Image style={styles.image} source={{uri: post.media_url}} /> + )} + </View> + <TaggPostFooter + // we currently don't have a way to retreive num of likes information + likes={undefined} + handle={post.username} + caption={post.caption || ''} + timestamp={post.timestamp} + /> + </View> + ); + } else { + // Post with large text + return ( + <View style={styles.textContianer}> + <Text style={styles.text}>{post.caption}</Text> + <DateLabel timestamp={post.timestamp} type={'default'} /> </View> - <TaggPostFooter - // we currently don't have a way to retreive num of likes information - likes={109} - handle={socialHandle} - caption={data?.caption || ''} - date={date} - /> - </> - ); + ); + } }; const styles = StyleSheet.create({ + photoContainer: { + marginBottom: 50, + }, image: { width: SCREEN_WIDTH, height: SCREEN_WIDTH, backgroundColor: '#eee', + marginBottom: 30, + }, + textContianer: {marginBottom: 50, paddingHorizontal: 10}, + text: { + marginBottom: 30, + fontSize: 18, + color: 'white', + flexWrap: 'wrap', }, }); diff --git a/src/components/taggs/TaggPostFooter.tsx b/src/components/taggs/TaggPostFooter.tsx index 024670a8..8371a847 100644 --- a/src/components/taggs/TaggPostFooter.tsx +++ b/src/components/taggs/TaggPostFooter.tsx @@ -1,30 +1,32 @@ import React from 'react'; import {StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; +import {DateLabel} from '../common'; interface TaggPostFooterProps { - likes: number; - handle: string; + likes?: number; + handle?: string; caption: string; - date: string; + timestamp: string; } const TaggPostFooter: React.FC<TaggPostFooterProps> = ({ likes, handle, caption, - date, + timestamp, }) => { + const handleText = handle ? handle : ''; return ( <View> <View style={styles.container}> - <Text style={styles.likeText}>{likes} likes</Text> + {likes ? <Text style={styles.likeText}>{likes} likes</Text> : <></>} <View style={styles.captionContainer}> <Text style={styles.handleText}> - {handle} + {handleText} <Text style={styles.captionText}> {caption}</Text> </Text> </View> - <Text style={styles.dateText}>{date}</Text> + <DateLabel timestamp={timestamp} type={'small'} /> </View> </View> ); @@ -33,11 +35,11 @@ const TaggPostFooter: React.FC<TaggPostFooterProps> = ({ const styles = StyleSheet.create({ container: { flexDirection: 'column', - padding: 10, - paddingBottom: '10%', + paddingHorizontal: 10, + marginBottom: 50, }, captionContainer: { - paddingVertical: 10, + paddingBottom: 30, }, likeText: { fontSize: 14, @@ -51,15 +53,10 @@ const styles = StyleSheet.create({ }, captionText: { fontSize: 14, - fontWeight: 'bold', + fontWeight: 'normal', color: 'white', flexWrap: 'wrap', }, - dateText: { - fontSize: 14, - fontWeight: 'bold', - color: '#8FA9C2', - }, }); export default TaggPostFooter; diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx index 933f355d..88f670b5 100644 --- a/src/components/taggs/TaggsBar.tsx +++ b/src/components/taggs/TaggsBar.tsx @@ -18,9 +18,40 @@ const TaggsBar: React.FC<TaggsBarProps> = ({ isProfileView, }) => { const taggs: Array<JSX.Element> = []; - for (let i = 0; i < 10; i++) { + + taggs.push( + <Tagg + key={0} + style={styles.tagg} + social={'Instagram'} + isProfileView={isProfileView} + />, + ); + taggs.push( + <Tagg + key={1} + style={styles.tagg} + social={'Facebook'} + isProfileView={isProfileView} + />, + ); + taggs.push( + <Tagg + key={2} + style={styles.tagg} + social={'Twitter'} + isProfileView={isProfileView} + />, + ); + + for (let i = 3; i < 10; i++) { taggs.push( - <Tagg key={i} style={styles.tagg} isProfileView={isProfileView} />, + <Tagg + key={i} + style={styles.tagg} + social={'Instagram'} + isProfileView={isProfileView} + />, ); } const shadowOpacity: Animated.Node<number> = interpolate(y, { diff --git a/src/components/taggs/TaggsFeed.tsx b/src/components/taggs/TaggsFeed.tsx deleted file mode 100644 index 3f27e248..00000000 --- a/src/components/taggs/TaggsFeed.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import {InstagramPostType, UserType} from '../../types'; -import {TaggPost} from './'; - -interface TaggsFeedProps { - user: UserType; - socialHandle: string; - posts: Array<InstagramPostType>; -} - -const TaggsFeed: React.FC<TaggsFeedProps> = ({user, socialHandle, posts}) => { - return ( - <> - {posts?.map((post, index) => ( - <TaggPost - key={index} - post={{ - owner: user, - social: 'Instagram', - socialHandle: socialHandle, - data: post, - }} - /> - ))} - </> - ); -}; - -export default TaggsFeed; diff --git a/src/components/taggs/TwitterTaggPost.tsx b/src/components/taggs/TwitterTaggPost.tsx new file mode 100644 index 00000000..2cc23bcf --- /dev/null +++ b/src/components/taggs/TwitterTaggPost.tsx @@ -0,0 +1,213 @@ +import React from 'react'; +import {Image, Linking, StyleSheet, View} from 'react-native'; +import {Text} from 'react-native-animatable'; +import LinearGradient from 'react-native-linear-gradient'; +import {AVATAR_DIM, TAGGS_GRADIENT} from '../../constants'; +import {TwitterPostType} from '../../types'; +import {SCREEN_WIDTH} from '../../utils'; +import {DateLabel} from '../common'; + +interface TwitterTaggPostProps { + ownerHandle: string; + post: TwitterPostType; +} +const TwitterTaggPost: React.FC<TwitterTaggPostProps> = ({ + ownerHandle, + post, +}) => { + return ( + <View style={styles.mainContainer}> + {/* Retweeted? */} + {post.type === 'retweet' ? ( + <Text style={styles.retweetedText}>@{ownerHandle} retweeted</Text> + ) : ( + <React.Fragment /> + )} + {/* Post header (avatar and handle) */} + <View style={styles.header}> + <Image + style={styles.avatar} + source={ + post.profile_pic + ? {uri: post.profile_pic} + : require('../../assets/images/avatar-placeholder.png') + } + /> + <Text style={styles.headerText}>@{post.handle}</Text> + </View> + {/* Tweet/Reply/Retweet Content */} + <View style={styles.contentContainer}> + {/* First part of content is text or empty */} + {post.text ? ( + <Text style={styles.contentText}>{post.text}</Text> + ) : ( + <React.Fragment /> + )} + {/* Second part of content is an image or empty */} + {post.media_url ? ( + <View style={styles.imageContainer}> + <Image style={styles.image} source={{uri: post.media_url}} /> + </View> + ) : ( + <React.Fragment /> + )} + {/* Third part of content is the reply/retweet container or empty */} + {(post.type === 'reply' || post.type === 'retweet') && + post.in_reply_to ? ( + <LinearGradient + colors={[TAGGS_GRADIENT.start, TAGGS_GRADIENT.end]} + useAngle={true} + angle={300} + angleCenter={{x: 0.5, y: 0.5}} + style={[styles.replyGradient]}> + <View style={styles.replyPostContainer}> + <View style={styles.replyHeader}> + <Image + style={styles.replyAvatar} + source={ + post.in_reply_to.profile_pic + ? {uri: post.in_reply_to.profile_pic} + : require('../../assets/images/avatar-placeholder.png') + } + /> + <Text style={styles.replyHandleText}> + @{post.in_reply_to.handle} + </Text> + <DateLabel + timestamp={post.in_reply_to.timestamp} + type={'short'} + decorate={(date) => ` • ${date}`} + /> + </View> + <Text style={styles.replyText} numberOfLines={2}> + {post.in_reply_to.text} + </Text> + <Text + style={styles.replyShowThisThread} + onPress={() => { + if (post.in_reply_to?.permalink) { + Linking.openURL(post.in_reply_to.permalink); + } + }}> + Show this thread + </Text> + </View> + </LinearGradient> + ) : ( + <React.Fragment /> + )} + </View> + {/* Footer */} + <View style={styles.footer}> + <DateLabel timestamp={post.timestamp} type={'default'} /> + <Text + style={styles.viewOnTwitterText} + onPress={() => { + if (post.permalink) { + Linking.openURL(post.permalink); + } + }}> + View on Twitter + </Text> + </View> + </View> + ); +}; + +const styles = StyleSheet.create({ + mainContainer: { + marginHorizontal: 10, + marginBottom: 50, + }, + retweetedText: { + fontSize: 12, + color: 'grey', + marginBottom: 20, + }, + header: { + alignItems: 'center', + flexDirection: 'row', + marginBottom: 30, + }, + avatar: { + width: AVATAR_DIM, + height: AVATAR_DIM, + borderRadius: AVATAR_DIM / 2, + }, + headerText: { + fontSize: 15, + fontWeight: 'bold', + color: 'white', + paddingHorizontal: 12, + }, + contentContainer: {}, + contentText: { + fontSize: 18, + color: 'white', + marginBottom: 30, + }, + // image media + imageContainer: { + marginBottom: 30, + }, + image: { + width: SCREEN_WIDTH - 20, + height: SCREEN_WIDTH - 20, + backgroundColor: '#eee', + borderRadius: 15, + }, + // footer + footer: { + height: 50, + flexDirection: 'column', + justifyContent: 'space-between', + marginBottom: 50, + }, + viewOnTwitterText: { + fontSize: 12, + color: '#6ee7e7', + }, + // reply post + replyPostContainer: { + flex: 1, + marginVertical: 1, + paddingHorizontal: 10, + width: SCREEN_WIDTH - 22, + justifyContent: 'space-between', + paddingTop: 10, + paddingBottom: 20, + borderRadius: 15, + backgroundColor: '#1d0034', + }, + replyGradient: { + height: 150, + borderRadius: 15, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 30, + }, + replyHeader: { + alignItems: 'center', + flexDirection: 'row', + }, + replyAvatar: { + height: AVATAR_DIM / 2, + width: AVATAR_DIM / 2, + borderRadius: AVATAR_DIM / 2 / 2, + }, + replyHandleText: { + fontSize: 15, + color: '#c4c4c4', + paddingLeft: 7, + }, + replyText: { + fontSize: 15, + color: 'white', + }, + replyShowThisThread: { + fontSize: 15, + color: '#698dd3', + }, +}); + +export default TwitterTaggPost; diff --git a/src/components/taggs/index.ts b/src/components/taggs/index.ts index 1cb0c412..5d0a4d48 100644 --- a/src/components/taggs/index.ts +++ b/src/components/taggs/index.ts @@ -1,4 +1,4 @@ export {default as TaggsBar} from './TaggsBar'; export {default as SocialMediaInfo} from './SocialMediaInfo'; -export {default as TaggsFeed} from './TaggsFeed'; export {default as TaggPost} from './TaggPost'; +export {default as TwitterTaggPost} from './TwitterTaggPost'; |