diff options
author | Ivan Chen <ivan@tagg.id> | 2021-05-07 16:01:47 -0400 |
---|---|---|
committer | Ivan Chen <ivan@tagg.id> | 2021-05-07 16:01:47 -0400 |
commit | 76bc8c5825f39257be6e7648d12b858f1e805569 (patch) | |
tree | b94d9570439ebfa42e6664144f124abe5d4113e3 /src/components/common | |
parent | 65c7411f4609edac3d4d5f23fc031ed274fc5872 (diff) | |
parent | c9d32e68fbb9d1bc175722bfda49454a6f627eae (diff) |
Merge branch 'master' into tma821-load-badges-faster-ft
# Conflicts:
# src/utils/users.ts
Diffstat (limited to 'src/components/common')
-rw-r--r-- | src/components/common/Avatar.tsx | 18 | ||||
-rw-r--r-- | src/components/common/AvatarTitle.tsx | 14 | ||||
-rw-r--r-- | src/components/common/EmptyContentView.tsx | 132 | ||||
-rw-r--r-- | src/components/common/TaggTypeahead.tsx | 75 | ||||
-rw-r--r-- | src/components/common/TaggUserRowCell.tsx | 52 | ||||
-rw-r--r-- | src/components/common/index.ts | 3 |
6 files changed, 284 insertions, 10 deletions
diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx new file mode 100644 index 00000000..831cf906 --- /dev/null +++ b/src/components/common/Avatar.tsx @@ -0,0 +1,18 @@ +import React, {FC} from 'react'; +import {Image, ImageStyle, StyleProp} from 'react-native'; + +type AvatarProps = { + style: StyleProp<ImageStyle>; + uri: string | undefined; +}; +const Avatar: FC<AvatarProps> = ({style, uri}) => { + return ( + <Image + style={style} + defaultSource={require('../../assets/images/avatar-placeholder.png')} + source={{uri, cache: 'reload'}} + /> + ); +}; + +export default Avatar; diff --git a/src/components/common/AvatarTitle.tsx b/src/components/common/AvatarTitle.tsx index 81351327..a2a7c0aa 100644 --- a/src/components/common/AvatarTitle.tsx +++ b/src/components/common/AvatarTitle.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import {Image, StyleSheet, View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import {TAGGS_GRADIENT} from '../../constants'; +import Avatar from './Avatar'; type AvatarTitleProps = { - avatar: string | null; + avatar: string | undefined; }; const AvatarTitle: React.FC<AvatarTitleProps> = ({avatar}) => { return ( @@ -16,14 +17,7 @@ const AvatarTitle: React.FC<AvatarTitleProps> = ({avatar}) => { angleCenter={{x: 0.5, y: 0.5}} style={styles.gradient} /> - <Image - style={styles.avatar} - source={ - avatar - ? {uri: avatar} - : require('../../assets/images/avatar-placeholder.png') - } - /> + <Avatar style={styles.avatar} uri={avatar} /> </View> ); }; diff --git a/src/components/common/EmptyContentView.tsx b/src/components/common/EmptyContentView.tsx new file mode 100644 index 00000000..14ad4af1 --- /dev/null +++ b/src/components/common/EmptyContentView.tsx @@ -0,0 +1,132 @@ +import React from 'react'; +import {Image, Text, StyleSheet, View} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import { + UP_TO_DATE, + NO_NEW_NOTIFICATIONS, + FIRST_MESSAGE, + START_CHATTING, +} from '../../constants/strings'; +import {NOTIFICATION_GRADIENT} from '../../constants/constants'; +import {SCREEN_HEIGHT, normalize, SCREEN_WIDTH} from '../../utils'; +import {EmptyViewProps} from '../../types/index'; + +const EmptyContentView: React.FC<EmptyViewProps> = ({viewType}) => { + const _getNotificationImage = () => { + return ( + <LinearGradient + style={styles.backgroundLinearView} + useAngle={true} + angle={180} + colors={NOTIFICATION_GRADIENT}> + <Image + source={require('../../assets/images/empty_notifications.png')} + /> + </LinearGradient> + ); + }; + + const _getChatImage = () => { + return ( + <LinearGradient + style={styles.backgroundLinearView} + useAngle={true} + angle={180} + colors={NOTIFICATION_GRADIENT}> + <Image + style={styles.imageStyles} + source={require('../../assets/images/no_chats.png')} + /> + </LinearGradient> + ); + }; + + const _getImageForType = () => { + switch (viewType) { + case 'Notification': + return _getNotificationImage(); + case 'ChatList': + return _getChatImage(); + } + }; + + const _getTextForNotification = () => { + return ( + <> + <View style={styles.topMargin}> + <Text style={styles.upperTextStyle}>{UP_TO_DATE}</Text> + </View> + <View> + <Text style={styles.bottomTextStyle}>{NO_NEW_NOTIFICATIONS}</Text> + </View> + </> + ); + }; + + const _getTextForChat = () => { + return ( + <View style={styles.chatTextStyles}> + <View style={styles.topMargin}> + <Text style={styles.upperTextStyle}>{START_CHATTING}</Text> + </View> + <View> + <Text style={styles.bottomTextStyle}>{FIRST_MESSAGE}</Text> + </View> + </View> + ); + }; + + const _getTextForType = () => { + switch (viewType) { + case 'Notification': + return _getTextForNotification(); + case 'ChatList': + return _getTextForChat(); + } + }; + + return ( + <View style={styles.container}> + {_getImageForType()} + {_getTextForType()} + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + topMargin: { + marginTop: SCREEN_HEIGHT * 0.025, + paddingBottom: '5%', + }, + upperTextStyle: { + textAlign: 'center', + fontWeight: '700', + fontSize: normalize(23), + lineHeight: normalize(40), + }, + chatTextStyles: { + width: '85%', + }, + bottomTextStyle: { + textAlign: 'center', + color: '#808080', + fontWeight: '600', + fontSize: normalize(20), + lineHeight: normalize(30), + }, + imageStyles: { + width: SCREEN_WIDTH * 0.72, + height: SCREEN_WIDTH * 0.72, + }, + backgroundLinearView: { + borderRadius: (SCREEN_WIDTH * 0.72) / 2, + }, +}); + +export default EmptyContentView; diff --git a/src/components/common/TaggTypeahead.tsx b/src/components/common/TaggTypeahead.tsx new file mode 100644 index 00000000..7cd99278 --- /dev/null +++ b/src/components/common/TaggTypeahead.tsx @@ -0,0 +1,75 @@ +import React, {Fragment, useEffect, useState} from 'react'; +import {ScrollView, StyleSheet} from 'react-native'; +import {MentionSuggestionsProps} from 'react-native-controlled-mentions'; +import {SEARCH_ENDPOINT_MESSAGES} from '../../constants'; +import {loadSearchResults} from '../../services'; +import {ProfilePreviewType} from '../../types'; +import {SCREEN_WIDTH} from '../../utils'; +import TaggUserRowCell from './TaggUserRowCell'; + +const TaggTypeahead: React.FC<MentionSuggestionsProps> = ({ + keyword, + onSuggestionPress, +}) => { + const [results, setResults] = useState<ProfilePreviewType[]>([]); + const [height, setHeight] = useState(0); + + useEffect(() => { + getQuerySuggested(); + }, [keyword]); + + const getQuerySuggested = async () => { + if (!keyword || keyword.length < 3) { + setResults([]); + return; + } + const searchResults = await loadSearchResults( + `${SEARCH_ENDPOINT_MESSAGES}?query=${keyword}`, + ); + if (searchResults && searchResults.users) { + setResults(searchResults.users); + } + }; + + if (results.length === 0) { + return <Fragment />; + } + + return ( + <ScrollView + style={[styles.container, {top: -(height + 30)}]} + showsVerticalScrollIndicator={false} + onLayout={(event) => { + setHeight(event.nativeEvent.layout.height); + }}> + {results.map((user) => ( + <TaggUserRowCell + onPress={() => { + onSuggestionPress({ + id: user.id, + name: user.username, + }); + setResults([]); + }} + user={user} + /> + ))} + </ScrollView> + ); +}; + +const styles = StyleSheet.create({ + container: { + marginLeft: SCREEN_WIDTH * 0.05, + width: SCREEN_WIDTH * 0.9, + maxHeight: 264, + borderRadius: 10, + backgroundColor: 'white', + position: 'absolute', + alignSelf: 'center', + zIndex: 1, + borderWidth: 1, + }, +}); + +export default TaggTypeahead; diff --git a/src/components/common/TaggUserRowCell.tsx b/src/components/common/TaggUserRowCell.tsx new file mode 100644 index 00000000..446dedc9 --- /dev/null +++ b/src/components/common/TaggUserRowCell.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'; +import {ProfilePreviewType} from '../../types'; +import {normalize} from '../../utils'; +import Avatar from './Avatar'; + +type TaggUserRowCellProps = { + onPress: () => void; + user: ProfilePreviewType; +}; +const TaggUserRowCell: React.FC<TaggUserRowCellProps> = ({onPress, user}) => { + return ( + <TouchableOpacity onPress={onPress} style={styles.container}> + <Avatar style={styles.image} uri={user.thumbnail_url} /> + <View style={styles.textContent}> + <Text style={styles.username}>{`@${user.username}`}</Text> + <Text style={styles.name}> + {user.first_name} {user.last_name} + </Text> + </View> + </TouchableOpacity> + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + paddingHorizontal: 25, + paddingVertical: 15, + width: '100%', + }, + image: { + width: normalize(30), + height: normalize(30), + borderRadius: 30, + }, + textContent: { + flexDirection: 'column', + justifyContent: 'space-between', + marginLeft: 20, + }, + username: { + fontWeight: '500', + fontSize: normalize(14), + }, + name: { + fontWeight: '500', + fontSize: normalize(12), + color: '#828282', + }, +}); +export default TaggUserRowCell; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 5a601f1d..b38056c6 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -23,3 +23,6 @@ export {default as FriendsButton} from './FriendsButton'; export {default as TaggSquareButton} from './TaggSquareButton'; export {default as GradientBorderButton} from './GradientBorderButton'; export {default as BasicButton} from './BasicButton'; +export {default as Avatar} from './Avatar'; +export {default as TaggTypeahead} from './TaggTypeahead'; +export {default as TaggUserRowCell} from './TaggUserRowCell'; |