Add tag functionality to posts and implement Tags screen

This commit is contained in:
Adolfo Reyna
2025-02-28 00:21:00 -05:00
parent f5c7ff38dd
commit 8cdcfefa0d
6 changed files with 177 additions and 34 deletions

3
API.js
View File

@@ -143,6 +143,9 @@ const API = {
if (userid) return getCall("/post/usr/" + userid); if (userid) return getCall("/post/usr/" + userid);
return getCall("/post/"); return getCall("/post/");
}, },
getPostsByTag(tag) {
return getCall("/post/tag/" + tag);
},
getPostsWithTag(userid, tag = "images") { getPostsWithTag(userid, tag = "images") {
if (userid) return getCall("/post/usr/" + userid + "/" + tag); if (userid) return getCall("/post/usr/" + userid + "/" + tag);
return getCall("/post/" + tag); return getCall("/post/" + tag);

5
App.js
View File

@@ -8,6 +8,7 @@ import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import Login from "./Views/Login.js" import Login from "./Views/Login.js"
import Feed from "./Views/Feed.js" import Feed from "./Views/Feed.js"
import Profile from "./Views/Profile.js" import Profile from "./Views/Profile.js"
import Tags from "./Views/Tags.js"
import Search from './Views/Search.js'; import Search from './Views/Search.js';
import Groups from './Views/Groups.js'; import Groups from './Views/Groups.js';
import Courses from './Views/Courses.js'; import Courses from './Views/Courses.js';
@@ -306,6 +307,10 @@ export default function App() {
name="Profile" name="Profile"
component={Profile} component={Profile}
/> />
<Stack.Screen
name="Tags"
component={Tags}
/>
<Stack.Screen <Stack.Screen
name="NewPost" name="NewPost"
component={NewPostView} component={NewPostView}

77
Views/Tags.js Normal file
View File

@@ -0,0 +1,77 @@
import { StatusBar } from 'expo-status-bar';
import React, { useState, useEffect } from 'react';
import { View, ActivityIndicator, StyleSheet, SafeAreaView, FlatList } from 'react-native';
import { Button, IconButton } from 'react-native-paper';
import API from './../API.js';
import Post from './../components/Post.js';
import NewPost from "./../components/NewPost.js";
import ProfileHeader from '../components/ProfileHeader.js';
import AsyncStorage from '@react-native-async-storage/async-storage';
let Tags = ({ navigation, route }) => {
let [Posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let subscribed = true;
const getData = async () => {
setPosts([]);
console.log("Posts by tag", route.params.tag);
API.getPostsByTag(route.params.tag).then((data) => {
if(!subscribed) return 0;
console.log("Posts by tag", data);
setPosts(data);
setLoading(false);
});
}
getData();
return ()=>{
subscribed = false;
}
}, [route.params?.tag]);
const renderPost = (({ item }) => {
if (item.nonOrganicType)
return (<></>);
return (<Post post={item} />);
});
const header = (
<View>
</View>
)
return (
<SafeAreaView style={styles.container}>
<View>
{!loading ?
<FlatList
data={Posts}
renderItem={renderPost}
keyExtractor={item => item.lastUpdated || item._id || item.ceatedAt}
ListHeaderComponent={header}
refreshing={loading}
initialNumToRender={3}
maxToRenderPerBatch={3}
removeClippedSubviews={true}
onRefresh={() => {
API.getPostsByTag(route.params.tag).then(setPosts);
}}
/> :
<></> //TODO: Add empty profile card here
}
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}
export default Tags;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#edf2f7",
},
});

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Text, Pressable, FlatList, StyleSheet, View, Share } from 'react-native'; import { Text, Pressable, FlatList, StyleSheet, View, Share, Alert, Linking } from 'react-native';
import Hyperlink from 'react-native-hyperlink' import Hyperlink from 'react-native-hyperlink'
import { Button, Card, Chip } from 'react-native-paper'; import { Button, Card, Chip } from 'react-native-paper';
import API from './../API.js'; import API from './../API.js';
@@ -14,6 +14,7 @@ import i18n from "../i18nMessages.js";
import ProfilePhotoCircle from './ProfilePhotoCircle.js'; import ProfilePhotoCircle from './ProfilePhotoCircle.js';
import { posthog } from './../PostHog.js'; import { posthog } from './../PostHog.js';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import ParsedText from 'react-native-parsed-text';
let Post = (props) => { let Post = (props) => {
@@ -67,6 +68,29 @@ let Post = (props) => {
const renderComment = ({ item }) => ( const renderComment = ({ item }) => (
<Comment comment={item} postid={post._id} /> <Comment comment={item} postid={post._id} />
); );
const handleTagPress = (tag) => {
// Alert.alert("tag pressed", `You pressed the tag: ${tag}`);
// You can navigate to another screen or perform any other action here
//remove hastag from tag
tag = tag.replace("#", "");
navigation.navigate("Tags", { tag: tag });
};
const handleLinkPress = (url) => {
Linking.canOpenURL(url)
.then((supported) => {
if (supported) {
Linking.openURL(url);
} else {
Alert.alert('Error', 'Unable to open the link.');
}
})
.catch((err) => console.error('An error occurred', err));
};
const handleLinkLongPress = (url) => {
Share.share({
url: url
});
};
return ( return (
<Card style={styles.card}> <Card style={styles.card}>
<Card.Content style={{ <Card.Content style={{
@@ -74,7 +98,7 @@ let Post = (props) => {
margin: 0, margin: 0,
marginBottom: 0 marginBottom: 0
}}> }}>
<Hyperlink linkDefault={true} linkStyle={{ color: '#2980b9' }}>
{!post.nonOrganicType ? {!post.nonOrganicType ?
<View> <View>
<ProfilePhotoCircle profileid={post.profileid} /> <ProfilePhotoCircle profileid={post.profileid} />
@@ -83,16 +107,23 @@ let Post = (props) => {
</View> </View>
<Pressable onLongPress={() => { <Pressable onLongPress={() => {
if(cleanContent.length > 10){ if (cleanContent.length > 10) {
Share.share({ Share.share({
message: cleanContent message: cleanContent
}); });
} }
}}> }}>
<Text style={{ fontSize: 16, padding: 3, paddingLeft: 40 }}>{ <ParsedText
cleanContent style={styles.text}
}</Text> parse={[
{ pattern: /#(\w+)/, style: styles.tag, onPress: handleTagPress },
{ pattern: /(https?:\/\/[^\s]+)/, style: styles.link, onPress: handleLinkPress, onLongPress: handleLinkLongPress },
]}
>
{cleanContent}
</ParsedText>
</Pressable> </Pressable>
<Media content={post.content} postId={post._id} post={post} style={{ paddingTop: 2 }} /> <Media content={post.content} postId={post._id} post={post} style={{ paddingTop: 2 }} />
</View> : </View> :
<View> <View>
@@ -101,7 +132,6 @@ let Post = (props) => {
<Media content={post.content} /> <Media content={post.content} />
</View> </View>
} }
</Hyperlink>
</Card.Content> </Card.Content>
<Card.Actions style={{ flexDirection: "row", flow: 4, fontSize: 16, marginLeft: 36, marginTop: -10 }}> <Card.Actions style={{ flexDirection: "row", flow: 4, fontSize: 16, marginLeft: 36, marginTop: -10 }}>
<Button <Button
@@ -178,5 +208,18 @@ const styles = StyleSheet.create({
margin: 8, margin: 8,
marginTop: 0, marginTop: 0,
padding: 8 padding: 8
} },
text: {
fontSize: 16,
padding: 3,
paddingLeft: 40
},
tag: {
color: '#77B5FE',
textDecorationLine: 'underline',
},
link: {
color: '#77B5FE',
textDecorationLine: 'underline',
},
}); });

14
package-lock.json generated
View File

@@ -40,6 +40,7 @@
"react-native-autoheight-webview": "^1.6.1", "react-native-autoheight-webview": "^1.6.1",
"react-native-hyperlink": "0.0.19", "react-native-hyperlink": "0.0.19",
"react-native-paper": "^4.11.2", "react-native-paper": "^4.11.2",
"react-native-parsed-text": "^0.0.22",
"react-native-safe-area-context": "4.10.5", "react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1", "react-native-screens": "3.31.1",
"react-native-vector-icons": "^9.1.0", "react-native-vector-icons": "^9.1.0",
@@ -13160,6 +13161,19 @@
"color-string": "^1.6.0" "color-string": "^1.6.0"
} }
}, },
"node_modules/react-native-parsed-text": {
"version": "0.0.22",
"resolved": "https://registry.npmjs.org/react-native-parsed-text/-/react-native-parsed-text-0.0.22.tgz",
"integrity": "sha512-hfD83RDXZf9Fvth3DowR7j65fMnlqM9PpxZBGWkzVcUTFtqe6/yPcIoIAgrJbKn6YmtzkivmhWE2MCE4JKBXrQ==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.7.x"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-safe-area-context": { "node_modules/react-native-safe-area-context": {
"version": "4.10.5", "version": "4.10.5",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.5.tgz", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.5.tgz",

View File

@@ -42,6 +42,7 @@
"react-native-autoheight-webview": "^1.6.1", "react-native-autoheight-webview": "^1.6.1",
"react-native-hyperlink": "0.0.19", "react-native-hyperlink": "0.0.19",
"react-native-paper": "^4.11.2", "react-native-paper": "^4.11.2",
"react-native-parsed-text": "^0.0.22",
"react-native-safe-area-context": "4.10.5", "react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1", "react-native-screens": "3.31.1",
"react-native-vector-icons": "^9.1.0", "react-native-vector-icons": "^9.1.0",