mirror of
https://github.com/lemeow125/StudE-Frontend.git
synced 2024-11-16 22:09:26 +08:00
Improved functionlity of messages page
This commit is contained in:
parent
63f863fa1e
commit
2cd770e5e1
7 changed files with 414 additions and 96 deletions
9
package-lock.json
generated
9
package-lock.json
generated
|
@ -22,6 +22,7 @@
|
|||
"expo-linking": "~4.0.1",
|
||||
"expo-location": "~15.1.1",
|
||||
"expo-status-bar": "~1.4.4",
|
||||
"moment": "^2.29.4",
|
||||
"moti": "^0.25.3",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.71.8",
|
||||
|
@ -11123,6 +11124,14 @@
|
|||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/moti": {
|
||||
"version": "0.25.3",
|
||||
"resolved": "https://registry.npmjs.org/moti/-/moti-0.25.3.tgz",
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"expo-linking": "~4.0.1",
|
||||
"expo-location": "~15.1.1",
|
||||
"expo-status-bar": "~1.4.4",
|
||||
"moment": "^2.29.4",
|
||||
"moti": "^0.25.3",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.71.8",
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
ActivationType,
|
||||
LocationType,
|
||||
LoginType,
|
||||
MessagePostType,
|
||||
OnboardingType,
|
||||
PatchUserInfoType,
|
||||
RegistrationType,
|
||||
|
@ -311,7 +312,6 @@ export async function GetStudyGroupListFiltered() {
|
|||
return instance
|
||||
.get("/api/v1/study_groups/near/", config)
|
||||
.then((response) => {
|
||||
console.log("DEBUGGG", response.data);
|
||||
return [true, response.data];
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -346,3 +346,56 @@ export async function CreateStudyGroup(info: StudyGroupCreateType) {
|
|||
return [false, error_message];
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetStudyGroup(name: string) {
|
||||
const config = await GetConfig();
|
||||
return instance
|
||||
.get(`/api/v1/study_groups/${name}`, config)
|
||||
.then((response) => {
|
||||
return [true, response.data];
|
||||
})
|
||||
.catch((error) => {
|
||||
let error_message = ParseError(error);
|
||||
return [false, error_message];
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetStudyGroupMessages() {
|
||||
const config = await GetConfig();
|
||||
return instance
|
||||
.get(`/api/v1/messages/`, config)
|
||||
.then((response) => {
|
||||
return [true, response.data];
|
||||
})
|
||||
.catch((error) => {
|
||||
let error_message = ParseError(error);
|
||||
return [false, error_message];
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetStudyGroupMemberAvatars() {
|
||||
const config = await GetConfig();
|
||||
return instance
|
||||
.get(`/api/v1/study_groups/member_avatars`, config)
|
||||
.then((response) => {
|
||||
return [true, response.data];
|
||||
})
|
||||
.catch((error) => {
|
||||
let error_message = ParseError(error);
|
||||
return [false, error_message];
|
||||
});
|
||||
}
|
||||
|
||||
export async function PostMessage(info: MessagePostType) {
|
||||
const config = await GetConfig();
|
||||
return instance
|
||||
.post(`/api/v1/messages/`, info, config)
|
||||
.then((response) => {
|
||||
return [true, response.data];
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error:", error.response.data);
|
||||
let error_message = ParseError(error);
|
||||
return [false, error_message];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -185,13 +185,31 @@ export interface StudyGroupCreateType {
|
|||
subject: string;
|
||||
}
|
||||
|
||||
export interface MessageType {
|
||||
id: number;
|
||||
user: string;
|
||||
study_group: string;
|
||||
message_content: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface MessagePostType {
|
||||
message_content: string;
|
||||
}
|
||||
|
||||
export interface GroupMessageAvatarType {
|
||||
username: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
export type GroupMessageAvatarListType = GroupMessageAvatarType[];
|
||||
export type GroupMessageAvatarReturnType = [boolean, GroupMessageAvatarType[]];
|
||||
export type MessageReturnType = [boolean, MessageType[]];
|
||||
export type StudyGroupDetailReturnType = [boolean, StudyGroupType];
|
||||
export type StudyGroupReturnType = [boolean, StudyGroupType[]];
|
||||
|
||||
export type StudentStatusReturnType = [boolean, StudentStatusType];
|
||||
|
||||
export type StudentStatusListType = Array<StudentStatusFilterType>;
|
||||
export type StudentStatusListReturnType = [boolean, StudentStatusListType];
|
||||
|
||||
export type RawLocationType = Location.LocationObject;
|
||||
|
||||
export interface UserInfoType {
|
||||
|
|
|
@ -1,10 +1,38 @@
|
|||
import * as React from "react";
|
||||
import { ActivityIndicator, Image } from "react-native";
|
||||
import styles from "../../styles";
|
||||
import { View, Text, TextInput, ScrollView, StyleSheet } from "react-native";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TextInput,
|
||||
ScrollView,
|
||||
NativeSyntheticEvent,
|
||||
TextInputChangeEventData,
|
||||
} from "react-native";
|
||||
import { colors } from "../../styles";
|
||||
import { useState } from "react";
|
||||
|
||||
const convStyles = StyleSheet.create({});
|
||||
import { useRef, useState } from "react";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
GetStudentStatus,
|
||||
GetStudyGroup,
|
||||
GetStudyGroupMemberAvatars,
|
||||
GetStudyGroupMessages,
|
||||
PostMessage,
|
||||
} from "../../components/Api/Api";
|
||||
import {
|
||||
StudentStatusType,
|
||||
StudentStatusReturnType,
|
||||
StudyGroupType,
|
||||
StudyGroupDetailReturnType,
|
||||
MessageType,
|
||||
MessageReturnType,
|
||||
MessagePostType,
|
||||
GroupMessageAvatarType,
|
||||
GroupMessageAvatarReturnType,
|
||||
} from "../../interfaces/Interfaces";
|
||||
import { useToast } from "react-native-toast-notifications";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
|
||||
|
||||
type ConversationType = {
|
||||
id: number;
|
||||
|
@ -15,91 +43,293 @@ type ConversationType = {
|
|||
};
|
||||
|
||||
export default function ConversationPage() {
|
||||
const [conversation, setConversation] = useState<ConversationType[]>([
|
||||
{
|
||||
user: "You",
|
||||
message_content: "Hello World naa ko diri canteen gutom sh*t.",
|
||||
id: Math.floor(Math.random() * 1000),
|
||||
color: Math.floor(Math.random() * 16777215).toString(16),
|
||||
study_group: "Heh group",
|
||||
const toast = useToast();
|
||||
// Student Status
|
||||
const [student_status, setStudentStatus] = useState<StudentStatusType>();
|
||||
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;
|
||||
},
|
||||
{
|
||||
user: "User 2",
|
||||
message_content: "Hahahah shor oy.",
|
||||
id: Math.floor(Math.random() * 1000),
|
||||
color: Math.floor(Math.random() * 16777215).toString(16),
|
||||
study_group: "Heh group",
|
||||
onSuccess: (data: StudentStatusReturnType) => {
|
||||
setStudentStatus(data[1]);
|
||||
},
|
||||
]);
|
||||
onError: (error: Error) => {
|
||||
toast.show(String(error), {
|
||||
type: "warning",
|
||||
placement: "top",
|
||||
duration: 2000,
|
||||
animationType: "slide-in",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.messageScrollViewContainer}>
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
backgroundColor: colors.secondary_2,
|
||||
borderRadius: 20,
|
||||
}}
|
||||
>
|
||||
<View style={{ padding: 15 }}>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text style={{ ...styles.text_white_medium }}>Group#57605</Text>
|
||||
</View>
|
||||
<Text>
|
||||
3 students
|
||||
<View style={{ ...styles.badge, backgroundColor: "blue" }}></View>
|
||||
<View style={{ ...styles.badge, backgroundColor: `green` }}></View>
|
||||
<View style={{ ...styles.badge, backgroundColor: `red` }}></View>
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
{conversation.map((item: ConversationType, index: number) => {
|
||||
const color = `rgba(${Math.floor(
|
||||
Math.random() * 256
|
||||
)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(
|
||||
Math.random() * 256
|
||||
)}, 0.7)`;
|
||||
return (
|
||||
<View
|
||||
key={item.id}
|
||||
// Study Group Detail
|
||||
const [studygroup, setStudyGroup] = useState<StudyGroupType>();
|
||||
const StudyGroupQuery = useQuery({
|
||||
enabled:
|
||||
student_status?.study_group != "" && student_status?.study_group != null,
|
||||
queryKey: ["study_group"],
|
||||
queryFn: async () => {
|
||||
const data = await GetStudyGroup(student_status?.study_group || "");
|
||||
if (data[0] == false) {
|
||||
return Promise.reject(new Error(JSON.stringify(data[1])));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data: StudyGroupDetailReturnType) => {
|
||||
if (data[1]) {
|
||||
setStudyGroup(data[1]);
|
||||
}
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.show(String(error), {
|
||||
type: "warning",
|
||||
placement: "top",
|
||||
duration: 2000,
|
||||
animationType: "slide-in",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Study Group Messages
|
||||
const [messages, setMessages] = useState<MessageType[]>([]);
|
||||
const MessageQuery = useQuery({
|
||||
refetchInterval: 3000,
|
||||
enabled:
|
||||
!StudentStatusQuery.isLoading &&
|
||||
(student_status?.study_group != null ||
|
||||
student_status?.study_group != ""),
|
||||
queryKey: ["study_group_messages"],
|
||||
queryFn: async () => {
|
||||
const data = await GetStudyGroupMessages();
|
||||
if (data[0] == false) {
|
||||
return Promise.reject(new Error(JSON.stringify(data[1])));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data: MessageReturnType) => {
|
||||
if (data[1]) {
|
||||
setMessages(data[1]);
|
||||
}
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.show(String(error), {
|
||||
type: "warning",
|
||||
placement: "top",
|
||||
duration: 2000,
|
||||
animationType: "slide-in",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Avatar List
|
||||
const [users, setUsers] = useState<GroupMessageAvatarType[]>([]);
|
||||
const AvatarsQuery = useQuery({
|
||||
refetchInterval: 3000,
|
||||
enabled:
|
||||
student_status?.study_group != null ||
|
||||
(student_status?.study_group != "" &&
|
||||
studygroup != null &&
|
||||
studygroup.students != null),
|
||||
queryKey: ["study_group_avatars"],
|
||||
queryFn: async () => {
|
||||
const data = await GetStudyGroupMemberAvatars();
|
||||
if (data[0] == false) {
|
||||
return Promise.reject(new Error(JSON.stringify(data[1])));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data: GroupMessageAvatarReturnType) => {
|
||||
if (data[1]) {
|
||||
setUsers(data[1]);
|
||||
}
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.show(String(error), {
|
||||
type: "warning",
|
||||
placement: "top",
|
||||
duration: 2000,
|
||||
animationType: "slide-in",
|
||||
});
|
||||
},
|
||||
});
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
const queryClient = useQueryClient();
|
||||
const [message, setMessage] = useState("");
|
||||
const send_message = useMutation({
|
||||
mutationFn: async (info: MessagePostType) => {
|
||||
const data = await PostMessage(info);
|
||||
if (data[0] != true) {
|
||||
return Promise.reject(new Error());
|
||||
}
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["study_group_messages"] });
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.show(String(error), {
|
||||
type: "warning",
|
||||
placement: "top",
|
||||
duration: 2000,
|
||||
animationType: "slide-in",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!StudyGroupQuery.isLoading &&
|
||||
!AvatarsQuery.isLoading &&
|
||||
!MessageQuery.isLoading &&
|
||||
student_status &&
|
||||
studygroup &&
|
||||
studygroup.students
|
||||
) {
|
||||
return (
|
||||
<View style={styles.background}>
|
||||
<AnimatedContainer>
|
||||
<View
|
||||
style={{
|
||||
padding: 15,
|
||||
alignSelf: "flex-start",
|
||||
}}
|
||||
>
|
||||
<View style={styles.flex_row}>
|
||||
<Text style={{ ...styles.text_white_medium }}>
|
||||
{`Group: ${studygroup?.name ? studygroup.name : ""}`}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ ...styles.flex_row }}>
|
||||
<Text
|
||||
style={{
|
||||
...styles.message_contentContainer,
|
||||
alignItems: index % 2 == 0 ? "flex-end" : "flex-start",
|
||||
...styles.text_white_small,
|
||||
textAlign: "left",
|
||||
paddingRight: 4,
|
||||
}}
|
||||
>
|
||||
<View style={styles.flex_row}>
|
||||
{index % 2 == 0 ? (
|
||||
<View
|
||||
style={{
|
||||
...styles.badge,
|
||||
...{ paddingRight: 2, backgroundColor: color },
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
...styles.badge,
|
||||
...{ paddingLeft: 2, backgroundColor: color },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Text style={styles.text_white_small}>{item.user}</Text>
|
||||
</View>
|
||||
{studygroup.students.length} studying
|
||||
</Text>
|
||||
{users.map((user: GroupMessageAvatarType, index: number) => {
|
||||
if (index > 6) {
|
||||
return <React.Fragment key={index} />;
|
||||
}
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{user.avatar != null && user.avatar != "" ? (
|
||||
<Image
|
||||
source={{ uri: user.avatar }}
|
||||
style={styles.profile_mini}
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
source={require("../../img/user_profile_placeholder.png")}
|
||||
style={styles.profile_mini}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView
|
||||
style={{ width: 320 }}
|
||||
onContentSizeChange={() => scrollViewRef.current?.scrollToEnd()}
|
||||
ref={scrollViewRef}
|
||||
>
|
||||
{messages.length > 0 ? (
|
||||
messages.map((message: MessageType, index: number) => {
|
||||
let avatar = "";
|
||||
users.filter((user: GroupMessageAvatarType) => {
|
||||
if (user.username == message.user) {
|
||||
avatar = user.avatar;
|
||||
}
|
||||
});
|
||||
return (
|
||||
<View
|
||||
key={message.id}
|
||||
style={{
|
||||
...styles.message_contentContainer,
|
||||
alignItems: index % 2 == 0 ? "flex-end" : "flex-start",
|
||||
}}
|
||||
>
|
||||
<View style={styles.flex_row}>
|
||||
{avatar != null && avatar != "" ? (
|
||||
<Image
|
||||
source={{ uri: avatar }}
|
||||
style={styles.profile_mini}
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
source={require("../../img/user_profile_placeholder.png")}
|
||||
style={styles.profile_mini}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Text style={styles.text_white_small}>
|
||||
{item.message_content}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<Text style={styles.text_white_small}>
|
||||
{message.user}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
...styles.text_white_tiny,
|
||||
...{ marginLeft: 4, alignContent: "center" },
|
||||
}}
|
||||
>
|
||||
{message.timestamp}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.text_white_small}>
|
||||
{message.message_content}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Text style={styles.text_white_small}>There are no messages</Text>
|
||||
)}
|
||||
</ScrollView>
|
||||
<TextInput
|
||||
style={styles.chatbox}
|
||||
placeholder="Send a message..."
|
||||
placeholderTextColor="white"
|
||||
value={message}
|
||||
onChange={(
|
||||
e: NativeSyntheticEvent<TextInputChangeEventData>
|
||||
): void => {
|
||||
setMessage(e.nativeEvent.text);
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
send_message.mutate({
|
||||
message_content: message,
|
||||
});
|
||||
setMessage("");
|
||||
}}
|
||||
/>
|
||||
</AnimatedContainer>
|
||||
</View>
|
||||
<TextInput
|
||||
style={styles.chatbox}
|
||||
placeholder="type here...."
|
||||
placeholderTextColor="white"
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
} else if (!student_status?.study_group) {
|
||||
return (
|
||||
<View style={styles.background}>
|
||||
<AnimatedContainer>
|
||||
<Text style={styles.text_white_medium}>
|
||||
You are not in a study group. Join one to start a conversation!
|
||||
</Text>
|
||||
</AnimatedContainer>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={styles.background}>
|
||||
<AnimatedContainer>
|
||||
<ActivityIndicator size={96} color={colors.secondary_1} />
|
||||
<Text style={styles.text_white_medium}>Loading...</Text>
|
||||
</AnimatedContainer>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -82,18 +82,14 @@ export default function Home() {
|
|||
}
|
||||
}
|
||||
|
||||
// Refresh every 15 seconds
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
requestLocation();
|
||||
}, 15000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
|
||||
// Refresh when screen loads
|
||||
useEffect(() => {
|
||||
// Refresh every 15 seconds
|
||||
const interval = setInterval(async () => {
|
||||
await requestLocation();
|
||||
}, 15000);
|
||||
requestLocation();
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
async function DistanceHandler(location: RawLocationType) {
|
||||
|
@ -829,7 +825,7 @@ export default function Home() {
|
|||
return (
|
||||
<>
|
||||
<Text style={styles.text_white_medium}>{feedback}</Text>
|
||||
<Button onPress={() => requestLocation()}>
|
||||
<Button onPress={async () => await requestLocation()}>
|
||||
<Text style={styles.text_white_medium}>Allow Access</Text>
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
@ -186,6 +186,16 @@ const styles = StyleSheet.create({
|
|||
borderColor: colors.primary_2,
|
||||
borderWidth: 3,
|
||||
},
|
||||
profile_mini: {
|
||||
height: 32,
|
||||
width: 32,
|
||||
alignSelf: "center",
|
||||
borderRadius: 150 / 2,
|
||||
overflow: "hidden",
|
||||
padding: 0,
|
||||
borderColor: colors.primary_2,
|
||||
borderWidth: 3,
|
||||
},
|
||||
input: {
|
||||
paddingHorizontal: 8,
|
||||
marginVertical: 2,
|
||||
|
@ -212,6 +222,7 @@ const styles = StyleSheet.create({
|
|||
backgroundColor: colors.primary_2,
|
||||
borderRadius: 20,
|
||||
borderColor: colors.primary_3,
|
||||
width: 256,
|
||||
},
|
||||
messageScrollViewContainer: {
|
||||
backgroundColor: colors.secondary_1,
|
||||
|
|
Loading…
Reference in a new issue