Improved homepage

This commit is contained in:
Keannu Bernasol 2023-08-10 17:23:12 +08:00
parent 33ffcde6be
commit 529a7a75fd
6 changed files with 246 additions and 82 deletions

View file

@ -6,7 +6,7 @@ import {
OnboardingParams, OnboardingParams,
PatchStudentData, PatchStudentData,
RegistrationParams, RegistrationParams,
StudentStatusParams, StudentStatus,
} from "../../interfaces/Interfaces"; } from "../../interfaces/Interfaces";
export let backendURL = "https://stude.keannu1.duckdns.org"; export let backendURL = "https://stude.keannu1.duckdns.org";
@ -261,20 +261,35 @@ export async function OnboardingUpdateStudentInfo(info: OnboardingParams) {
}); });
} }
export async function PostStudentStatus(info: StudentStatusParams) { export async function GetStudentStatus() {
const config = await GetConfig(); const config = await GetConfig();
console.log(info);
return instance return instance
.patch("/api/v1/student_status/self/", info, config) .get("/api/v1/student_status/self/", config)
.then((response) => { .then((response) => {
console.log("heh1");
return [true, response.data]; return [true, response.data];
}) })
.catch((error) => { .catch((error) => {
let error_message = ""; let error_message = "";
if (error.response) error_message = error.response.data; if (error.response) error_message = error.response.data;
else error_message = "Unable to reach servers"; else error_message = "Unable to reach servers";
console.log("heh2", error); console.log(error_message);
return [false, error_message];
});
}
export async function PatchStudentStatus(info: StudentStatus) {
const config = await GetConfig();
console.log(info);
return instance
.patch("/api/v1/student_status/self/", info, config)
.then((response) => {
return [true, response.data];
})
.catch((error) => {
let error_message = "";
if (error.response) error_message = error.response.data;
else error_message = "Unable to reach servers";
console.log(error_message);
return [false, error_message]; return [false, error_message];
}); });
} }

View file

@ -122,6 +122,23 @@ export interface PatchStudentData {
avatar?: string | null; avatar?: string | null;
} }
interface Location {
latitude: number;
longitude: number;
}
export interface StudentStatus {
user?: string;
subject?: string;
location?: Location;
landmark?: string | null;
active?: boolean;
}
export type StudentStatusParams = [boolean, StudentStatus];
export type LocationType = Location.LocationObject;
export interface StudentData { export interface StudentData {
first_name: string; first_name: string;
last_name: string; last_name: string;
@ -135,21 +152,8 @@ export interface StudentData {
course_shortname: string; course_shortname: string;
year_level: string; year_level: string;
yearlevel_shortname: string; yearlevel_shortname: string;
subjects: string[]; // To-do subjects: string[];
username: string; username: string;
} }
export type UserInfoParams = [boolean, StudentData]; export type UserInfoParams = [boolean, StudentData];
interface Location {
latitude: number;
longtitude: number;
}
export interface StudentStatusParams {
subject?: string;
location?: Location;
active?: boolean;
}
export type LocationType = Location.LocationObject;

View file

@ -1,23 +1,34 @@
import styles, { Viewport, colors } from "../../styles"; import styles, { Viewport, colors } from "../../styles";
import { View, Text } from "react-native"; import { View, Text, ToastAndroid } from "react-native";
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer"; import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import MapView, { Callout, Marker, UrlTile } from "react-native-maps"; import MapView, { Callout, Marker, UrlTile } from "react-native-maps";
import * as Location from "expo-location"; import * as Location from "expo-location";
import GetDistance from "../../components/GetDistance/GetDistance"; import GetDistance from "../../components/GetDistance/GetDistance";
import Button from "../../components/Button/Button"; import Button from "../../components/Button/Button";
import { RootDrawerParamList } from "../../interfaces/Interfaces"; import {
RootDrawerParamList,
StudentStatusParams,
} from "../../interfaces/Interfaces";
import { LocationType } from "../../interfaces/Interfaces"; import { LocationType } from "../../interfaces/Interfaces";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { urlProvider } from "../../components/Api/Api"; import {
GetStudentStatus,
PatchStudentStatus,
urlProvider,
} from "../../components/Api/Api";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
export default function Home() { export default function Home() {
// Switch this condition to see the main map when debugging
const map_debug = true;
const navigation = useNavigation<RootDrawerParamList>(); const navigation = useNavigation<RootDrawerParamList>();
const [location, setLocation] = useState<LocationType | null>(null); const [location, setLocation] = useState<LocationType | null>(null);
const [dist, setDist] = useState<number | null>(null); const [dist, setDist] = useState<number | null>(null);
const [feedback, setFeedback] = useState( const [feedback, setFeedback] = useState(
"To continue, please allow Stud-E permission to location services" "To continue, please allow Stud-E permission to location services"
); );
const queryClient = useQueryClient();
const ustpCoords = { const ustpCoords = {
latitude: 8.4857, latitude: 8.4857,
@ -28,8 +39,10 @@ export default function Home() {
async function requestLocation() { async function requestLocation() {
const { status } = await Location.requestForegroundPermissionsAsync(); const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") { if (status !== "granted") {
setFeedback( setFeedback("Allow location permissions to continue");
"Permission to access location was denied. Please allow permission" ToastAndroid.show(
"Location permission was denied. Please allow in order to use StudE",
ToastAndroid.SHORT
); );
return; return;
} }
@ -71,12 +84,86 @@ export default function Home() {
ustpCoords.longitude ustpCoords.longitude
); );
setDist(Math.round(dist)); setDist(Math.round(dist));
// Deactivate student status if too far away
if (dist >= 2 && !map_debug)
mutation.mutate({
active: false,
});
} }
// Student Status
const [studying, setStudying] = useState(false);
const [subject, setSubject] = useState("");
const [buttonLabel, setButtonLabel] = useState("Start studying");
const StudentStatus = useQuery({
queryKey: ["user_status"],
queryFn: GetStudentStatus,
onSuccess: (data: StudentStatusParams) => {
if (data[1].active !== undefined) {
setStudying(data[1].active);
}
if (data[1].subject !== undefined) {
setSubject(data[1].subject);
}
if (data[1].active == true) {
setButtonLabel("Stop Studying");
} else if (data[1].active == false) {
setButtonLabel("Start Studying");
}
console.log(data[1]);
},
onError: () => {
setFeedback("Unable to query available subjects");
},
});
const mutation = useMutation({
mutationFn: PatchStudentStatus,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user"] });
queryClient.invalidateQueries({ queryKey: ["user_status"] });
ToastAndroid.show(
"You are no longer studying " + subject,
ToastAndroid.SHORT
);
},
onError: () => {
ToastAndroid.show(
"Server error. Unable to update student status",
ToastAndroid.SHORT
);
},
});
function CustomCallout() {
if (location && location.coords) {
if (studying) {
return (
<Callout>
<Text style={styles.text_black_medium}>
You are here {"\n"}
X: {Math.round(location.coords.longitude) + "\n"}
Z: {Math.round(location.coords.latitude) + "\n"}
Studying: {subject}
</Text>
</Callout>
);
} else {
return (
<Callout>
<Text style={styles.text_black_medium}>
You are here {"\n"}
X: {Math.round(location.coords.longitude) + "\n"}
Z: {Math.round(location.coords.latitude)}
</Text>
</Callout>
);
}
}
return <></>;
}
function CustomMap() { function CustomMap() {
if (dist && location) { if (dist && location) {
if (dist >= 2) { if (dist <= 2 || map_debug) {
// Just switch this condition for map debugging
return ( return (
<View> <View>
<MapView <MapView
@ -149,21 +236,21 @@ export default function Home() {
}} }}
pinColor={colors.primary_1} pinColor={colors.primary_1}
> >
<Callout> <CustomCallout />
<Text style={styles.text_black_medium}>
You are here {"\n"}
X: {Math.round(location.coords.longitude) + "\n"}
Z: {Math.round(location.coords.latitude)}
</Text>
</Callout>
</Marker> </Marker>
</MapView> </MapView>
<Button <Button
onPress={async () => { onPress={async () => {
navigation.navigate("Start Studying", { location: location }); if (!studying) {
navigation.navigate("Start Studying", { location: location });
} else {
mutation.mutate({
active: false,
});
}
}} }}
> >
<Text style={styles.text_white_medium}>Start Studying</Text> <Text style={styles.text_white_medium}>{buttonLabel}</Text>
</Button> </Button>
</View> </View>
); );

View file

@ -1,21 +1,26 @@
import * as React from "react"; import * as React from "react";
import styles, { Viewport } from "../../styles"; import styles, { Viewport } from "../../styles";
import { View, Text } from "react-native"; import { View, Text, ToastAndroid } from "react-native";
import { useState } from "react"; import { useState } from "react";
import { UserInfoParams, OptionType } from "../../interfaces/Interfaces"; import {
UserInfoParams,
OptionType,
RootDrawerParamList,
} from "../../interfaces/Interfaces";
import Button from "../../components/Button/Button"; import Button from "../../components/Button/Button";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { UserInfo } from "../../components/Api/Api"; import { PatchStudentStatus, UserInfo } from "../../components/Api/Api";
import { colors } from "../../styles"; import { colors } from "../../styles";
import DropDownPicker from "react-native-dropdown-picker"; import DropDownPicker from "react-native-dropdown-picker";
import AnimatedContainerNoScroll from "../../components/AnimatedContainer/AnimatedContainerNoScroll"; import AnimatedContainerNoScroll from "../../components/AnimatedContainer/AnimatedContainerNoScroll";
import { urlProvider } from "../../components/Api/Api"; import { urlProvider } from "../../components/Api/Api";
import MapView, { UrlTile, Marker } from "react-native-maps"; import MapView, { UrlTile, Marker } from "react-native-maps";
import { useNavigation } from "@react-navigation/native";
export default function StartStudying({ route }: any) { export default function StartStudying({ route }: any) {
const { location } = route.params; const { location } = route.params;
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [feedback, setFeedback] = useState(""); const navigation = useNavigation<RootDrawerParamList>();
// Subject choices // Subject choices
const [selected_subject, setSelectedSubject] = useState(""); const [selected_subject, setSelectedSubject] = useState("");
@ -32,9 +37,32 @@ export default function StartStudying({ route }: any) {
setSubjects(subjects); setSubjects(subjects);
}, },
onError: () => { onError: () => {
setFeedback("Unable to query available subjects"); ToastAndroid.show(
"Server error: Unable to query available subjects",
ToastAndroid.SHORT
);
}, },
}); });
const mutation = useMutation({
mutationFn: PatchStudentStatus,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user"] });
queryClient.invalidateQueries({ queryKey: ["user_status"] });
ToastAndroid.show(
"You are now studying " + selected_subject,
ToastAndroid.SHORT
);
navigation.navigate("Home");
},
onError: () => {
ToastAndroid.show(
"A server error has occured. Please try again",
ToastAndroid.SHORT
);
},
});
if (location && location.coords) { if (location && location.coords) {
return ( return (
<View style={styles.background}> <View style={styles.background}>
@ -87,50 +115,60 @@ export default function StartStudying({ route }: any) {
/> />
</MapView> </MapView>
<View style={styles.padding} /> <View style={styles.padding} />
<Text style={styles.text_white_small}>{feedback}</Text>
</View>
<View style={styles.flex_row}>
<View style={{ flex: 1 }}>
<Text style={styles.text_white_small_bold}>Start Studying</Text>
</View>
<View style={{ flex: 3 }}>
<DropDownPicker
zIndex={1000}
max={16}
open={subjectsOpen}
value={selected_subject}
items={subjects}
setOpen={(open) => {
setSubjectsOpen(open);
}}
setValue={setSelectedSubject}
placeholderStyle={{
...styles.text_white_tiny_bold,
...{ textAlign: "left" },
}}
placeholder="Select subject"
multipleText="Select subject"
style={styles.input}
textStyle={{
...styles.text_white_tiny_bold,
...{ textAlign: "left" },
}}
modalContentContainerStyle={{
backgroundColor: colors.primary_2,
borderWidth: 0,
zIndex: 1000,
}}
autoScroll
dropDownDirection="BOTTOM"
listMode="MODAL"
/>
</View>
</View> </View>
<DropDownPicker
zIndex={1000}
max={16}
open={subjectsOpen}
value={selected_subject}
items={subjects}
setOpen={(open) => {
setSubjectsOpen(open);
}}
setValue={setSelectedSubject}
placeholderStyle={{
...styles.text_white_tiny_bold,
...{ textAlign: "left" },
}}
placeholder="Select subject"
multipleText="Select subject"
style={styles.input}
textStyle={{
...styles.text_white_tiny_bold,
...{ textAlign: "left" },
}}
modalContentContainerStyle={{
backgroundColor: colors.primary_2,
borderWidth: 0,
zIndex: 1000,
}}
autoScroll
dropDownDirection="BOTTOM"
listMode="MODAL"
/>
<View style={styles.padding} /> <View style={styles.padding} />
<Button onPress={() => {}}> <Button
onPress={() => {
console.log({
subject: selected_subject,
location: {
latitude: location.coords.latitude,
longitude: location.coords.longitude,
},
});
mutation.mutate({
active: true,
subject: selected_subject,
location: {
latitude: location.coords.latitude,
longitude: location.coords.longitude,
},
});
}}
>
<Text style={styles.text_white_small}>Start Studying</Text> <Text style={styles.text_white_small}>Start Studying</Text>
</Button> </Button>
<View style={styles.padding} />
</AnimatedContainerNoScroll> </AnimatedContainerNoScroll>
</View> </View>
); );

View file

@ -52,7 +52,7 @@ export default function SubjectsPage() {
course_shortname: "", course_shortname: "",
avatar: "", avatar: "",
student_id_number: "", student_id_number: "",
subjects: [] as Subjects, subjects: [] as string[],
}); });
const StudentInfo = useQuery({ const StudentInfo = useQuery({
queryKey: ["user"], queryKey: ["user"],

View file

@ -28,6 +28,7 @@ import {
GetSemesters, GetSemesters,
GetSubjects, GetSubjects,
GetYearLevels, GetYearLevels,
PatchStudentStatus,
PatchUserInfo, PatchUserInfo,
UserInfo, UserInfo,
} from "../../components/Api/Api"; } from "../../components/Api/Api";
@ -47,6 +48,20 @@ export default function UserInfoPage() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [feedback, setFeedback] = useState(""); const [feedback, setFeedback] = useState("");
// Student Status
const studentstatus_mutation = useMutation({
mutationFn: PatchStudentStatus,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user"] });
queryClient.invalidateQueries({ queryKey: ["user_status"] });
setFeedback("Changes applied successfully");
},
onError: () => {
setFeedback("An error has occured\nChanges have not been saved");
},
});
// User Info // User Info
const [user, setUser] = useState({ const [user, setUser] = useState({
first_name: "", first_name: "",
@ -86,11 +101,16 @@ export default function UserInfoPage() {
setFeedback("Unable to query user info"); setFeedback("Unable to query user info");
}, },
}); });
const mutation = useMutation({ const mutation = useMutation({
mutationFn: PatchUserInfo, mutationFn: PatchUserInfo,
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user"] }); queryClient.invalidateQueries({ queryKey: ["user"] });
queryClient.invalidateQueries({ queryKey: ["subjects"] }); queryClient.invalidateQueries({ queryKey: ["subjects"] });
// Reset student status when changing user info to prevent bugs
studentstatus_mutation.mutate({
active: false,
});
setFeedback("Changes applied successfully"); setFeedback("Changes applied successfully");
dispatch(setUserinState(user)); dispatch(setUserinState(user));
}, },