Notifications + AsyncStorage
This commit is contained in:
@@ -1,12 +1,40 @@
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { Session } from "@supabase/supabase-js";
|
||||
import { Redirect, Tabs } from "expo-router";
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import { Redirect, router, Tabs } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function UseNotificationObserver() {
|
||||
useEffect(() => {
|
||||
function redirect(notification: Notifications.Notification) {
|
||||
const aId = notification.request.content.data?.aId;
|
||||
|
||||
if (typeof aId === 'string') {
|
||||
router.push({pathname: "/assignment/viewDetailsAssignment", params: { aId }});
|
||||
}
|
||||
}
|
||||
|
||||
const response = Notifications.getLastNotificationResponse();
|
||||
if (response?.notification) {
|
||||
redirect(response.notification);
|
||||
}
|
||||
|
||||
const subscription = Notifications.addNotificationResponseReceivedListener(response => {
|
||||
redirect(response.notification);
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
export default function TabLayout() {
|
||||
const [session, SetSession] = useState<Session | null>(null)
|
||||
const [loading, SetLoading] = useState(true);
|
||||
|
||||
UseNotificationObserver();
|
||||
|
||||
useEffect(() => {
|
||||
const loadSession = async () => {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
@@ -38,18 +66,6 @@ export default function TabLayout() {
|
||||
<Tabs.Screen name="assignments" options={{title: "Assignments"}} />
|
||||
<Tabs.Screen name="subjects" options={{title: "Subjects"}} />
|
||||
<Tabs.Screen name="timer" options={{title: "Timer"}} />
|
||||
|
||||
<Tabs.Screen name="subject/createSubject" options={{ href: null }} />
|
||||
<Tabs.Screen name="subject/editSubject" options={{ href: null }} />
|
||||
<Tabs.Screen name="subject/viewDetailsSubject" options={{ href: null }} />
|
||||
|
||||
<Tabs.Screen name="assignment/createAssignment" options={{ href: null }} />
|
||||
<Tabs.Screen name="assignment/editAssignment" options={{ href: null }} />
|
||||
<Tabs.Screen name="assignment/viewDetailsAssignment" options={{ href: null }} />
|
||||
|
||||
<Tabs.Screen name="task/createTask" options={{ href: null }} />
|
||||
<Tabs.Screen name="task/editTask" options={{ href: null }} />
|
||||
<Tabs.Screen name="task/viewDetailsTask" options={{ href: null }} />
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,34 @@
|
||||
import { defaultStyles } from "@/constants/defaultStyles";
|
||||
import { RegisterForLocalNotificationsAsync } from '@/lib/notifications';
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { Stack } from "expo-router";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Text, View } from "react-native";
|
||||
|
||||
export default function HomeScreen() {
|
||||
const [session, SetSession] = useState<Session | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
supabase.auth
|
||||
.getSession()
|
||||
.then(({ data }) => SetSession(data.session ?? null));
|
||||
|
||||
const { data: sub } = supabase.auth.onAuthStateChange(
|
||||
(_event, newSession) => {
|
||||
SetSession(newSession);
|
||||
}
|
||||
);
|
||||
|
||||
return () => sub.subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (session) {
|
||||
RegisterForLocalNotificationsAsync();
|
||||
}
|
||||
}, [session])
|
||||
|
||||
return (
|
||||
<View style={defaultStyles.container}>
|
||||
<Stack.Screen
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { Stack } from "expo-router";
|
||||
import '../global.css';
|
||||
import "../global.css";
|
||||
|
||||
export default function RootLayout() {
|
||||
return <Stack screenOptions={{ headerShown: false }} />;
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="subject" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="assignment" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="task" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="createUser" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="login" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
11
app/assignment/_layout.tsx
Normal file
11
app/assignment/_layout.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function AssignmentLayout() {
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Screen name="createAssignment" options={{ title: "Create Assignment" }} />
|
||||
<Stack.Screen name="editAssignment" options={{ title: "Edit Assignment" }} />
|
||||
<Stack.Screen name="viewDetailsAssignment" options={{ title: "Assignment Details" }} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { defaultStyles } from '@/constants/defaultStyles';
|
||||
import * as AsyncStorage from '@/lib/asyncStorage';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import { router, Stack, useLocalSearchParams } from 'expo-router';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
@@ -18,37 +20,62 @@ import {
|
||||
|
||||
export default function CreateAssignment() {
|
||||
const sId = (useLocalSearchParams().sId as string) ?? null;
|
||||
|
||||
const [title, SetTitle] = useState('');
|
||||
const [description, SetDescription] = useState('');
|
||||
const [deadline, SetDeadline] = useState('');
|
||||
const [isCompleted, SetIsCompleted] = useState(false);
|
||||
const [isSaving, SetIsSaving] = useState(false);
|
||||
|
||||
const ScheduleDeadlineReminder = async (aId: string, title: string, deadline: string) => {
|
||||
const dl = new Date(deadline);
|
||||
|
||||
if (isNaN(dl.getTime())) return null;
|
||||
|
||||
const deadlineReminder = new Date(dl.getTime() - 24 * 60 * 60 * 1000);
|
||||
|
||||
if (deadlineReminder <= new Date()) return null;
|
||||
|
||||
const nId = await Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: 'Assignment deadline coming up',
|
||||
body: `${title} is due in 24 hours.`,
|
||||
data: { aId },
|
||||
},
|
||||
trigger: {
|
||||
type: Notifications.SchedulableTriggerInputTypes.DATE,
|
||||
date: deadlineReminder,
|
||||
},
|
||||
});
|
||||
|
||||
return nId;
|
||||
}
|
||||
|
||||
const CreateAssignment = async () => {
|
||||
if (title.trim() === '') {
|
||||
Alert.alert('Title is required!');
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, error: userError } = await supabase.auth.getUser();
|
||||
const { data: userData, error: userError } = await supabase.auth.getUser();
|
||||
|
||||
if (userError || !data.user) {
|
||||
if (userError || !userData.user) {
|
||||
router.replace('../createUser');
|
||||
return;
|
||||
}
|
||||
|
||||
SetIsSaving(true);
|
||||
|
||||
const { error: dbError } = await supabase.from('assignments').insert({
|
||||
const { data: assignmentData, error: dbError } = await supabase.from('assignments').insert({
|
||||
title: title.trim(),
|
||||
description: description.trim(),
|
||||
deadline: deadline.trim(),
|
||||
isCompleted,
|
||||
lastChanged: new Date().toISOString(),
|
||||
uId: data.user.id,
|
||||
uId: userData.user.id,
|
||||
sId,
|
||||
});
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (dbError) {
|
||||
SetIsSaving(false);
|
||||
@@ -58,6 +85,14 @@ export default function CreateAssignment() {
|
||||
|
||||
Alert.alert('Assignment successfully created!');
|
||||
|
||||
if (!isCompleted && assignmentData) {
|
||||
const nId = await ScheduleDeadlineReminder(assignmentData.aId, assignmentData.title, assignmentData.deadline);
|
||||
|
||||
if (nId) {
|
||||
await AsyncStorage.SaveAssignmentNotificationId(assignmentData.aId, nId);
|
||||
}
|
||||
}
|
||||
|
||||
SetTitle('');
|
||||
SetDescription('');
|
||||
SetDeadline('');
|
||||
@@ -1,5 +1,7 @@
|
||||
import { defaultStyles } from '@/constants/defaultStyles';
|
||||
import { GetAssignmentNotificationId, RemoveAssignmentNotificationId, SaveAssignmentNotificationId } from '@/lib/asyncStorage';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
|
||||
@@ -20,6 +22,39 @@ export default function EditAssignment() {
|
||||
const [assignment, SetAssignment] = useState<Assignment | null>(null)
|
||||
const [isSaving, SetIsSaving] = useState(false);
|
||||
|
||||
const ScheduleDeadlineReminder = async (aId: string, title: string, deadline: string) => {
|
||||
const dl = new Date(deadline);
|
||||
|
||||
if (isNaN(dl.getTime())) return null;
|
||||
|
||||
const deadlineReminder = new Date(dl.getTime() - 24 * 60 * 60 * 1000);
|
||||
|
||||
if (deadlineReminder <= new Date()) return null;
|
||||
|
||||
const nId = await Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: 'Assignment deadline coming up',
|
||||
body: `${title} is due in 24 hours.`,
|
||||
data: { aId },
|
||||
},
|
||||
trigger: {
|
||||
type: Notifications.SchedulableTriggerInputTypes.DATE,
|
||||
date: deadlineReminder,
|
||||
},
|
||||
});
|
||||
|
||||
return nId;
|
||||
}
|
||||
|
||||
const CancelDeadlineReminder = async (aId: string) => {
|
||||
const nId = await GetAssignmentNotificationId(aId);
|
||||
|
||||
if (!nId) return;
|
||||
|
||||
await Notifications.cancelScheduledNotificationAsync(nId);
|
||||
await RemoveAssignmentNotificationId(aId);
|
||||
}
|
||||
|
||||
const GetAssignment = async (aId: string) => {
|
||||
const { data, error } = await supabase.from("assignments").select("*").eq("aId", aId).single();
|
||||
|
||||
@@ -47,24 +82,27 @@ export default function EditAssignment() {
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, error: userError } = await supabase.auth.getUser();
|
||||
const { data: userData, error: userError } = await supabase.auth.getUser();
|
||||
|
||||
if(userError || !data.user) {
|
||||
if(userError || !userData.user) {
|
||||
router.replace("../createUser");
|
||||
return;
|
||||
}
|
||||
|
||||
SetIsSaving(true);
|
||||
|
||||
const { error: dbError } = await supabase.from("assignments").update({
|
||||
const { data: assignmentData, error: dbError } = await supabase.from("assignments").update({
|
||||
title: assignment.title,
|
||||
description: assignment.description,
|
||||
deadline: assignment.deadline,
|
||||
isCompleted: assignment.isCompleted,
|
||||
lastChanged: new Date().toISOString(),
|
||||
uId: data.user.id,
|
||||
uId: userData.user.id,
|
||||
sId: assignment.sId,
|
||||
}).eq("aId", aId);
|
||||
})
|
||||
.eq("aId", aId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
SetIsSaving(false);
|
||||
|
||||
@@ -75,6 +113,18 @@ export default function EditAssignment() {
|
||||
|
||||
Alert.alert("Assignment successfully edited!");
|
||||
|
||||
if (assignmentData) {
|
||||
await CancelDeadlineReminder(assignmentData.aId);
|
||||
|
||||
if (!assignmentData.isCompleted) {
|
||||
const nId = await ScheduleDeadlineReminder(assignmentData.aId, assignmentData.title, assignmentData.deadline);
|
||||
|
||||
if (nId) {
|
||||
await SaveAssignmentNotificationId(assignmentData.aId, nId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
router.back();
|
||||
}
|
||||
|
||||
11
app/subject/_layout.tsx
Normal file
11
app/subject/_layout.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function SubjectLayout() {
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Screen name="createSubject" options={{ title: "Create Subject" }} />
|
||||
<Stack.Screen name="editSubject" options={{ title: "Edit Subject" }} />
|
||||
<Stack.Screen name="viewDetailsSubject" options={{ title: "Subject Details" }} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
11
app/task/_layout.tsx
Normal file
11
app/task/_layout.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function TaskLayout() {
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Screen name="createTask" options={{ title: "Create Task" }} />
|
||||
<Stack.Screen name="editTask" options={{ title: "Edit Task" }} />
|
||||
<Stack.Screen name="viewDetailsTask" options={{ title: "Task Details" }} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user