Sherlock / app / (tabs) / (home) / home.tsx
home.tsx
Raw
import { BackHandler, ListRenderItem, ListRenderItemInfo, Pressable, RefreshControl, StyleSheet } from 'react-native';
import { Text, View } from '../../../components/Themed';
import React, { useContext, useEffect } from 'react';
import { useFocusEffect, useNavigationState, useRoute } from '@react-navigation/native';
import constants from 'expo-constants';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import Colors from '../../../constants/Colors';
import { Badge, BadgeText, FlatList, set, Spinner, ToastDescription, VStack} from '@gluestack-ui/themed';
import { TouchableOpacity } from 'react-native-gesture-handler';
import global from '../../../constants/global';
import { router, useLocalSearchParams, useNavigation, useRouter } from 'expo-router';
import { getFeed, getInitPosts, getNewerPosts, getOlderPosts, getUserAlias } from '../../../components/backend';
import { getIncidentEmoji } from '../../../constants/emojis';
import PostListView from '../../../components/postListView';
import { storeData, getData } from '../../../components/storage';
import { Toast, useToast } from '@gluestack-ui/themed';
import { ToastTitle } from '@gluestack-ui/themed';
import { FeedContext } from '../../../components/feedContext';
import { useAuth } from '../../../components/authContext';
import { supabase } from '../../../components/supabase';

export default function Home() {
  const [aliasemoji, setAliasEmoji] = React.useState('๐Ÿ‘€');
  const [refreshing, setRefreshing] = React.useState(false);
  const [cached, setCached] = React.useState(false);
  const [feed, setFeed] = useContext(FeedContext) as any;
  const [loading, setLoading] = React.useState(true);
  const [noUser, setNoUser] = React.useState(true);
  const [userLocation, setUserLocation] = React.useState<any>(null);
  const [locStat, setLocStat] = React.useState<any>();
  const [user, setUser] = React.useState(null);
  let initRan = false;
  const feedRef = React.useRef<any>(null);
  const nav = useNavigation();
  const auth = useAuth()

  //Scrolls to top on tab click
  useEffect(() => {
    //@ts-ignore
    const unsub = nav.getParent()?.addListener('tabPress', (e) => {
      if(nav.isFocused()){
        feedRef.current.scrollToOffset({ animated: true, offset: 0 });
      }
    })

    return unsub;

  }, [nav]);


  function getInit(){
    console.log("Fetching init posts"); 
    getInitPosts().then((res) => {
      initRan = true;
      let temp:any[] = res.posts.concat(res.reportposts)
      if(temp.length == 0){
          console.log("No posts fetched on start. Using cache...");
          setFeed(cached);
          setRefreshing(false);
          setLoading(false);
          return;
        }
        
        setNoUser(false);

        temp.sort(function(a:any, b:any){
          return b.eid - a.eid;
        })
      
        //Makes sure there is no duplicates
        for(let i=0;i<feed.length;i++){
          for(let j=0;j<temp.length;j++){
            if(feed[i].eid == temp[j].eid){
              temp.splice(j,1);
            }
        }}
      
        for(let i = 0; i < temp.length; i++){
          temp[i].liked = false;
          temp[i].likeCount=0;
          temp[i].commentCount=0;
        }
      
        console.log("Init fetched: ", temp.length, "posts");
        setRefreshing(false);
        setFeed(temp);
        setLoading(false);
      
      }).catch((err) => {
          console.log("Error fetching init posts: ", err);
          if(err.message == "No current user"){
            setNoUser(true);
            return;
          }else{
            setFeed(cached)
          }

      })
      setRefreshing(false);
  }

  //Fetches cache on mount
  useEffect(() => {
    // Fetches cached feed
    getData("feedCache")
      .then((res) => {
        if (res && feed.length == 0) {
          setCached(res);
        }
      })
      .catch((err) => {
        console.log("Error fetching cached feed: ", err);
      })
  }, []);


  //Fetches init feed on login
  useEffect(()=>{
    const {data: { subscription }} = supabase.auth.onAuthStateChange((_event, session) => {
          if(session?.user){
            //Sets user alias and its emoji
            getUserAlias()
              .then((alias) => {
                setNoUser(false);

                let temp = getIncidentEmoji(alias.alias, "6", true);
                global.aliasEmoji = temp[0];
                global.aliasClass = temp[1];

                global.alias = alias.alias;
                setAliasEmoji(global.aliasEmoji);
              })
              .catch((err) => {
                console.log("Could not fetch a user alias at home.", err);
                if (err.message == "No current user") {
                  setNoUser(true);
                  return;
                }
              });

            getInit();
          }
          
      })
      
      return () => {
          subscription.unsubscribe()
      }
  },[])

  
  // Prevents the back button from exiting the app
  useFocusEffect(
    React.useCallback(() => {
      const onBackPress = () => {
        return true;
      };

      const noback = BackHandler.addEventListener("hardwareBackPress", onBackPress);

      noback.remove()
    }, [])
  );

  function handleRefresh(clear:boolean=false){
    setRefreshing(true);

    if(noUser){
      console.log("No user, cannot refresh feed");
      setRefreshing(false);
      return;
    }

    //Uses oldest local post as reference for fetching more posts
    //Will only get posts older than the oldest local post
    if(feed.length == 0 || !feed){
      getInit();
      return;
    }

    getNewerPosts(feed[0].eid).then((res) => {
      if (!res) {
        console.log("Error fetching more posts");
        setRefreshing(false);
        return;
      }

      if (res.message) {
        if (res.message == "Invalid request") {
          console.log("Invalid request");
          setRefreshing(false);
          return;
        }

        setRefreshing(false);
        return;
      }

      setNoUser(false);

      if (res.posts.length == 0 && res.reportposts.length == 0) {
        console.log("No more posts");
        setRefreshing(false);
        return;
      }

      let temp = res.posts.concat(res.reportposts);

      temp.sort(function(a:any, b:any){
        return b.eid - a.eid;
      })

      for(let i = 0; i < temp.length; i++){
        temp[i].liked = false;
        temp[i].likeCount=0;
      }

      if(clear){
        storeData('feedCache', []).then(() => {
          setFeed(temp);
        }).catch((err) => {
          console.log("Error clearing cache: ", err);
        })
        return;
      }

      storeData('feedCache', [...temp, ...feed]).then(() => {
        console.log("Feed cache updated");
      }).catch((err) => {
        console.log("Error updating cache: ", err);
      })

      console.log("Fetched: ", temp.length, "posts");
      setFeed([...temp, ...feed]);
    }).catch((err) => {
      console.log("Error fetching more posts: ", err);
      if(err.message == "No current user"){
        setNoUser(true);
        return;
      }
    })
  }

  function handleEndReached(){
    if(noUser || !feed || feed.length == 0){return};
    setRefreshing(true);

    //Checking for edge case where feed is empty.
    //This mainly just applies to startup
    if((feed.length == 0 || !feed)&&initRan){
      setRefreshing(false);
      getInit();
      return;
    }else if(!initRan && feed.length == 0){
      setRefreshing(false);
      return;
    }

    if(noUser || !feed[feed.length-1].eid){
      return
    }

    //Uses oldest local post as reference for fetching more posts
    getOlderPosts(feed[feed.length-1].eid).then((res) => {
      if (!res) {
        console.log("Error fetching more posts");
        setRefreshing(false);
        return;
      }

      if (res.message) {
        if (res.message == "Invalid request") {
          console.log("Invalid request");
          setRefreshing(false);
          return;
        }

        setRefreshing(false);
        return;
      }

      if (res.posts.length == 0 && res.reportposts.length == 0) {
        console.log("No more posts");
        setRefreshing(false);
        return;
      }

      let temp = res.posts.concat(res.reportposts);

      temp.sort(function(a:any, b:any){
        return b.eid - a.eid;
      })

      for(let i = 0; i < temp.length; i++){
        temp[i].liked = false;
        temp[i].likeCount=0;
      }

      
      storeData('feedCache', [...feed, ...temp]).then(() => {
        console.log("Feed cache updated");
      }).catch((err) => {
        console.log("Error updating cache: ", err);
      })
      
      console.log("Fetched: ", temp.length, "posts");
      setFeed([...feed, ...temp]);

      setRefreshing(false);
    })
  }

  return (
    <View style={styles.container}>
      {/* Header */}
      <View style={styles.header}>
        {/* <View style={styles.sherlock}> */}
          <View style={styles.titleDiv}>
            <Text style={styles.title}>Sherlock</Text>

            <View style={styles.sherlock}>
              <Badge
                size="sm"
                variant="solid"
                action="info"
                style={styles.beta}
              >
                <BadgeText ml={"auto"} mr={"auto"}>
                  Beta
                </BadgeText>
              </Badge>

              {/* Will later be dynamic for location */}
              <Text style={styles.location}>Knoxville</Text>
            </View>
          {/* </View> */}
        </View>

        <View
          style={{
            flexDirection: "row",
            justifyContent: "center",
            alignItems: "center",
            backgroundColor: Colors.dark.tabBg,
          }}
        >
          <View
            style={[
              styles.aliasHeader,
              {
                backgroundColor:
                  Colors.map[
                    ("class" + global.aliasClass) as keyof typeof Colors.map
                  ],
              },
            ]}
          >
            <Text adjustsFontSizeToFit numberOfLines={1} style={[styles.emoji]}>
              {" "}
              {aliasemoji}{" "}
            </Text>
          </View>

          {/* Notifications button */}
          {/* <TouchableOpacity
            onPress={() => {
              router.push("/(home)/notifications");
            }}
          >
            <MaterialCommunityIcons
              name="bell"
              size={30}
              color={Colors.dark.redPastel}
            />
          </TouchableOpacity> */}
        </View>
      </View>

      <View style={styles.separator} darkColor="rgba(255,255,255,0.1)" />

      {/* Main content view */}
      <FlatList
        key={"feed"}
        style={styles.list}
        data={feed}
        ref={feedRef}
        onEndReachedThreshold={0.1}
        contentContainerStyle={{ paddingBottom: 70 }}
        ListEmptyComponent={
          <Text
            style={{
              textAlign: "center",
              fontSize: 25,
              justifyContent: "center",
              marginTop: global.height * 0.3,
              color: Colors.dark.redBright,
            }}
          >
            This is awkward; No posts were found.{"\n\n"}
            <Text style={{ fontSize: 20, color: Colors.dark.text }}>
              Try refreshing or, if you're feeling fancy, add one!
            </Text>
            {"\n"}
            ๐Ÿ™‚โ€โ†•๏ธ
          </Text>
        }
        refreshControl={
          <RefreshControl
            refreshing={refreshing}
            colors={[Colors.dark.cyan, Colors.dark.redBright]}
            progressBackgroundColor={Colors.dark.backgroundProfile}
            onRefresh={() => {
              handleRefresh(true);
            }}
          />
        }
        onEndReached={() => {
          handleEndReached();
        }}
        renderItem={(post: ListRenderItemInfo<any>) => {
          return <PostListView post={post} />;
        }}
      />

      {/* Add post button */}
      <TouchableOpacity
        style={styles.newPost}
        onPress={() => {
          console.log("New Post");

          // Navigate to new post screen
          router.push("/(home)/postCreate");
        }}
      >
        <MaterialCommunityIcons
          name="text-box-plus"
          size={50}
          color={Colors.dark.redPastel}
        />
      </TouchableOpacity>

      {loading && (
        <View style={styles.loading}>
          <VStack style={styles.spinner}>
            <Spinner size="large" color={Colors.dark.redBright} />
            <Text style={{ textAlign: "center", fontSize: 20 }}>
              Loading all the posts...{"\n"} ๐Ÿ‘€
            </Text>
          </VStack>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  spinner: {
    top: global.height * 0.5,
    backgroundColor: Colors.dark.backgroundModal,
    borderRadius: 10,
    padding: 10,
    textAlign: "center",
    alignSelf: "center",
  },
  loading: {
    position: "absolute",
    backgroundColor: Colors.dark.transparentTint,
    height: global.height,
    width: global.width,
  },
  sherlock: {
    backgroundColor: Colors.dark.tabBg,
    flexDirection: "column",
    columnGap:0,
  },
  beta: {
    height: 20,
    backgroundColor:Colors.dark.transparentBlue,
    width:45,
    // flex:1,
    // flexShrink:1,
    borderRadius:50,
  },
  location: {
    color: Colors.dark.redBright,
    marginLeft: 0,
    fontWeight: "bold",
    textAlignVertical: 'center',
    fontSize: 13,
    // backgroundColor: Colors.dark.red,
    height: 30,
  },
  titleDiv: {
    flexDirection: "row",
    backgroundColor: Colors.dark.tabBg,
    columnGap: 0,
  },
  title: {
    fontSize: 25,
    fontWeight: "bold",
    color: Colors.dark.cyan,
    marginLeft: 0,
    marginTop: 10,
  },
  container: {
    flex: 1,
    backgroundColor: Colors.dark.tabBg,
  },
  content: {
    padding: 10,
    width: global.width,
  },
  header: {
    marginTop: constants.statusBarHeight,
    flexDirection: "row",
    justifyContent: "space-between",
    padding: 10,
    backgroundColor: Colors.dark.tabBg,
  },
  separator: {
    marginVertical: 30,
    height: 1,
    width: global.width * 0.95,
    alignSelf: "center",
    marginTop: 0,
    marginBottom: 5,
  },
  newPost: {
    position: "absolute",
    bottom: 80,
    right: 20,
    backgroundColor: Colors.dark.tabBg,
    borderRadius: 20,
    padding: 5,
  },
  aliasHeader: {
    justifyContent: "center",
    alignItems: "center",
    height: global.height * 0.07,
    width: global.height * 0.07,
    borderWidth: 5,
    borderColor: Colors.dark.backgroundProfile,
    borderRadius: 10,
    // marginRight: 15,
  },
  emoji: {
    fontSize: 20,
  },
  list: {
    width: global.width * 0.95,
    alignSelf: "center",
    marginBottom: 60,
    backgroundColor: Colors.dark.tabBg,
  },
});