aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets/icons/tagging/tag-icon.pngbin0 -> 85713 bytes
-rw-r--r--src/assets/icons/tagging/white-plus-icon.pngbin0 -> 18393 bytes
-rw-r--r--src/assets/icons/tagging/x-icon.pngbin0 -> 20875 bytes
-rw-r--r--src/components/common/TaggRadioButton.tsx53
-rw-r--r--src/components/common/TaggUserSelectionCell.tsx73
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/moments/TagFriendsFoooter.tsx132
-rw-r--r--src/components/moments/index.ts1
-rw-r--r--src/components/profile/ProfilePreview.tsx44
-rw-r--r--src/components/search/SearchBar.tsx24
-rw-r--r--src/constants/constants.ts1
-rw-r--r--src/constants/index.ts1
-rw-r--r--src/routes/main/MainStackNavigator.tsx10
-rw-r--r--src/routes/main/MainStackScreen.tsx13
-rw-r--r--src/screens/chat/ChatScreen.tsx3
-rw-r--r--src/screens/chat/ChatSearchBar.tsx14
-rw-r--r--src/screens/chat/NewChatModal.tsx1
-rw-r--r--src/screens/index.ts1
-rw-r--r--src/screens/moments/TagFriendsScreen.tsx152
-rw-r--r--src/screens/moments/TagSelectionScreen.tsx178
-rw-r--r--src/screens/moments/index.ts2
-rw-r--r--src/screens/onboarding/BasicInfoOnboarding.tsx8
-rw-r--r--src/screens/profile/CaptionScreen.tsx118
-rw-r--r--src/screens/profile/InviteFriendsScreen.tsx93
-rw-r--r--src/types/types.ts3
-rw-r--r--src/utils/search.ts13
26 files changed, 811 insertions, 128 deletions
diff --git a/src/assets/icons/tagging/tag-icon.png b/src/assets/icons/tagging/tag-icon.png
new file mode 100644
index 00000000..5dfa9099
--- /dev/null
+++ b/src/assets/icons/tagging/tag-icon.png
Binary files differ
diff --git a/src/assets/icons/tagging/white-plus-icon.png b/src/assets/icons/tagging/white-plus-icon.png
new file mode 100644
index 00000000..258166a7
--- /dev/null
+++ b/src/assets/icons/tagging/white-plus-icon.png
Binary files differ
diff --git a/src/assets/icons/tagging/x-icon.png b/src/assets/icons/tagging/x-icon.png
new file mode 100644
index 00000000..5f2b244c
--- /dev/null
+++ b/src/assets/icons/tagging/x-icon.png
Binary files differ
diff --git a/src/components/common/TaggRadioButton.tsx b/src/components/common/TaggRadioButton.tsx
new file mode 100644
index 00000000..3cc2780c
--- /dev/null
+++ b/src/components/common/TaggRadioButton.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import {
+ GestureResponderEvent,
+ StyleSheet,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import {RADIO_BUTTON_GREY, TAGG_LIGHT_BLUE_2} from '../../constants/constants';
+
+interface TaggRadioButtonProps {
+ pressed: boolean;
+ onPress: (event: GestureResponderEvent) => void;
+}
+const TaggRadioButton: React.FC<TaggRadioButtonProps> = ({
+ pressed,
+ onPress,
+}) => {
+ const activeOuterStyle = {
+ borderColor: pressed ? TAGG_LIGHT_BLUE_2 : RADIO_BUTTON_GREY,
+ };
+
+ const activeInnerStyle = {
+ backgroundColor: pressed ? TAGG_LIGHT_BLUE_2 : 'white',
+ };
+ return (
+ <TouchableOpacity
+ style={[styles.outer, activeOuterStyle]}
+ onPress={onPress}>
+ {pressed && <View style={[styles.inner, activeInnerStyle]} />}
+ </TouchableOpacity>
+ );
+};
+
+const styles = StyleSheet.create({
+ outer: {
+ width: 20,
+ height: 20,
+ borderWidth: 1.5,
+ borderRadius: 20,
+
+ backgroundColor: 'white',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ inner: {
+ width: 14,
+ height: 14,
+ borderRadius: 8,
+ },
+});
+
+export default TaggRadioButton;
diff --git a/src/components/common/TaggUserSelectionCell.tsx b/src/components/common/TaggUserSelectionCell.tsx
new file mode 100644
index 00000000..2ea1e4ce
--- /dev/null
+++ b/src/components/common/TaggUserSelectionCell.tsx
@@ -0,0 +1,73 @@
+import React, {useEffect, useState} from 'react';
+import {StyleSheet, View} from 'react-native';
+import {ProfilePreview} from '..';
+import {ProfilePreviewType, ScreenType} from '../../types';
+import {SCREEN_WIDTH} from '../../utils';
+import TaggRadioButton from './TaggRadioButton';
+
+interface TaggUserSelectionCellProps {
+ item: ProfilePreviewType;
+ selectedUsers: ProfilePreviewType[];
+ setSelectedUsers: Function;
+}
+const TaggUserSelectionCell: React.FC<TaggUserSelectionCellProps> = ({
+ item,
+ selectedUsers,
+ setSelectedUsers,
+}) => {
+ const [pressed, setPressed] = useState<boolean>(false);
+
+ /*
+ * To update state of radio button on initial render and subsequent re-renders
+ */
+ useEffect(() => {
+ const updatePressed = () => {
+ const userSelected = selectedUsers.findIndex(
+ (selectedUser) => item.id === selectedUser.id,
+ );
+ setPressed(userSelected !== -1);
+ };
+ updatePressed();
+ });
+
+ /*
+ * Handles on press on radio button
+ * Adds/removes user from selected list of users
+ */
+ const handlePress = () => {
+ // Add to selected list of users
+ if (pressed === false) {
+ setSelectedUsers([...selectedUsers, item]);
+ }
+ // Remove item from selected list of users
+ else {
+ const filteredSelection = selectedUsers.filter(
+ (user) => user.id !== item.id,
+ );
+ setSelectedUsers(filteredSelection);
+ }
+ };
+ return (
+ <View style={styles.container}>
+ <View style={{width: SCREEN_WIDTH * 0.8}}>
+ <ProfilePreview
+ profilePreview={item}
+ previewType={'Search'}
+ screenType={ScreenType.Profile}
+ />
+ </View>
+ <TaggRadioButton pressed={pressed} onPress={handlePress} />
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ marginHorizontal: '3%',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+});
+
+export default TaggUserSelectionCell;
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index 692c9f8a..4f5c0232 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -27,4 +27,5 @@ export {default as Avatar} from './Avatar';
export {default as TaggTypeahead} from './TaggTypeahead';
export {default as TaggUserRowCell} from './TaggUserRowCell';
export {default as LikeButton} from './LikeButton';
+export {default as TaggUserSelectionCell} from './TaggUserSelectionCell';
export {default as MomentTags} from './MomentTags';
diff --git a/src/components/moments/TagFriendsFoooter.tsx b/src/components/moments/TagFriendsFoooter.tsx
new file mode 100644
index 00000000..6b8fc62a
--- /dev/null
+++ b/src/components/moments/TagFriendsFoooter.tsx
@@ -0,0 +1,132 @@
+import {useNavigation} from '@react-navigation/native';
+import React, {Dispatch, SetStateAction} from 'react';
+import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
+import {ProfilePreview} from '..';
+import {ProfilePreviewType, ScreenType} from '../../types';
+import {normalize} from '../../utils/layouts';
+
+interface TagFriendsFooterProps {
+ taggedUsers: ProfilePreviewType[];
+ setTaggedUsers: Dispatch<SetStateAction<ProfilePreviewType[]>>;
+}
+const TagFriendsFooter: React.FC<TagFriendsFooterProps> = ({
+ taggedUsers,
+ setTaggedUsers,
+}) => {
+ const navigation = useNavigation();
+
+ const handleRemoveTag = (user: ProfilePreviewType) => {
+ const filteredSelection = taggedUsers.filter((item) => user.id !== item.id);
+ setTaggedUsers(filteredSelection);
+ };
+
+ const TaggMoreButton = () => (
+ <TouchableOpacity
+ onPress={() =>
+ navigation.navigate('TagSelectionScreen', {
+ selectedUsers: taggedUsers,
+ })
+ }
+ style={{
+ flexDirection: 'column',
+ alignItems: 'center',
+ }}>
+ <Image
+ source={require('../../assets/icons/tagging/white-plus-icon.png')}
+ style={{width: 38, height: 38, top: -2}}
+ />
+ <Text style={styles.taggMoreLabel}>{'Tagg More'}</Text>
+ </TouchableOpacity>
+ );
+
+ const TaggedUser = (user: ProfilePreviewType) => (
+ <View style={{flexDirection: 'row-reverse'}} key={user.id}>
+ <TouchableOpacity
+ style={styles.closeIconContainer}
+ onPress={() => handleRemoveTag(user)}>
+ <Image
+ source={require('../../assets/icons/tagging/x-icon.png')}
+ style={{
+ width: 20,
+ height: 20,
+ }}
+ />
+ </TouchableOpacity>
+ <ProfilePreview
+ profilePreview={user}
+ previewType={'Tag Selection'}
+ screenType={ScreenType.Profile}
+ />
+ </View>
+ );
+
+ /*
+ * Title/Button depending on the number of users inside taggedUsers list
+ * If taggUsers is empty, title acts as a button
+ * Else, gets disabled and TaggMore button appears
+ */
+ const TagFriendsTitle = () => (
+ <TouchableOpacity
+ style={{
+ flexDirection: 'row',
+ }}
+ disabled={taggedUsers.length !== 0}
+ onPress={() =>
+ navigation.navigate('TagSelectionScreen', {
+ selectedUsers: taggedUsers,
+ })
+ }>
+ <Image
+ source={require('../../assets/icons/tagging/tag-icon.png')}
+ style={styles.tagIcon}
+ />
+ <Text style={styles.tagFriendsTitle}>Tag Friends</Text>
+ </TouchableOpacity>
+ );
+
+ return (
+ <>
+ <TagFriendsTitle />
+ <View style={styles.tagFriendsContainer}>
+ {taggedUsers.map((user) => (
+ <TaggedUser {...user} />
+ ))}
+ {taggedUsers.length !== 0 && <TaggMoreButton />}
+ </View>
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ tagIcon: {width: 20, height: 20, marginRight: '3%'},
+ tagFriendsTitle: {
+ color: 'white',
+ fontSize: normalize(12),
+ lineHeight: normalize(16.71),
+ letterSpacing: normalize(0.3),
+ fontWeight: '600',
+ },
+ tagFriendsContainer: {
+ flexDirection: 'row',
+ marginTop: '3%',
+ flexWrap: 'wrap',
+ justifyContent: 'flex-start',
+ },
+ taggMoreLabel: {
+ fontWeight: '500',
+ fontSize: normalize(9),
+ lineHeight: normalize(10),
+ letterSpacing: normalize(0.2),
+ color: 'white',
+ textAlign: 'center',
+ marginVertical: '5%',
+ },
+ closeIconContainer: {
+ width: 20,
+ height: 20,
+ right: -20,
+ zIndex: 1,
+ },
+});
+
+export default TagFriendsFooter;
diff --git a/src/components/moments/index.ts b/src/components/moments/index.ts
index 89fd689c..6af29bc5 100644
--- a/src/components/moments/index.ts
+++ b/src/components/moments/index.ts
@@ -3,3 +3,4 @@ export {default as CaptionScreenHeader} from './CaptionScreenHeader';
export {default as MomentPostHeader} from './MomentPostHeader';
export {default as MomentPostContent} from './MomentPostContent';
export {default as Moment} from './Moment';
+export {default as TagFriendsFooter} from './TagFriendsFoooter';
diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx
index 66d68d8f..88c075e2 100644
--- a/src/components/profile/ProfilePreview.tsx
+++ b/src/components/profile/ProfilePreview.tsx
@@ -148,6 +148,14 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
usernameStyle = styles.discoverUsersUsername;
nameStyle = styles.discoverUsersName;
break;
+ case 'Tag Selection':
+ containerStyle = styles.tagSelectionContainer;
+ avatarStyle = styles.tagSelectionAvatar;
+ nameContainerStyle = styles.tagSelectionNameContainer;
+ usernameToDisplay = '@' + username;
+ usernameStyle = styles.tagSelectionUsername;
+ nameStyle = styles.tagSelectionName;
+ break;
case 'Comment':
containerStyle = styles.commentContainer;
avatarStyle = styles.commentAvatar;
@@ -195,10 +203,9 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
<Text style={nameStyle}>{first_name.concat(' ', last_name)}</Text>
</>
)}
- {previewType === 'Comment' && (
- <Text style={usernameStyle}>{usernameToDisplay}</Text>
- )}
- {previewType === 'Discover Users' && (
+ {(previewType === 'Discover Users' ||
+ previewType === 'Tag Selection' ||
+ previewType === 'Comment') && (
<>
<Text style={usernameStyle}>{usernameToDisplay}</Text>
</>
@@ -368,6 +375,35 @@ const styles = StyleSheet.create({
marginRight: 15,
borderRadius: 50,
},
+ tagSelectionContainer: {
+ width: 60,
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'flex-start',
+ margin: '1%',
+ },
+ tagSelectionAvatar: {
+ width: 34,
+ height: 34,
+ borderRadius: 20,
+ },
+ tagSelectionNameContainer: {
+ width: '100%',
+ marginVertical: '10%',
+ },
+ tagSelectionUsername: {
+ fontWeight: '500',
+ fontSize: normalize(9),
+ lineHeight: normalize(10),
+ letterSpacing: normalize(0.2),
+ color: 'white',
+ textAlign: 'center',
+ },
+ tagSelectionName: {
+ fontWeight: '500',
+ fontSize: 8,
+ color: 'white',
+ },
});
export default ProfilePreview;
diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx
index 25ea3b59..a17d0695 100644
--- a/src/components/search/SearchBar.tsx
+++ b/src/components/search/SearchBar.tsx
@@ -21,10 +21,10 @@ import {getSearchSuggestions, normalize} from '../../utils';
const AnimatedIcon = Animated.createAnimatedComponent(Icon);
interface SearchBarProps extends TextInputProps {
- onCancel: () => void;
- animationProgress: Animated.SharedValue<number>;
- searching: boolean;
- onLayout: (e: LayoutChangeEvent) => void;
+ onCancel?: () => void;
+ animationProgress?: Animated.SharedValue<number>;
+ searching?: boolean;
+ onLayout?: (e: LayoutChangeEvent) => void;
}
const SearchBar: React.FC<SearchBarProps> = ({
onFocus,
@@ -113,8 +113,8 @@ const SearchBar: React.FC<SearchBarProps> = ({
* On-search marginRight style ("cancel" button slides and fades in).
*/
const animatedStyles = useAnimatedStyle<ViewStyle>(() => ({
- marginRight: animationProgress.value * 58,
- opacity: animationProgress.value,
+ marginRight: (animationProgress ? animationProgress.value : 0) * 58,
+ opacity: animationProgress ? animationProgress.value : 0,
}));
return (
@@ -136,11 +136,13 @@ const SearchBar: React.FC<SearchBarProps> = ({
{...{placeholder, value, onChangeText, onFocus, onBlur}}
/>
</Animated.View>
- <Animated.View style={animatedStyles}>
- <TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
- <Text style={styles.cancelText}>Cancel</Text>
- </TouchableOpacity>
- </Animated.View>
+ {onCancel && (
+ <Animated.View style={animatedStyles}>
+ <TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
+ <Text style={styles.cancelText}>Cancel</Text>
+ </TouchableOpacity>
+ </Animated.View>
+ )}
</View>
);
};
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index f533563d..99d3901b 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -65,6 +65,7 @@ export const TAGG_DARK_BLUE = '#4E699C';
export const TAGG_LIGHT_BLUE: string = '#698DD3';
export const TAGG_LIGHT_BLUE_2: string = '#6EE7E7';
export const TAGG_LIGHT_PURPLE = '#F4DDFF';
+export const RADIO_BUTTON_GREY: string = '#BEBEBE';
export const TAGGS_GRADIENT = {
start: '#9F00FF',
diff --git a/src/constants/index.ts b/src/constants/index.ts
index a9cfe947..96ed3fb0 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -1,3 +1,4 @@
export * from './api';
export * from './constants';
export * from './regex';
+export * from './badges';
diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx
index 8f2192f1..aeead38d 100644
--- a/src/routes/main/MainStackNavigator.tsx
+++ b/src/routes/main/MainStackNavigator.tsx
@@ -6,6 +6,7 @@ import {Image} from 'react-native-image-crop-picker';
import {
CommentBaseType,
MomentType,
+ ProfilePreviewType,
ScreenType,
SearchCategoryType,
} from '../../types';
@@ -39,6 +40,7 @@ export type MainStackParams = {
title: string;
image: Image;
screenType: ScreenType;
+ selectedUsers?: ProfilePreviewType[];
};
IndividualMoment: {
moment: MomentType;
@@ -97,6 +99,14 @@ export type MainStackParams = {
ChatList: undefined;
Chat: undefined;
NewChatModal: undefined;
+ TagSelectionScreen: {
+ selectedUsers: ProfilePreviewType[];
+ };
+ TagFriendsScreen: {
+ image: Image;
+ screenType: ScreenType;
+ selectedUsers?: ProfilePreviewType[];
+ };
};
export const MainStack = createStackNavigator<MainStackParams>();
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index d76f9137..1b646f24 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -32,6 +32,8 @@ import {
SuggestedPeopleScreen,
SuggestedPeopleUploadPictureScreen,
SuggestedPeopleWelcomeScreen,
+ TagSelectionScreen,
+ TagFriendsScreen,
} from '../../screens';
import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders';
import {ScreenType} from '../../types';
@@ -310,6 +312,17 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
component={NewChatModal}
options={{headerShown: false, ...newChatModalStyle}}
/>
+ <MainStack.Screen
+ name="TagSelectionScreen"
+ component={TagSelectionScreen}
+ options={{
+ ...headerBarOptions('black', ''),
+ }}
+ />
+ <MainStack.Screen
+ name="TagFriendsScreen"
+ component={TagFriendsScreen}
+ />
</MainStack.Navigator>
);
};
diff --git a/src/screens/chat/ChatScreen.tsx b/src/screens/chat/ChatScreen.tsx
index 17618867..8991d65b 100644
--- a/src/screens/chat/ChatScreen.tsx
+++ b/src/screens/chat/ChatScreen.tsx
@@ -21,6 +21,7 @@ import {
MessageFooter,
TypingIndicator,
} from '../../components';
+import {TAGG_LIGHT_BLUE_2} from '../../constants/constants';
import {MainStackParams} from '../../routes';
import {ScreenType} from '../../types';
import {HeaderHeight, normalize, SCREEN_WIDTH} from '../../utils';
@@ -38,7 +39,7 @@ const ChatScreen: React.FC<ChatScreenProps> = ({navigation}) => {
const insets = useSafeAreaInsets();
const chatTheme: DeepPartial<Theme> = {
colors: {
- accent_blue: '#6EE7E7',
+ accent_blue: TAGG_LIGHT_BLUE_2,
},
messageList: {
container: {
diff --git a/src/screens/chat/ChatSearchBar.tsx b/src/screens/chat/ChatSearchBar.tsx
index 91018d4c..1c91f493 100644
--- a/src/screens/chat/ChatSearchBar.tsx
+++ b/src/screens/chat/ChatSearchBar.tsx
@@ -17,6 +17,7 @@ interface SearchBarProps extends TextInputProps {
onCancel: () => void;
searching: boolean;
placeholder: string;
+ label?: string;
}
const ChatSearchBar: React.FC<SearchBarProps> = ({
onFocus,
@@ -26,6 +27,7 @@ const ChatSearchBar: React.FC<SearchBarProps> = ({
onCancel,
onLayout,
placeholder,
+ label,
}) => {
const handleSubmit = (
e: NativeSyntheticEvent<TextInputSubmitEditingEventData>,
@@ -34,14 +36,18 @@ const ChatSearchBar: React.FC<SearchBarProps> = ({
Keyboard.dismiss();
};
+ const extraLabelStyle = {paddingLeft: label ? 0 : 10};
+
return (
<View style={styles.container} onLayout={onLayout}>
<Animated.View style={styles.inputContainer}>
- <Animated.View style={styles.searchTextContainer}>
- <Text style={styles.searchTextStyes}>To:</Text>
- </Animated.View>
+ {label && (
+ <Animated.View style={styles.searchTextContainer}>
+ <Text style={styles.searchTextStyes}>{label}</Text>
+ </Animated.View>
+ )}
<TextInput
- style={styles.input}
+ style={[extraLabelStyle, styles.input]}
placeholderTextColor={'#828282'}
onSubmitEditing={handleSubmit}
clearButtonMode="always"
diff --git a/src/screens/chat/NewChatModal.tsx b/src/screens/chat/NewChatModal.tsx
index 9872dd6f..e57e7f7a 100644
--- a/src/screens/chat/NewChatModal.tsx
+++ b/src/screens/chat/NewChatModal.tsx
@@ -98,6 +98,7 @@ const NewChatModal: React.FC<NewChatModalProps> = ({
value={query}
searching={searching}
placeholder={''}
+ label={'To:'}
/>
{results.length > 0 && (
<View style={styles.headerContainerStyles}>
diff --git a/src/screens/index.ts b/src/screens/index.ts
index 44ae4b52..0c7d911f 100644
--- a/src/screens/index.ts
+++ b/src/screens/index.ts
@@ -6,3 +6,4 @@ export * from './suggestedPeople';
export * from './suggestedPeopleOnboarding';
export * from './badge';
export * from './chat';
+export * from './moments';
diff --git a/src/screens/moments/TagFriendsScreen.tsx b/src/screens/moments/TagFriendsScreen.tsx
new file mode 100644
index 00000000..ba180921
--- /dev/null
+++ b/src/screens/moments/TagFriendsScreen.tsx
@@ -0,0 +1,152 @@
+import {RouteProp} from '@react-navigation/core';
+import {useNavigation} from '@react-navigation/native';
+import React, {Fragment, useEffect, useRef, useState} from 'react';
+import {
+ Image,
+ Keyboard,
+ KeyboardAvoidingView,
+ Platform,
+ StyleSheet,
+ TouchableWithoutFeedback,
+ View,
+} from 'react-native';
+import {Button} from 'react-native-elements';
+import {MainStackParams} from 'src/routes';
+import {
+ CaptionScreenHeader,
+ MomentTags,
+ SearchBackground,
+ TaggLoadingIndicator,
+} from '../../components';
+import {TagFriendsFooter} from '../../components/moments';
+import {TAGG_LIGHT_BLUE_2} from '../../constants';
+import {ProfilePreviewType} from '../../types';
+import {SCREEN_WIDTH, StatusBarHeight} from '../../utils';
+
+type TagFriendsScreenRouteProps = RouteProp<
+ MainStackParams,
+ 'TagFriendsScreen'
+>;
+interface TagFriendsScreenProps {
+ route: TagFriendsScreenRouteProps;
+}
+const TagFriendsScreen: React.FC<TagFriendsScreenProps> = ({route}) => {
+ const {image, selectedUsers} = route.params;
+ const navigation = useNavigation();
+ const imageRef = useRef(null);
+ const [taggedUsers, setTaggedUsers] = useState<ProfilePreviewType[]>([]);
+
+ /*
+ * Update list of tagged users from route params
+ */
+ useEffect(() => {
+ setTaggedUsers(selectedUsers ? selectedUsers : []);
+ }, [route.params]);
+
+ /*
+ * Navigate back to Tag Users Screen, send selected users
+ */
+ const handleDone = () => {
+ navigation.navigate('CaptionScreen', {
+ ...route.params,
+ selectedUsers: taggedUsers,
+ });
+ };
+
+ 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>
+ <CaptionScreenHeader
+ style={styles.header}
+ title={'Tap on photo to Tag friends!'}
+ />
+ <Image
+ ref={imageRef}
+ style={styles.image}
+ source={{uri: image.path}}
+ resizeMode={'cover'}
+ />
+ <MomentTags
+ editing={true}
+ tags={taggedUsers.map((user) => ({
+ id: '',
+ x: 0,
+ y: 0,
+ user,
+ }))}
+ imageRef={imageRef}
+ deleteFromList={(user) =>
+ setTaggedUsers(taggedUsers.filter((u) => u.id !== user.id))
+ }
+ />
+ <View style={{marginHorizontal: '5%', marginTop: '3%'}}>
+ <TagFriendsFooter
+ taggedUsers={taggedUsers}
+ setTaggedUsers={setTaggedUsers}
+ />
+ </View>
+ </View>
+ </KeyboardAvoidingView>
+ </TouchableWithoutFeedback>
+ </SearchBackground>
+ );
+};
+
+const styles = StyleSheet.create({
+ contentContainer: {
+ paddingTop: StatusBarHeight,
+ justifyContent: 'flex-end',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginLeft: '5%',
+ marginRight: '5%',
+ },
+ button: {
+ backgroundColor: 'transparent',
+ },
+ shareButtonTitle: {
+ fontWeight: 'bold',
+ color: TAGG_LIGHT_BLUE_2,
+ },
+ header: {
+ marginVertical: 20,
+ },
+ image: {
+ position: 'relative',
+ width: SCREEN_WIDTH,
+ aspectRatio: 1,
+ marginBottom: '3%',
+ },
+ text: {
+ position: 'relative',
+ backgroundColor: 'white',
+ width: '100%',
+ paddingHorizontal: '2%',
+ paddingVertical: '1%',
+ height: 60,
+ },
+ flex: {
+ flex: 1,
+ },
+});
+
+export default TagFriendsScreen;
diff --git a/src/screens/moments/TagSelectionScreen.tsx b/src/screens/moments/TagSelectionScreen.tsx
new file mode 100644
index 00000000..8d679b87
--- /dev/null
+++ b/src/screens/moments/TagSelectionScreen.tsx
@@ -0,0 +1,178 @@
+import {useNavigation} from '@react-navigation/core';
+import {RouteProp} from '@react-navigation/native';
+import React, {useEffect, useState} from 'react';
+import {
+ SafeAreaView,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import {FlatList} from 'react-native-gesture-handler';
+import BackIcon from '../../assets/icons/back-arrow.svg';
+import {SearchBar, TaggUserSelectionCell} from '../../components';
+import {SEARCH_ENDPOINT_MESSAGES} from '../../constants';
+import {MainStackParams} from '../../routes';
+import {loadSearchResults} from '../../services';
+import {ProfilePreviewType} from '../../types';
+import {
+ loadTaggUserSuggestions,
+ normalize,
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+} from '../../utils';
+
+type TagSelectionScreenRouteProps = RouteProp<
+ MainStackParams,
+ 'TagSelectionScreen'
+>;
+interface TagSelectionScreenProps {
+ route: TagSelectionScreenRouteProps;
+}
+
+const TagSelectionScreen: React.FC<TagSelectionScreenProps> = ({route}) => {
+ const navigation = useNavigation();
+ const [users, setUsers] = useState<ProfilePreviewType[]>([]);
+ const [selectedUsers, setSelectedUsers] = useState<ProfilePreviewType[]>(
+ route.params.selectedUsers,
+ );
+ const [searching, setSearching] = useState(false);
+ const [query, setQuery] = useState<string>('');
+ const [label, setLabel] = useState<string>('Recent');
+
+ /*
+ * Add back button, Send selected users to CaptionScreen
+ */
+ useEffect(() => {
+ navigation.setOptions({
+ headerLeft: () => (
+ <TouchableOpacity
+ onPress={() => {
+ navigation.navigate('TagFriendsScreen', {
+ ...route.params,
+ selectedUsers: selectedUsers,
+ });
+ }}>
+ <BackIcon
+ height={normalize(18)}
+ width={normalize(18)}
+ color={'black'}
+ style={styles.backButton}
+ />
+ </TouchableOpacity>
+ ),
+ });
+ });
+
+ /*
+ * Load the initial list users from search/suggested endpoint
+ * that the loggedInUser might want to select
+ */
+ const loadUsers = async () => {
+ const data: ProfilePreviewType[] = await loadTaggUserSuggestions();
+ const filteredData: ProfilePreviewType[] = data.filter((user) => {
+ const index = selectedUsers.findIndex((s) => s.id === user.id);
+ return index === -1;
+ });
+ setUsers([...filteredData, ...selectedUsers]);
+ };
+
+ /*
+ * Load list of users based on search query
+ */
+ const getQuerySuggested = async () => {
+ if (query.length > 0) {
+ const searchResults = await loadSearchResults(
+ `${SEARCH_ENDPOINT_MESSAGES}?query=${query}`,
+ );
+ setUsers(searchResults?.users);
+ } else {
+ setUsers([]);
+ }
+ };
+
+ /*
+ * Make calls to appropriate functions to load user lists for selection
+ */
+ useEffect(() => {
+ if (query.length === 0) {
+ setLabel('Recent');
+ loadUsers();
+ }
+
+ if (!searching) {
+ return;
+ }
+
+ if (query.length < 3) {
+ return;
+ }
+ setLabel('');
+ getQuerySuggested();
+ }, [query]);
+
+ return (
+ <SafeAreaView style={styles.safeAreaView}>
+ <View style={styles.searchBarContainer}>
+ <SearchBar
+ onChangeText={setQuery}
+ onFocus={() => {
+ setSearching(true);
+ }}
+ value={query}
+ />
+ </View>
+ <Text style={styles.title}>{label}</Text>
+ {users && (
+ <FlatList
+ data={users}
+ keyExtractor={(item) => item.id}
+ renderItem={(item) => (
+ <TaggUserSelectionCell
+ key={item.item.id}
+ item={item.item}
+ selectedUsers={selectedUsers}
+ setSelectedUsers={setSelectedUsers}
+ />
+ )}
+ />
+ )}
+ </SafeAreaView>
+ );
+};
+
+const styles = StyleSheet.create({
+ safeAreaView: {
+ backgroundColor: 'white',
+ height: SCREEN_HEIGHT,
+ },
+ backButton: {
+ marginLeft: 30,
+ marginTop: 20,
+ },
+ searchBarContainer: {
+ width: SCREEN_WIDTH * 0.9,
+ alignSelf: 'flex-end',
+ marginTop: 10,
+ },
+ searchContainer: {
+ alignSelf: 'center',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ width: '100%',
+ height: normalize(42),
+ alignItems: 'center',
+ marginBottom: '3%',
+ marginHorizontal: 10,
+ },
+ title: {
+ fontWeight: '700',
+ fontSize: normalize(17),
+ lineHeight: normalize(20.29),
+ marginHorizontal: '7%',
+ marginTop: '4%',
+ marginBottom: '2%',
+ },
+});
+
+export default TagSelectionScreen;
diff --git a/src/screens/moments/index.ts b/src/screens/moments/index.ts
new file mode 100644
index 00000000..aac2ddeb
--- /dev/null
+++ b/src/screens/moments/index.ts
@@ -0,0 +1,2 @@
+export {default as TagSelectionScreen} from './TagSelectionScreen';
+export {default as TagFriendsScreen} from './TagFriendsScreen';
diff --git a/src/screens/onboarding/BasicInfoOnboarding.tsx b/src/screens/onboarding/BasicInfoOnboarding.tsx
index 3058a04e..e5e6f59b 100644
--- a/src/screens/onboarding/BasicInfoOnboarding.tsx
+++ b/src/screens/onboarding/BasicInfoOnboarding.tsx
@@ -27,6 +27,7 @@ import {
nameRegex,
passwordRegex,
phoneRegex,
+ TAGG_LIGHT_BLUE_2,
usernameRegex,
} from '../../constants';
import {
@@ -70,9 +71,8 @@ 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),
);
@@ -565,7 +565,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
arrow: {
- color: '#6EE7E7',
+ color: TAGG_LIGHT_BLUE_2,
},
showPassContainer: {
marginVertical: '1%',
diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx
index 56fe672e..2093a1f9 100644
--- a/src/screens/profile/CaptionScreen.tsx
+++ b/src/screens/profile/CaptionScreen.tsx
@@ -1,6 +1,6 @@
import {RouteProp} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
-import React, {Fragment, useRef, useState} from 'react';
+import React, {Fragment, useEffect, useState} from 'react';
import {
Alert,
Image,
@@ -8,13 +8,16 @@ import {
KeyboardAvoidingView,
Platform,
StyleSheet,
+ Text,
+ TouchableOpacity,
TouchableWithoutFeedback,
View,
} from 'react-native';
import {MentionInput} from 'react-native-controlled-mentions';
-import {Button} from 'react-native-elements';
+import {Button, normalize} from 'react-native-elements';
import {useDispatch, useSelector} from 'react-redux';
-import {MomentTags, SearchBackground} from '../../components';
+import FrontArrow from '../../assets/icons/front-arrow.svg';
+import {SearchBackground} from '../../components';
import {CaptionScreenHeader} from '../../components/';
import TaggLoadingIndicator from '../../components/common/TaggLoadingIndicator';
import {TAGG_LIGHT_BLUE_2} from '../../constants';
@@ -26,6 +29,7 @@ import {
updateProfileCompletionStage,
} from '../../store/actions';
import {RootState} from '../../store/rootReducer';
+import {ProfilePreviewType} from '../../types';
import {SCREEN_WIDTH, StatusBarHeight} from '../../utils';
import {mentionPartTypes} from '../../utils/comments';
@@ -43,41 +47,34 @@ interface CaptionScreenProps {
}
const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => {
- const {title, image, screenType} = route.params;
+ const {title, image, screenType, selectedUsers} = route.params;
const {
user: {userId},
} = useSelector((state: RootState) => state.user);
const dispatch = useDispatch();
const [caption, setCaption] = useState('');
const [loading, setLoading] = useState(false);
- const imageRef = useRef(null);
+ const [taggedUsers, setTaggedUsers] = useState<ProfilePreviewType[]>([]);
+ const [taggedList, setTaggedList] = useState<string>('');
- const [taggList, setTaggList] = useState([
- {
- first_name: 'Ivan',
- id: 'cee45bf8-7f3d-43c8-99bb-ec04908efe58',
- last_name: 'Chen',
- thumbnail_url:
- 'https://tagg-dev.s3.us-east-2.amazonaws.com/thumbnails/smallProfilePicture/spp-cee45bf8-7f3d-43c8-99bb-ec04908efe58-thumbnail.jpg',
- username: 'ivan.tagg',
- },
- {
- first_name: 'Ankit',
- id: '3bcf6947-bee6-46b0-ad02-6f4d25eaeac3',
- last_name: 'Thanekar',
- thumbnail_url:
- 'https://tagg-dev.s3.us-east-2.amazonaws.com/thumbnails/smallProfilePicture/spp-3bcf6947-bee6-46b0-ad02-6f4d25eaeac3-thumbnail.jpg',
- username: 'ankit.thanekar',
- },
- {
- first_name: 'Ankit',
- id: '3bcf6947-bee6-46b0-ad02-6f4d25eaeac3',
- last_name: 'Thanekar',
- thumbnail_url:
- 'https://tagg-dev.s3.us-east-2.amazonaws.com/thumbnails/smallProfilePicture/spp-3bcf6947-bee6-46b0-ad02-6f4d25eaeac3-thumbnail.jpg',
- username: 'ankit.thanekar',
- },
- ]);
+ useEffect(() => {
+ setTaggedUsers(selectedUsers ? selectedUsers : []);
+ }, [route.params]);
+
+ useEffect(() => {
+ const getTaggedUsersListString = () => {
+ let listString = '';
+ for (let i = 0; i < taggedUsers.length; i++) {
+ if (listString.length < 21) {
+ listString = listString.concat(`@${taggedUsers[i].username} `);
+ } else {
+ break;
+ }
+ }
+ setTaggedList(listString);
+ };
+ getTaggedUsersListString();
+ }, [taggedUsers]);
const navigateToProfile = () => {
//Since the logged In User is navigating to own profile, useXId is not required
@@ -135,7 +132,6 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => {
<CaptionScreenHeader style={styles.header} {...{title: title}} />
{/* this is the image we want to center our tags' initial location within */}
<Image
- ref={imageRef}
style={styles.image}
source={{uri: image.path}}
resizeMode={'cover'}
@@ -148,19 +144,43 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => {
onChange={setCaption}
partTypes={mentionPartTypes('blue')}
/>
- <MomentTags
- editing={true}
- tags={taggList.map((user) => ({
- id: '',
- x: 0,
- y: 0,
- user,
- }))}
- imageRef={imageRef}
- deleteFromList={(user) =>
- setTaggList(taggList.filter((u) => u.id !== user.id))
- }
- />
+ <View
+ style={{
+ marginHorizontal: '5%',
+ marginTop: '3%',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ }}>
+ <Image
+ source={require('../../assets/icons/tagging/tag-icon.png')}
+ style={{width: 20, height: 20}}
+ />
+ <Text style={styles.tagFriendsTitle}>Tag Friends</Text>
+ <Text
+ numberOfLines={1}
+ style={{
+ color: 'white',
+ width: 150,
+ fontSize: normalize(10),
+ lineHeight: normalize(11),
+ letterSpacing: normalize(0.3),
+ textAlign: 'right',
+ }}>
+ {taggedList}
+ {taggedList.length > 21 ? '. . .' : ''}
+ </Text>
+ <TouchableOpacity
+ onPress={() =>
+ navigation.navigate('TagFriendsScreen', {
+ image: image,
+ screenType: screenType,
+ selectedUsers: taggedUsers,
+ })
+ }>
+ <FrontArrow width={12} height={12} color={'white'} />
+ </TouchableOpacity>
+ </View>
</View>
</KeyboardAvoidingView>
</TouchableWithoutFeedback>
@@ -205,6 +225,14 @@ const styles = StyleSheet.create({
flex: {
flex: 1,
},
+ tagFriendsTitle: {
+ color: 'white',
+ fontSize: normalize(12),
+ lineHeight: normalize(16.71),
+ letterSpacing: normalize(0.3),
+ fontWeight: '600',
+ },
+ tagFriendsContainer: {flexDirection: 'row', marginTop: '3%'},
});
export default CaptionScreen;
diff --git a/src/screens/profile/InviteFriendsScreen.tsx b/src/screens/profile/InviteFriendsScreen.tsx
index 4f6319f7..89f2e62f 100644
--- a/src/screens/profile/InviteFriendsScreen.tsx
+++ b/src/screens/profile/InviteFriendsScreen.tsx
@@ -9,14 +9,12 @@ import {
StatusBar,
StyleSheet,
Text,
- TextInput,
TouchableWithoutFeedback,
View,
} from 'react-native';
import {checkPermission} from 'react-native-contacts';
-import Animated from 'react-native-reanimated';
-import Icon from 'react-native-vector-icons/Feather';
-import {TabsGradient} from '../../components';
+import {TAGG_LIGHT_BLUE} from '../../constants';
+import {SearchBar, TabsGradient} from '../../components';
import {InviteFriendTile} from '../../components/friends';
import {headerBarOptions} from '../../routes';
import {
@@ -33,7 +31,6 @@ import {
SCREEN_WIDTH,
StatusBarHeight,
} from '../../utils';
-const AnimatedIcon = Animated.createAnimatedComponent(Icon);
export type InviteContactType = {
firstName: string;
@@ -193,32 +190,13 @@ const InviteFriendsScreen: React.FC = () => {
</Text>
</View>
<View style={styles.container}>
- <Animated.View style={styles.inputContainer}>
- <AnimatedIcon
- name="search"
- color={'#7E7E7E'}
- size={16}
- style={styles.searchIcon}
- />
- <TextInput
- style={[styles.input]}
- placeholderTextColor={'#828282'}
- clearButtonMode="while-editing"
- autoCapitalize="none"
- autoCorrect={false}
- onChangeText={(text) => {
- setQuery(text.toLowerCase());
- }}
- onBlur={() => {
- Keyboard.dismiss();
- }}
- onEndEditing={() => {
- Keyboard.dismiss();
- }}
- value={query}
- placeholder={'Search'}
- />
- </Animated.View>
+ <SearchBar
+ onChangeText={setQuery}
+ onBlur={() => {
+ Keyboard.dismiss();
+ }}
+ value={query}
+ />
</View>
<View
style={[
@@ -278,43 +256,44 @@ const styles = StyleSheet.create({
marginBottom: '5%',
},
container: {
+ width: '100%',
+ height: normalize(42),
+ marginBottom: '3%',
+ },
+ ppContainer: {
alignSelf: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
height: normalize(42),
alignItems: 'center',
- marginBottom: '3%',
+ marginBottom: '5%',
marginHorizontal: 10,
},
- inputContainer: {
- flexGrow: 1,
- flexDirection: 'row',
- alignItems: 'center',
- paddingHorizontal: 8,
- marginHorizontal: '3%',
- borderRadius: 20,
- backgroundColor: '#F0F0F0',
- height: 34,
- },
- searchIcon: {
- marginRight: '5%',
- },
- input: {
- flex: 1,
- fontSize: normalize(16),
- color: '#000',
- letterSpacing: normalize(0.5),
- },
- cancelButton: {
+ friend: {
+ alignSelf: 'center',
height: '100%',
- position: 'absolute',
+ },
+ addFriendButton: {
+ alignSelf: 'center',
justifyContent: 'center',
- paddingHorizontal: 8,
+ alignItems: 'center',
+ width: 82,
+ height: 25,
+ borderColor: TAGG_LIGHT_BLUE,
+ borderWidth: 2,
+ borderRadius: 2,
+ padding: 0,
+ backgroundColor: TAGG_LIGHT_BLUE,
},
- cancelText: {
- color: '#818181',
- fontWeight: '500',
+ addFriendButtonTitle: {
+ color: 'white',
+ padding: 0,
+ fontSize: normalize(11),
+ fontWeight: '700',
+ lineHeight: normalize(13.13),
+ letterSpacing: normalize(0.6),
+ paddingHorizontal: '3.8%',
},
});
diff --git a/src/types/types.ts b/src/types/types.ts
index 1b4b7ecf..e957483b 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -150,7 +150,8 @@ export type PreviewType =
| 'Discover Users'
| 'Friend'
| 'Suggested People Drawer'
- | 'Suggested People Screen';
+ | 'Suggested People Screen'
+ | 'Tag Selection';
export enum ScreenType {
Profile,
diff --git a/src/utils/search.ts b/src/utils/search.ts
index 26f40b1b..789acbc3 100644
--- a/src/utils/search.ts
+++ b/src/utils/search.ts
@@ -1,6 +1,7 @@
import AsyncStorage from '@react-native-community/async-storage';
+import {loadSearchResults} from '../services';
-import {BADGE_DATA} from '../constants/badges';
+import {BADGE_DATA, SEARCH_ENDPOINT_SUGGESTED} from '../constants';
import {
ProfilePreviewType,
CategoryPreviewType,
@@ -138,3 +139,13 @@ export const getRecentlySearchedCategories = async (): Promise<
}
return [];
};
+
+/*
+ * Retrieves and returns a list of suggested tagg users
+ */
+export const loadTaggUserSuggestions = async (): Promise<
+ ProfilePreviewType[]
+> => {
+ const searchResults = await loadSearchResults(`${SEARCH_ENDPOINT_SUGGESTED}`);
+ return searchResults?.users;
+};