Skip to content

Commit 4b7cb03

Browse files
committed
Made notification categories and real-time updates
1 parent bd55288 commit 4b7cb03

13 files changed

Lines changed: 209 additions & 60 deletions

File tree

app/(tabs)/home/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default function HomeScreen() {
1717
<HomeHeader tY={0} h={50 + insets.top} pT={insets.top} />
1818
<PostList
1919
Header={
20-
<View style={{ height: 50 + insets.top }}></View>
20+
<View style={{ height: 20 + insets.top }}></View>
2121
}
2222
/>
2323
</KeyboardAvoidingView>

app/(tabs)/home/notifications.tsx

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,77 @@ import CustomText from "@components/display/customText";
44
import NothingHere from "@components/display/nothing";
55
import NotificationBox from "@components/display/notification";
66
import OnlyIconButton from "@components/inputs/onlyIconButton";
7+
import RadioBtn from "@components/inputs/radioBtn";
78
import { FlashList, ListRenderItemInfo } from "@shopify/flash-list";
9+
import { notification } from "@utils/types";
810
import { useRouter } from "expo-router";
9-
import { collection, getDocs, Timestamp } from "firebase/firestore";
10-
import { useEffect, useState } from "react";
11+
import { collection, onSnapshot } from "firebase/firestore";
12+
import React, { useEffect, useState } from "react";
1113
import { StyleSheet, View } from "react-native";
1214
import { useSafeAreaInsets } from "react-native-safe-area-context";
1315

14-
type notification = {
15-
id: string;
16-
message: string;
17-
title: string;
18-
data: { url: string };
19-
createdAt: Timestamp;
20-
}
2116

2217
export default function NotificationScreen() {
2318
const insets = useSafeAreaInsets();
2419
const router = useRouter();
25-
const [notif, setNotif] = useState<notification[]>([]);
20+
const [readNotif, setReadNotif] = useState<notification[]>([]);
21+
const [unreadNotif, setUnreadNotif] = useState<notification[]>([]);
22+
const [currTab, setCurrTab] = useState<string>("Unread");
2623
const currUser = auth.currentUser;
2724

28-
async function loadNotif() {
29-
const snap = await getDocs(collection(db, "notifications", currUser ? currUser.uid : "", "unread"));
3025

31-
const data = snap.docs.map(doc => ({
32-
id: doc.id,
33-
...(doc.data()) as Omit<notification, "id">
34-
}));
26+
useEffect(
27+
() => {
28+
const readNotifSub = onSnapshot(collection(db, "notifications", currUser ? currUser.uid : "", "read"), (snap) => {
29+
const data: notification[] = snap.docs.map(doc => (
30+
{
31+
id: doc.id,
32+
...(doc.data() as Omit<notification, 'id'>)
33+
}
34+
));
35+
console.log(data);
36+
setReadNotif(data);
37+
});
38+
39+
40+
const unreadNotifSub = onSnapshot(collection(db, "notifications", currUser ? currUser.uid : "", "unread"), (snap) => {
41+
const data: notification[] = snap.docs.map(doc => (
42+
{
43+
id: doc.id,
44+
...(doc.data() as Omit<notification, 'id'>)
45+
}
46+
));
47+
48+
setUnreadNotif(data);
49+
50+
});
51+
}, []
52+
)
3553

36-
setNotif(data);
37-
// console.log(data);
38-
}
3954

40-
useEffect(() => {
41-
loadNotif();
42-
}, [])
4355
return (
4456
<View style={{ backgroundColor: "#17171d", flex: 1, paddingTop: insets.top, paddingBottom: 100, alignItems: "center" }}>
45-
<View style={{ flexDirection: "row", alignItems: "center", width: "100%", marginBottom: 20 }}>
57+
<View style={{ flexDirection: "row", alignItems: "center", width: "100%", marginBottom: 10 }}>
4658
<OnlyIconButton icon="arrow-left" func={() => { router.back() }} style={{ top: 0, left: 20, zIndex: 5 }} />
4759
<CustomText style={{ color: "white", left: 50, fontSize: 18, top: 0, fontWeight: 700 }}>Notifications</CustomText>
4860
</View>
61+
<RadioBtn
62+
options={["Unread", "Read"]}
63+
iconList={["email-mark-as-unread", "sticker-check"]}
64+
selected={currTab}
65+
setSelected={setCurrTab}
66+
style={{ width: "100%", alignItems: "center", justifyContent: "center" }}
67+
/>
4968
<View style={styles.listContainer}>
69+
5070
<FlashList
51-
data={notif}
71+
data={(currTab == "Unread") ? unreadNotif : readNotif}
5272
keyExtractor={item => item.id}
5373
ListEmptyComponent={<NothingHere />}
5474
renderItem={({ item }: ListRenderItemInfo<notification>) => (
5575
<NotificationBox
76+
status={currTab == "Unread" ? "unread" : "read"}
77+
id={item.id}
5678
title={item.title}
5779
message={item.message}
5880
data={item.data}
@@ -71,6 +93,9 @@ const styles = StyleSheet.create({
7193
listContainer: {
7294
flex: 1,
7395
width: "95%",
96+
borderTopWidth: 1,
97+
borderTopColor: "#5f6977ff",
98+
paddingTop: 10
7499
}
75100

76101
});

app/(tabs)/profile/[user_id].tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function ProfileScreen() {
3131
const [uid, setUid] = useState(currentUser ? (currentUser.uid) : "");
3232

3333

34-
async function postWrapper() {
34+
async function updateUserData() {
3535
if (!user_id) {
3636

3737
getDoc(doc(db, "users", uid)).then(
@@ -64,13 +64,14 @@ export default function ProfileScreen() {
6464
setUid(user_id);
6565
}
6666
}
67-
postWrapper();
67+
updateUserData();
6868
}, [])
6969

7070

7171

7272
return (
7373
<PostList
74+
onReload={updateUserData}
7475
uidFilter={user_id ? user_id : currentUser ? currentUser.uid : ""}
7576
Header={
7677
<View style={{ backgroundColor: "#17171d", flex: 1 }}>

app/comments/[post_id].tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
doc,
1212
getDoc,
1313
getDocs,
14+
onSnapshot,
1415
orderBy,
1516
query,
1617
where
@@ -52,6 +53,17 @@ export default function CommentsScreen() {
5253

5354
const [refreshing, setRefreshing] = React.useState(false);
5455

56+
const commentSub = onSnapshot(collection(db, "notifications", user ? user.uid : "", "read"), (snap) => {
57+
const data: comment[] = snap.docs.map(doc => (
58+
{
59+
id: doc.id,
60+
...(doc.data() as Omit<comment, 'id'>)
61+
}
62+
));
63+
setCommentData(data);
64+
return commentSub;
65+
});
66+
5567
const onRefresh = React.useCallback(() => {
5668
setRefreshing(true);
5769
loadComments();
@@ -87,6 +99,7 @@ export default function CommentsScreen() {
8799
}
88100

89101

102+
90103
async function loadComments() {
91104
const c = collection(db, "posts", post_id, "comments");
92105
const q = query(c, orderBy("timestamp", "desc"));

src/components/containers/HomeHeader.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import CustomText from "../display/customText";
77
import { useUserData } from "../../contexts/userContext";
88

99
//navigation
10+
import { auth, db } from "@auth/firebase";
1011
import { useRouter } from "expo-router";
12+
import { doc, onSnapshot } from "firebase/firestore";
13+
import { useEffect, useState } from "react";
1114

1215
//typecasting
1316

@@ -20,8 +23,16 @@ interface Props {
2023

2124
export default function HomeHeader({ tY, h, pT }: Props) {
2225
const router = useRouter();
23-
2426
const { userData } = useUserData();
27+
const user = auth.currentUser;
28+
const [unreadCount, setUnreadCount] = useState(0);
29+
useEffect(() => {
30+
const unsub = onSnapshot(doc(db, "notifications", user ? user.uid : ""), (doc) => {
31+
const data = doc.data()?.unread_count
32+
setUnreadCount(data);
33+
});
34+
}, [])
35+
2536

2637
return (
2738
<Animated.View style={[styles.header, { height: h, paddingTop: pT, transform: [{ translateY: tY }] }]}>
@@ -30,8 +41,14 @@ export default function HomeHeader({ tY, h, pT }: Props) {
3041
</Pressable>
3142

3243
<CustomText style={{ fontSize: 18, color: "white", marginRight: "auto" }}> Ahoy, Hacker!</CustomText>
33-
<Pressable style={styles.button} onPress={() => { router.push('/(tabs)/home/notifications') }}>
34-
<MaterialDesignIcons name="bell" color="white" size={25} />
44+
<Pressable style={[{ position: "relative" }, styles.button]} onPress={() => { router.push('/(tabs)/home/notifications') }}>
45+
<MaterialDesignIcons name={"bell"} color="white" size={25} />
46+
{
47+
(unreadCount > 0) ?
48+
<MaterialDesignIcons style={{ position: "absolute", top: 4, right: 2, zIndex: 2 }} name={"circle"} color="#ec3750" size={10} />
49+
:
50+
null
51+
}
3552
</Pressable>
3653
<Pressable style={styles.button} onPress={() => { router.navigate('/(tabs)/home/settings') }}>
3754
<MaterialDesignIcons name="cog-outline" color="white" size={25} />

src/components/containers/postDots.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default function PostThreeDots({ postId, OpId, currentUserId }: Props) {
1919
}
2020
data={[
2121
(OpId == currentUserId) ?
22-
{ text: "Delete Post", func: () => { deletePost(postId); closeSheet(); ToastAndroid.show("Successfully deleted post, reload to see changes", 3) }, icon: "delete" } :
22+
{ text: "Delete Post", func: () => { deletePost(postId); closeSheet(); ToastAndroid.show("Successfully deleted post, reopen the app to see changes", 3) }, icon: "delete" } :
2323
{ text: "Like Post", func: () => { likePost(postId, currentUserId) }, icon: "heart" }
2424
,
2525
{ text: "Share Post", func: () => { sharePost(postId) }, icon: "share-variant" },

src/components/display/notification.tsx

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,46 @@
1+
import { auth } from '@auth/firebase';
2+
import { deleteNotifObject, markAsReadUnread } from '@utils/notificationUtils';
3+
import { extractTime } from '@utils/stringTimeUtils';
14
import * as Linking from 'expo-linking';
25
import { Timestamp } from "firebase/firestore";
36
import { Pressable, StyleSheet, View } from "react-native";
47
import CustomText from "./customText";
58
import ThreeDots from './threeDots';
69

710
interface Props {
11+
id: string,
812
message: string,
913
title: string,
1014
data: { url: string },
11-
createdAt: Timestamp
15+
createdAt: Timestamp,
16+
status: "read" | "unread"
1217
}
1318

14-
export default function NotificationBox({ message, title, data, createdAt }: Props) {
15-
19+
export default function NotificationBox({ id, message, title, data, createdAt, status }: Props) {
20+
const user = auth.currentUser
1621
function redirectUser() {
1722
Linking.openURL(data.url);
1823
}
1924
return (
20-
<Pressable style={styles.container} onPress={() => { redirectUser() }}>
25+
<View style={styles.container} >
2126
<View style={{ position: "absolute", top: 10, right: 10, zIndex: 6, padding: 5 }}>
2227
<ThreeDots
2328
data={[
24-
{ text: 'Mark as Read', icon: "sticker-check-outline", func: () => { } }
29+
{ text: status == "read" ? 'Mark as Unread' : "Mark as Read", icon: "sticker-check-outline", func: () => { markAsReadUnread(user ? user.uid : "", id, status); } },
30+
{ text: 'Delete Message', icon: "delete", func: () => { deleteNotifObject(user ? user.uid : "", id, status); } }
2531
]}
2632
/>
2733

2834
</View>
29-
<View style={{ flexDirection: "row", justifyContent: "center", alignItems: "center", width: "100%", paddingHorizontal: 5 }}>
35+
36+
<CustomText style={styles.time}>{extractTime(createdAt)}</CustomText>
37+
<Pressable onPress={() => { status == "unread" ? markAsReadUnread(user ? user.uid : "", id, status) : null; redirectUser() }} style={{ flexDirection: "row", justifyContent: "center", alignItems: "center", width: "100%", paddingHorizontal: 5 }}>
3038
<View style={styles.detailsContainer}>
3139
<CustomText style={styles.title}>{title}</CustomText>
3240
<CustomText style={styles.message}>{message}</CustomText>
3341
</View>
34-
</View>
35-
</Pressable>
42+
</Pressable>
43+
</View>
3644
);
3745
}
3846

@@ -43,7 +51,8 @@ const styles = StyleSheet.create({
4351
borderWidth: StyleSheet.hairlineWidth,
4452
borderColor: "#444456ff",
4553
borderRadius: 12,
46-
position: "relative"
54+
position: "relative",
55+
marginVertical: 4
4756
},
4857
title: {
4958
fontSize: 18,
@@ -59,11 +68,19 @@ const styles = StyleSheet.create({
5968
width: "100%",
6069
margin: 10
6170
},
71+
time: {
72+
fontSize: 13,
73+
color: "#8492a6",
74+
textAlign: "left",
75+
width: "100%",
76+
marginHorizontal: 20,
77+
marginTop: 10
78+
},
6279
detailsContainer: {
6380
padding: 15,
6481
display: "flex",
6582
width: "100%",
6683
justifyContent: "center",
67-
alignItems: "flex-start"
84+
alignItems: "center"
6885
}
6986
});

src/components/display/postList.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ const postLimit = 10;
2121

2222
interface Props {
2323
uidFilter?: string,
24-
Header?: React.ReactNode
24+
Header?: React.ReactNode,
25+
onReload?: () => {}
2526
}
2627

27-
export default function PostList({ uidFilter, Header }: Props) {
28+
export default function PostList({ onReload, uidFilter, Header }: Props) {
2829
const insets = useSafeAreaInsets();
2930
const user = auth.currentUser;
3031

@@ -38,6 +39,9 @@ export default function PostList({ uidFilter, Header }: Props) {
3839
setRefreshing(true);
3940
setLastDoc(null);
4041
loadPosts();
42+
if (onReload) {
43+
onReload();
44+
}
4145
setTimeout(() => {
4246
setRefreshing(false);
4347
}, 500);
@@ -47,7 +51,8 @@ export default function PostList({ uidFilter, Header }: Props) {
4751

4852
const renderPost: ListRenderItem<post> = useCallback(({ item }: ListRenderItemInfo<post>) =>
4953
(
50-
<Post comment_count={item.num_comments}
54+
<Post
55+
comment_count={item.num_comments}
5156
user_uid={user ? user.uid : ""}
5257
id={item.id}
5358
uid={item.uid}

src/components/display/threeDots.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ interface Props {
1313
export default function ThreeDots({ data, sheetHeader, color }: Props) {
1414
const { closedState, setSheetData, closeSheet, expandSheet, setHeader } = useBottomSheetContext();
1515

16-
1716
function toggleSheet() {
1817
setSheetData(data);
1918
setHeader(sheetHeader);
20-
console.log(closedState);
2119
if (closedState) {
2220
expandSheet();
2321
} else {

src/components/inputs/radioBtn.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { Pressable, StyleProp, StyleSheet, View, ViewStyle } from "react-native"
44

55
interface Props {
66
options: string[],
7-
iconList: ("earth" | "account-group" | "account" | "post" | "message" | "heart")[],
7+
iconList: ("earth" | "account-group" | "account" | "post" | "message" | "heart" | "email-mark-as-unread" | "sticker-check"
8+
)[],
89
selected: string,
910
setSelected: (a: string) => void,
1011
vertical?: boolean,

0 commit comments

Comments
 (0)