mirror of
https://github.com/urosran/cally.git
synced 2025-07-15 09:45:20 +00:00
Notification batch updates and notifications page update
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import {FlatList, StyleSheet} from "react-native";
|
||||
import React, {useCallback} from "react";
|
||||
import {ActivityIndicator, Animated, FlatList, StyleSheet} from "react-native";
|
||||
import React, {useCallback, useState} from "react";
|
||||
import {Card, Text, View} from "react-native-ui-lib";
|
||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||
import {Notification, useGetNotifications} from "@/hooks/firebase/useGetNotifications";
|
||||
@ -7,12 +7,88 @@ import {formatDistanceToNow} from "date-fns";
|
||||
import {useRouter} from "expo-router";
|
||||
import {useSetAtom} from "jotai";
|
||||
import {modeAtom, selectedDateAtom} from "@/components/pages/calendar/atoms";
|
||||
import {Swipeable} from 'react-native-gesture-handler';
|
||||
import {useDeleteNotification} from "@/hooks/firebase/useDeleteNotification";
|
||||
|
||||
const NotificationsPage = () => {
|
||||
interface NotificationItemProps {
|
||||
item: Notification;
|
||||
onDelete: (id: string) => void;
|
||||
onPress: () => void;
|
||||
isDeleting: boolean;
|
||||
}
|
||||
|
||||
const NotificationItem: React.FC<NotificationItemProps> = React.memo(({
|
||||
item,
|
||||
onDelete,
|
||||
onPress,
|
||||
isDeleting
|
||||
}) => {
|
||||
const renderRightActions = useCallback((
|
||||
progress: Animated.AnimatedInterpolation<number>,
|
||||
dragX: Animated.AnimatedInterpolation<number>
|
||||
) => {
|
||||
const trans = dragX.interpolate({
|
||||
inputRange: [-100, 0],
|
||||
outputRange: [0, 100],
|
||||
extrapolate: 'clamp'
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.deleteAction,
|
||||
{
|
||||
transform: [{translateX: trans}],
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={styles.deleteActionText}>Delete</Text>
|
||||
</Animated.View>
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Swipeable
|
||||
renderRightActions={renderRightActions}
|
||||
onSwipeableRightOpen={() => onDelete(item.id)}
|
||||
overshootRight={false}
|
||||
enabled={!isDeleting}
|
||||
>
|
||||
<Card
|
||||
padding-20
|
||||
marginB-10
|
||||
onPress={onPress}
|
||||
activeOpacity={0.6}
|
||||
enableShadow={false}
|
||||
style={styles.card}
|
||||
>
|
||||
{isDeleting && (
|
||||
<View style={styles.loadingOverlay}>
|
||||
<ActivityIndicator color="#000" size="large"/>
|
||||
</View>
|
||||
)}
|
||||
<Text text70>{item.content}</Text>
|
||||
<View row spread marginT-10>
|
||||
<Text text90>
|
||||
{formatDistanceToNow(new Date(item.timestamp), {addSuffix: true})}
|
||||
</Text>
|
||||
<Text text90>
|
||||
{item.timestamp.toLocaleDateString()}
|
||||
</Text>
|
||||
</View>
|
||||
</Card>
|
||||
</Swipeable>
|
||||
);
|
||||
});
|
||||
|
||||
const NotificationsPage: React.FC = () => {
|
||||
const setSelectedDate = useSetAtom(selectedDateAtom);
|
||||
const setMode = useSetAtom(modeAtom);
|
||||
const {data: notifications} = useGetNotifications();
|
||||
const deleteNotification = useDeleteNotification();
|
||||
const {push} = useRouter();
|
||||
const [deletingIds, setDeletingIds] = useState<Set<string>>(new Set());
|
||||
|
||||
const goToEventDay = useCallback((notification: Notification) => () => {
|
||||
if (notification?.date) {
|
||||
setSelectedDate(notification.date);
|
||||
@ -21,6 +97,27 @@ const NotificationsPage = () => {
|
||||
push({pathname: "/calendar"});
|
||||
}, [push, setSelectedDate]);
|
||||
|
||||
const handleDelete = useCallback((notificationId: string) => {
|
||||
setDeletingIds(prev => new Set(prev).add(notificationId));
|
||||
deleteNotification.mutate(notificationId, {
|
||||
onSettled: () => {
|
||||
setDeletingIds(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(notificationId);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [deleteNotification]);
|
||||
|
||||
const renderNotificationItem = useCallback(({item}: { item: Notification }) => (
|
||||
<NotificationItem
|
||||
item={item}
|
||||
onDelete={handleDelete}
|
||||
onPress={goToEventDay(item)}
|
||||
isDeleting={deletingIds.has(item.id)}
|
||||
/>
|
||||
), [handleDelete, goToEventDay, deletingIds]);
|
||||
|
||||
return (
|
||||
<View flexG height={"100%"}>
|
||||
@ -39,27 +136,8 @@ const NotificationsPage = () => {
|
||||
<FlatList
|
||||
contentContainerStyle={styles.listContainer}
|
||||
data={notifications ?? []}
|
||||
renderItem={({item}) => (
|
||||
<Card
|
||||
padding-20
|
||||
marginB-10
|
||||
key={item.content}
|
||||
onPress={goToEventDay(item)}
|
||||
activeOpacity={0.6}
|
||||
enableShadow={false}
|
||||
style={styles.card}
|
||||
>
|
||||
<Text text70>{item.content}</Text>
|
||||
<View row spread marginT-10>
|
||||
<Text text90>
|
||||
{formatDistanceToNow(new Date(item.timestamp), {addSuffix: true})}
|
||||
</Text>
|
||||
<Text text90>
|
||||
{item.timestamp.toLocaleDateString()}
|
||||
</Text>
|
||||
</View>
|
||||
</Card>
|
||||
)}
|
||||
renderItem={renderNotificationItem}
|
||||
keyExtractor={(item) => item.id}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
@ -79,14 +157,26 @@ const styles = StyleSheet.create({
|
||||
fontFamily: "Manrope_400Regular",
|
||||
fontSize: 14,
|
||||
},
|
||||
searchField: {
|
||||
borderWidth: 0.7,
|
||||
borderColor: "#9b9b9b",
|
||||
borderRadius: 15,
|
||||
height: 42,
|
||||
paddingLeft: 10,
|
||||
marginVertical: 20,
|
||||
deleteAction: {
|
||||
backgroundColor: '#FF3B30',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end',
|
||||
paddingRight: 30,
|
||||
marginBottom: 10,
|
||||
width: 100,
|
||||
borderRadius: 10,
|
||||
},
|
||||
deleteActionText: {
|
||||
color: 'white',
|
||||
fontWeight: '600',
|
||||
},
|
||||
loadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default NotificationsPage;
|
||||
export default NotificationsPage
|
||||
|
@ -164,19 +164,15 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
|
||||
}
|
||||
}, [user, ready, redirectOverride]);
|
||||
|
||||
// useEffect(() => {
|
||||
// const handleNotification = async (notification: Notifications.Notification) => {
|
||||
// const eventId = notification?.request?.content?.data?.eventId;
|
||||
//
|
||||
// // if (eventId) {
|
||||
// queryClient.invalidateQueries(['events']);
|
||||
// // }
|
||||
// };
|
||||
//
|
||||
// const sub = Notifications.addNotificationReceivedListener(handleNotification);
|
||||
//
|
||||
// return () => sub.remove();
|
||||
// }, []);
|
||||
useEffect(() => {
|
||||
const handleNotification = async (notification: Notifications.Notification) => {
|
||||
queryClient.invalidateQueries(["notifications"]);
|
||||
};
|
||||
|
||||
const sub = Notifications.addNotificationReceivedListener(handleNotification);
|
||||
|
||||
return () => sub.remove();
|
||||
}, []);
|
||||
|
||||
if (!ready) {
|
||||
return null;
|
||||
|
File diff suppressed because it is too large
Load Diff
37
hooks/firebase/useDeleteNotification.ts
Normal file
37
hooks/firebase/useDeleteNotification.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import {useMutation, useQueryClient} from "react-query";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
import {Notification} from "@/hooks/firebase/useGetNotifications";
|
||||
|
||||
export const useDeleteNotification = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {user} = useAuthContext();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
await firestore()
|
||||
.collection("Notifications")
|
||||
.doc(id)
|
||||
.delete();
|
||||
},
|
||||
onMutate: async (deletedId) => {
|
||||
await queryClient.cancelQueries(["notifications", user?.uid]);
|
||||
|
||||
const previousNotifications = queryClient.getQueryData<Notification[]>(["notifications", user?.uid]);
|
||||
|
||||
queryClient.setQueryData<Notification[]>(["notifications", user?.uid], (old) =>
|
||||
old?.filter((notification) => notification?.id! !== deletedId) ?? []
|
||||
);
|
||||
|
||||
return {previousNotifications};
|
||||
},
|
||||
onError: (_err, _deletedId, context) => {
|
||||
if (context?.previousNotifications) {
|
||||
queryClient.setQueryData(["notifications", user?.uid], context.previousNotifications);
|
||||
}
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries(["notifications", user?.uid]);
|
||||
},
|
||||
});
|
||||
};
|
@ -17,6 +17,7 @@ interface NotificationFirestore {
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
id: string;
|
||||
creatorId: string;
|
||||
familyId: string;
|
||||
content: string;
|
||||
@ -40,11 +41,14 @@ export const useGetNotifications = () => {
|
||||
const data = doc.data() as NotificationFirestore;
|
||||
|
||||
return {
|
||||
id: doc.id,
|
||||
...data,
|
||||
timestamp: new Date(data.timestamp.seconds * 1000 + data.timestamp.nanoseconds / 1e6),
|
||||
date: data.date ? new Date(data.date.seconds * 1000 + data.date.nanoseconds / 1e6) : undefined
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
refetchOnWindowFocus: true,
|
||||
staleTime: 60000,
|
||||
});
|
||||
};
|
Reference in New Issue
Block a user