Files
EMI-ExpoAPP/Views/Profile.js
2026-02-20 22:10:44 -05:00

301 lines
12 KiB
JavaScript

import { StatusBar } from 'expo-status-bar';
import React, { useState, useEffect } from 'react';
import { View, ActivityIndicator, StyleSheet, SafeAreaView, FlatList, Alert } 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';
import { useSnapshot } from 'valtio';
import GlobalState from '../contexts/GlobalState.js';
const PROFILE_LOG_PREFIX = '[Profile]';
const logProfile = (...args) => {
if (__DEV__) console.log(PROFILE_LOG_PREFIX, ...args);
};
const storeProfilePosts = async (profileid, value) => {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem('profile_' + profileid, jsonValue)
} catch (e) {
}
}
const getProfilePosts = async (profileid) => {
try {
const value = await AsyncStorage.getItem('profile_' + profileid)
if (value !== null) {
return JSON.parse(value);
}
return [];
} catch (e) {
console.log('fail getProfilePosts', e)
return [];
}
}
let Profile = ({ navigation, route }) => {
const viewer = useSnapshot(GlobalState).me || {};
let [Posts, setPosts] = useState([]);
let [profile, setProfile] = useState({});
const [showNewPost, setShowNewPost] = useState(false);
const [tag, setTag] = useState('');
const [loading, setLoading] = useState(true);
const [isDeletingGroup, setIsDeletingGroup] = useState(false);
const isOwnedGroup = !!(
profile?._id &&
profile?.isGroup &&
String(profile?.userid || '') === String(viewer?.userid || '')
);
useEffect(() => {
let subscribed = true;
const getData = async () => {
logProfile('load:start', {
profileid: route?.params?.profileid || null,
tag,
});
setLoading(true);
setPosts([]);
if (route.params && route.params.profileid && tag == '') {
logProfile('cache:read:start', { profileid: route.params.profileid });
await API.getUserProfile(route.params.profileid).then((profileObj) => {
if(!subscribed) return 0;
const nextProfile = profileObj && profileObj._id ? profileObj : {};
const profileData = nextProfile.profile || {};
setProfile(nextProfile);
navigation.setOptions({ title: (profileData.firstName || "") + " " + (profileData.lastName || "") });
logProfile('profile:loaded', {
profileid: nextProfile._id || route.params.profileid,
hasName: !!(profileData.firstName || profileData.lastName),
});
});
await getProfilePosts(route.params.profileid).then((cachedPosts) => {
const safeCachedPosts = Array.isArray(cachedPosts) ? cachedPosts : [];
setPosts(safeCachedPosts);
logProfile('cache:read:end', {
profileid: route.params.profileid,
count: safeCachedPosts.length,
});
});
API.getPosts(route.params.profileid).then((data) => {
if(!subscribed) return 0;
const safePosts = Array.isArray(data) ? data : [];
setPosts(safePosts);
storeProfilePosts(route.params.profileid, safePosts);
setLoading(false);
logProfile('network:loaded', {
profileid: route.params.profileid,
count: safePosts.length,
cached: true,
});
});
} else {
if(route.params && route.params.profileid){
logProfile('tag:load:start', {
profileid: route.params.profileid,
tag,
});
API.getPostsWithTag(route.params.profileid, tag).then((data) => {
if(!subscribed) return 0;
const safeTagPosts = Array.isArray(data?.posts) ? data.posts : [];
setPosts(safeTagPosts);
setLoading(false);
logProfile('tag:load:end', {
profileid: route.params.profileid,
tag,
count: safeTagPosts.length,
});
});
} else {
// if no profile information is pressent should load feed
logProfile('redirect:feed');
navigation.navigate('Feed')
}
}
logProfile('load:end');
}
getData();
return ()=>{
subscribed = false;
logProfile('load:cleanup');
}
}, [tag, route.params?.profileid]);
const getTagPosts = ()=>{
API.getPostsWithTag(tag).then((data) => {
//if(!subscribed) return 0;
console.log(data.posts);
setPosts(Array.isArray(data?.posts) ? data.posts : []);
});
}
const renderPost = (({ item }) => {
if (item.nonOrganicType)
return (<></>);
return (<Post post={item} />);
});
const handleDeleteGroup = () => {
if (!profile?._id || isDeletingGroup) return;
Alert.alert(
"Delete group?",
"This will permanently delete this group profile.",
[
{ text: "Cancel", style: "cancel" },
{
text: "Delete",
style: "destructive",
onPress: async () => {
setIsDeletingGroup(true);
try {
const result = await API.deleteProfile(profile._id);
if (result?.status !== "ok") {
return Alert.alert("Could not delete group", result?.status || "Please try again.");
}
const me = await API.getMe();
if (me && me._id) GlobalState.me = me;
if (navigation.canGoBack()) {
navigation.goBack();
} else {
navigation.reset({
index: 0,
routes: [{ name: 'MainNavigation' }],
});
}
} catch (error) {
Alert.alert("Could not delete group", "Please try again.");
} finally {
setIsDeletingGroup(false);
}
}
}
]
);
};
const header = (
<View>
<ProfileHeader profileObj={profile} key={profile._id} />
<View style={{
flexDirection: "row",
alignItems:"center",
justifyContent: "space-evenly",
paddingTop: 10
}}>
<View style={{
height: 40,
width: 40,
top: 0,
borderRadius: 28,
backgroundColor: "#c44d56",
justifyContent: 'center',
alignItems: 'center',
elevation: 10,
zIndex: 1,
}}>
<IconButton icon={showNewPost ? 'remove' : "add"} mode="outlined" color="white" onPress={()=>{
//setShowNewPost(!showNewPost);
navigation.navigate('NewPost', {toProfile: profile._id})
}} />
</View>
<Button style={{paddingLeft:12, backgroundColor:"#fff"}} title="Images" icon={tag == 'images' ? 'remove' : "image"} mode="outlined" onPress={()=>{
if(tag == 'images') return setTag('');
setTag('images');
}}>{tag == 'images' ? "Images" : ''}</Button>
<Button style={{paddingLeft:12, backgroundColor:"#fff"}} title="Media" icon={tag == 'media' ? 'remove' : "subscriptions"} mode="outlined" onPress={()=>{
if(tag == 'media') return setTag('');
setTag('media');
}}>{tag == 'media' ? "Media" : ''}</Button>
<Button style={{paddingLeft:12, backgroundColor:"#fff"}} title="Embedded" icon={tag == 'embedded' ? 'remove' : "folder"} mode="outlined" onPress={()=>{
if(tag == 'embedded') return setTag('');
setTag('embedded');
}}>{tag == 'embedded' ? "Files" : ''}</Button>
</View>
{isOwnedGroup ? (
<View style={styles.deleteGroupRow}>
<Button
mode="contained"
icon="delete"
buttonColor="#b3261e"
loading={isDeletingGroup}
disabled={isDeletingGroup}
onPress={handleDeleteGroup}
>
Delete Group
</Button>
</View>
) : <></>}
{ showNewPost ?
<NewPost newPostCB={(newPost) => setPosts([newPost, ...Posts])} />
: <></>
}
</View>
)
return (
<SafeAreaView style={styles.container}>
<View>
{(Posts.length !== 0 || profile._id) ?
<FlatList
data={Posts}
renderItem={renderPost}
keyExtractor={item => item.lastUpdated || item._id || item.createdAt}
ListHeaderComponent={header}
refreshing={loading}
initialNumToRender={3}
maxToRenderPerBatch={3}
removeClippedSubviews={true}
onRefresh={() => {
logProfile('refresh:start', {
profileid: route?.params?.profileid || null,
tag,
});
setLoading(true);
Promise.all([
API.getUserProfile(route.params.profileid, true),
API.getPosts(route.params.profileid),
]).then(([profileObj, data]) => {
const nextProfile = profileObj && profileObj._id ? profileObj : {};
const profileData = nextProfile.profile || {};
const safePosts = Array.isArray(data) ? data : [];
setProfile(nextProfile);
navigation.setOptions({ title: (profileData.firstName || "") + " " + (profileData.lastName || "") });
setPosts(safePosts);
storeProfilePosts(route.params.profileid, safePosts);
setLoading(false);
logProfile('refresh:end', {
profileid: route.params.profileid,
count: safePosts.length,
cached: false,
});
}).catch(() => {
setLoading(false);
});
}}
/> :
<></> //TODO: Add empty profile card here
}
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}
export default Profile;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#edf2f7",
},
deleteGroupRow: {
paddingHorizontal: 12,
paddingTop: 12,
},
});