import styles, { colors } from "../../styles"; import { View, Text, Pressable, ScrollView, Switch, ActivityIndicator, } from "react-native"; import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer"; import { useState, useEffect } from "react"; import MapView, { Circle, Marker, UrlTile } from "react-native-maps"; import * as Location from "expo-location"; import GetDistance from "../../components/GetDistance/GetDistance"; import Button from "../../components/Button/Button"; import { RootDrawerParamList, StudentStatusReturnType, RawLocationType, StudentStatusType, StudentStatusListReturnType, StudentStatusListType, StudentStatusPatchType, StudyGroupType, StudyGroupReturnType, StudentStatusFilterType, } from "../../interfaces/Interfaces"; import { useNavigation } from "@react-navigation/native"; import { GetStudentStatus, GetStudentStatusList, GetStudentStatusListNear, GetStudyGroupList, GetStudyGroupListFiltered, PatchStudentStatus, urlProvider, } from "../../components/Api/Api"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useToast } from "react-native-toast-notifications"; import React from "react"; import MapRendererFar from "../../components/MapRenderer/MapRendererFar"; import GetDistanceFromUSTP from "../../components/GetDistance/GetDistanceFromUSTP"; import Modal from "react-native-modal"; import DropdownIcon from "../../icons/CaretDownIcon/CaretDownIcon"; import CaretUpIcon from "../../icons/CaretUpIcon/CaretUpIcon"; import RefreshIcon from "../../icons/RefreshIcon/RefreshIcon"; export default function Home() { // Switch this condition to see the main map when debugging const map_distance_override = false; const navigation = useNavigation(); const [location, setLocation] = useState(null); const [locationFetched, setLocationFetched] = useState(false); const [locationPermitted, setLocationPermitted] = useState(false); const [dist, setDist] = useState(null); const [feedback, setFeedback] = useState( "To continue, please allow Stud-E permission to location services" ); const queryClient = useQueryClient(); const toast = useToast(); const [modalOpen, setModalOpen] = useState(false); const [modalByGroup, setModalByGroup] = useState(false); async function requestLocation() { const { status } = await Location.requestForegroundPermissionsAsync(); if (status !== "granted") { setFeedback("Allow location permissions to continue"); toast.show( "Location permission was denied. Please allow in order to use StudE", { type: "warning", placement: "top", duration: 2000, animationType: "slide-in", } ); return; } if (status == "granted") { if (locationPermitted === false) { setLocationPermitted(true); } let newLocation = await Location.getCurrentPositionAsync(); if (newLocation) { // Only update location state if user's location has changed if ( !location || newLocation.coords.latitude !== location.coords.latitude || newLocation.coords.longitude !== location.coords.longitude ) { setLocation(newLocation); DistanceHandler(newLocation); } } } } // Refresh every 10 seconds useEffect(() => { const interval = setInterval(() => { requestLocation(); }, 10000); return () => clearInterval(interval); }); // Refresh when screen loads requestLocation(); useEffect(() => { requestLocation(); }, []); async function DistanceHandler(location: RawLocationType) { let dist = GetDistanceFromUSTP(location.coords); setDist(dist); // Deactivate student status if too far away and still studying if (dist >= 2 && !map_distance_override && studying) stop_studying.mutate({ active: false, }); setLocationFetched(true); } // Student Status const [studying, setStudying] = useState(false); const [subject, setSubject] = useState(""); const [buttonLabel, setButtonLabel] = useState("Start studying"); const [student_status, setStudentStatus] = useState(); const StudentStatusQuery = useQuery({ queryKey: ["user_status"], queryFn: async () => { const data = await GetStudentStatus(); if (data[0] == false) { return Promise.reject(new Error(JSON.stringify(data[1]))); } return data; }, onSuccess: (data: StudentStatusReturnType) => { if (data[1].active == true) { setButtonLabel("Stop Studying"); } else if (data[1].active == false) { setButtonLabel("Start Studying"); } setSubject(data[1].subject); setStudying(data[1].active); setStudentStatus(data[1]); }, onError: (error: Error) => { toast.show(String(error), { type: "warning", placement: "top", duration: 2000, animationType: "slide-in", }); }, }); const stop_studying = useMutation({ mutationFn: async (info: StudentStatusPatchType) => { const data = await PatchStudentStatus(info); if (data[0] != true) { return Promise.reject(new Error()); } return data; }, onSuccess: () => { if (student_status?.study_group) { // Display separate toast if you stop studying while in a study group toast.show("You left study group \n" + student_status?.study_group, { type: "success", placement: "top", duration: 2000, animationType: "slide-in", }); } toast.show("You are no longer studying \n" + subject, { type: "success", placement: "top", duration: 2000, animationType: "slide-in", }); queryClient.invalidateQueries({ queryKey: ["user_status"] }); // Delay refetching for study groups since backend still needs to delete groups without students after leaving a study group setTimeout(() => { queryClient.invalidateQueries({ queryKey: ["study_group_list"] }); queryClient.invalidateQueries({ queryKey: ["study_group_list_global"], }); }, 500); setStudyGroups([]); setStudying(false); }, onError: (error: Error) => { toast.show(String(error), { type: "warning", placement: "top", duration: 2000, animationType: "slide-in", }); }, }); const change_study_group = useMutation({ mutationFn: async (info: StudentStatusPatchType) => { const data = await PatchStudentStatus(info); if (data[0] != true) { return Promise.reject(new Error()); } return data; }, onSuccess: () => { if (student_status?.study_group) { // Display separate toast if you stop studying while in a study group toast.show("You left study group \n" + student_status?.study_group, { type: "success", placement: "top", duration: 2000, animationType: "slide-in", }); } queryClient.invalidateQueries({ queryKey: ["user_status"] }); // Delay refetching for study groups since backend still needs to delete groups without students after leaving a study group setTimeout(() => { queryClient.invalidateQueries({ queryKey: ["study_group_list"] }); queryClient.invalidateQueries({ queryKey: ["study_group_list_global"], }); }, 500); setStudyGroups([]); }, onError: (error: Error) => { toast.show(String(error), { type: "warning", placement: "top", duration: 2000, animationType: "slide-in", }); }, }); const [student_statuses, setStudentStatuses] = useState([]); // Student Status List const StudentStatusListQuery = useQuery({ enabled: studying && !StudentStatusQuery.isLoading, queryKey: ["user_status_list"], queryFn: async () => { const data = await GetStudentStatusListNear(); if (data[0] == false) { return Promise.reject(new Error(JSON.stringify(data[1]))); } return data; }, onSuccess: (data: StudentStatusListReturnType) => { if (data[1] && location) { // Filter to only include students studying solo let data_filtered = data[1].filter( (item: StudentStatusFilterType) => item.study_group == null ); setStudentStatuses(data_filtered); } }, onError: (error: Error) => { toast.show(String(error), { type: "warning", placement: "top", duration: 2000, animationType: "slide-in", }); }, }); const [student_statuses_global, setStudentStatusesGlobal] = useState([]); // Student Status List Global const StudentStatusListGlobalQuery = useQuery({ enabled: !studying && !StudentStatusQuery.isLoading, queryKey: ["user_status_list_global"], queryFn: async () => { const data = await GetStudentStatusList(); if (data[0] == false) { return Promise.reject(new Error(JSON.stringify(data[1]))); } return data; }, onSuccess: (data: StudentStatusListReturnType) => { if (data[1] && location) { // Filter to only include students studying solo let data_filtered = data[1].filter( (item: StudentStatusFilterType) => item.study_group == null ); setStudentStatusesGlobal(data_filtered); } }, onError: (error: Error) => { toast.show(String(error), { type: "warning", placement: "top", duration: 2000, animationType: "slide-in", }); }, }); const [study_groups, setStudyGroups] = useState([]); // Study Group List const StudyGroupQuery = useQuery({ enabled: studying && !StudentStatusQuery.isLoading, queryKey: ["study_group_list"], queryFn: async () => { const data = await GetStudyGroupListFiltered(); if (data[0] == false) { return Promise.reject(new Error(JSON.stringify(data[1]))); } return data; }, onSuccess: (data: StudyGroupReturnType) => { if (data[1] && location) { setStudyGroups(data[1]); } }, onError: (error: Error) => { toast.show(String(error), { type: "warning", placement: "top", duration: 2000, animationType: "slide-in", }); }, }); const [study_groups_global, setStudyGroupsGlobal] = useState< StudyGroupType[] >([]); // Study Group Global List const StudyGroupGlobalQuery = useQuery({ enabled: !studying && !StudentStatusQuery.isLoading, queryKey: ["study_group_list_global"], queryFn: async () => { const data = await GetStudyGroupList(); if (data[0] == false) { return Promise.reject(new Error(JSON.stringify(data[1]))); } return data; }, onSuccess: (data: StudyGroupReturnType) => { if (data[1] && location) { setStudyGroupsGlobal(data[1]); } }, onError: (error: Error) => { toast.show(String(error), { type: "warning", placement: "top", duration: 2000, animationType: "slide-in", }); }, }); function CustomMap() { if ( (StudentStatusQuery.isFetching && studying) || StudentStatusListQuery.isFetching || StudyGroupQuery.isFetching || (StudentStatusQuery.isFetching && !studying) || StudentStatusListGlobalQuery.isFetching || StudyGroupGlobalQuery.isFetching ) { return ( <> Loading... ); } else if (!locationPermitted) { console.log(locationPermitted); return ( <> {feedback} ); } else if (dist && location && locationFetched) { if (dist <= 1 || map_distance_override) { return ( <> { queryClient.invalidateQueries({ queryKey: ["user"] }); queryClient.invalidateQueries({ queryKey: ["user_status"] }); queryClient.invalidateQueries({ queryKey: ["user_status_list"], }); queryClient.invalidateQueries({ queryKey: ["study_group_list"], }); }} > {!studying ? ( student_statuses_global.map( (student_status: StudentStatusFilterType, index: number) => { const randomColorWithOpacity = `rgba(${Math.floor( Math.random() * 256 )}, ${Math.floor(Math.random() * 256)}, ${Math.floor( Math.random() * 256 )}, 0.7)`; return ( { toast.hideAll(); toast.show( Student: {student_status.user} {`Studying ${student_status.subject}`} , { type: "normal", placement: "top", duration: 2000, animationType: "slide-in", style: { backgroundColor: colors.secondary_2, borderWidth: 1, borderColor: colors.primary_1, }, } ); }} /> ); } ) ) : ( <> )} {studying ? ( student_statuses.map( (student_status: StudentStatusFilterType, index: number) => { const randomColorWithOpacity = `rgba(${Math.floor( Math.random() * 256 )}, ${Math.floor(Math.random() * 256)}, ${Math.floor( Math.random() * 256 )}, 0.7)`; return ( { toast.hideAll(); toast.show( Student: {student_status.user} {`Studying ${student_status.subject}`} {`${Math.round( student_status.distance * 1000 )}m away`} , { type: "normal", placement: "top", duration: 2000, animationType: "slide-in", style: { backgroundColor: colors.secondary_2, borderWidth: 1, borderColor: colors.primary_1, }, } ); }} /> ); } ) ) : ( <> )} {studying ? ( study_groups.map( (studygroup: StudyGroupType, index: number) => { const randomColorWithOpacity = `rgba(${Math.floor( Math.random() * 256 )}, ${Math.floor(Math.random() * 256)}, ${Math.floor( Math.random() * 256 )}, 0.7)`; return ( { toast.hideAll(); toast.show( Study Group: {studygroup.name} {`Studying ${studygroup.subject}`} {`${studygroup.students.length} ${ studygroup.students.length > 1 ? "students" : "student" } studying`} {`${Math.round( studygroup.distance * 1000 )}m away`} {student_status?.study_group != studygroup.name ? ( ) : ( <> )} {student_status?.study_group == studygroup.name ? ( ) : ( <> )} , { type: "normal", placement: "top", duration: 2000, animationType: "slide-in", style: { backgroundColor: colors.secondary_2, borderWidth: 1, borderColor: colors.primary_1, }, } ); }} /> ); } ) ) : ( <> )} {!studying ? ( study_groups_global.map( (studygroup: StudyGroupType, index: number) => { const randomColorWithOpacity = `rgba(${Math.floor( Math.random() * 256 )}, ${Math.floor(Math.random() * 256)}, ${Math.floor( Math.random() * 256 )}, 0.7)`; return ( { toast.hideAll(); toast.show( Study Group: {studygroup.name} {`Studying ${studygroup.subject}`} {`${studygroup.students.length} ${ studygroup.students.length > 1 ? "students" : "student" } studying`} {student_status?.study_group != studygroup.name ? ( Study nearby to join ) : ( <> )} , { type: "normal", placement: "top", duration: 2000, animationType: "slide-in", style: { backgroundColor: colors.secondary_2, borderWidth: 1, borderColor: colors.primary_1, }, } ); }} /> ); } ) ) : ( <> )} {!studying || !student_status?.study_group ? ( { const newLocation = e.nativeEvent.coordinate; const distance = GetDistance( newLocation.latitude, newLocation.longitude, location.coords.latitude, location.coords.longitude ); if (distance <= 0.1) { // If the new location is within 100 meters of the actual location, update the location state setLocation({ ...location, coords: { ...location.coords, latitude: newLocation.latitude, longitude: newLocation.longitude, }, }); } else { // If the new location is more than 100 meters away from the actual location, reset the marker to the actual location setLocation({ ...location, }); } }} pinColor={colors.primary_1} onPress={() => { toast.hideAll(); toast.show( You are here {student_status?.active && !student_status?.study_group ? ( <> {student_status?.active ? "Studying " + student_status?.subject : ""} ) : ( <> )} {student_status?.study_group ? ( <> {`Studying: ${student_status?.subject}`} {`In group: ${student_status?.study_group}`} ) : ( <> )} , { type: "normal", placement: "top", duration: 2000, animationType: "slide-in", style: { backgroundColor: colors.secondary_2, borderWidth: 1, borderColor: colors.primary_1, }, } ); }} > ) : ( <> )} { setModalOpen(true); }} > {studying ? : <>} ); } else { return ; } } else { return ( <> Loading... ); } } return ( setModalOpen(false)} > { setModalByGroup(!modalByGroup); }} /> List View {!modalByGroup ? ( student_statuses.map( (student_status: StudentStatusFilterType, index: number) => { return ( Student: {student_status.user} {`Studying ${student_status.subject}`} {`${Math.round(student_status.distance * 1000)}m away`} ); } ) ) : ( <> )} {modalByGroup ? ( study_groups.map((studygroup: StudyGroupType, index: number) => { return ( Group Name: {studygroup.name} {`Studying ${studygroup.subject}`} Students Studying: {studygroup.students.length} {student_status?.study_group != studygroup.name ? ( {`${Math.round(studygroup.distance * 1000)}m away`} ) : ( <> )} {student_status?.study_group != studygroup.name ? ( { change_study_group.mutate({ study_group: studygroup.name, subject: studygroup.subject, }); setModalOpen(!modalOpen); }} > Join Group ) : ( <> )} {student_status?.study_group == studygroup.name ? ( { change_study_group.mutate({ study_group: "", }); setModalOpen(!modalOpen); }} > Leave Group ) : ( <> )} ); }) ) : ( <> )} ); }