diff --git a/app/(tabs)/subjects.tsx b/app/(tabs)/subjects.tsx
index f57a860..62d82eb 100644
--- a/app/(tabs)/subjects.tsx
+++ b/app/(tabs)/subjects.tsx
@@ -8,8 +8,6 @@ import { Redirect, router, Stack, useFocusEffect } from 'expo-router';
import { useCallback, useEffect, useState } from 'react';
import { Alert, Modal, Pressable, ScrollView, Text, View, ActivityIndicator } from 'react-native';
-import type { SubjectColor } from '@/lib/subjectColors';
-
const FLOW_STEPS = [
{
label: '1',
@@ -77,8 +75,6 @@ export default function Subjects() {
}, [session?.user.id]);
const GetSubjects = useCallback(async () => {
- if (!session?.user.id) return;
- const GetSubjects = async () => {
if (!session?.user.id) {
SetIsLoading(false);
return;
@@ -86,26 +82,21 @@ export default function Subjects() {
SetIsLoading(true);
- SetIsLoading(true);
-
const { data, error } = await supabase
.from('subjects')
.select('*')
.eq('uId', session.user.id)
.order('lastChanged', { ascending: false });
- SetIsLoading(false);
+ SetIsLoading(false);
if (error) {
Alert.alert('Subjects could not be fetched, please try again');
- SetIsLoading(false);
return;
}
SetSubjects((data as Subject[]) ?? []);
}, [session?.user.id]);
- SetIsLoading(false);
- };
useFocusEffect(
useCallback(() => {
@@ -121,6 +112,8 @@ export default function Subjects() {
if (needsSetup) {
return ;
+ }
+
const RenderSubjectCard = (subject: Subject) => {
const colorKey: SubjectColor = subject.color ?? 'slate';
const colorSet = SUBJECT_COLORS[colorKey];
diff --git a/app/subject/viewDetailsSubject.tsx b/app/subject/viewDetailsSubject.tsx
index 5658457..cf27a22 100644
--- a/app/subject/viewDetailsSubject.tsx
+++ b/app/subject/viewDetailsSubject.tsx
@@ -348,39 +348,36 @@ export default function ViewDetailsSubject() {
- Assignments completed
+ Assignment Progress
- {totalAssignments > 0 ? (
-
-
-
- Assignment Progress
-
-
- {completedAssignments}/{totalAssignments}
-
-
-
-
-
-
-
-
- {remainingAssignments === 0
- ? 'All assignments complete'
- : `${remainingAssignments} assignment${
- remainingAssignments === 1 ? '' : 's'
- } remaining`}
+
+ {completedAssignments}/{totalAssignments}
- ) : null}
+
+
+
+
+
+
+ {remainingAssignments === 0
+ ? 'All assignments complete'
+ : `${remainingAssignments} assignment${
+ remainingAssignments === 1 ? '' : 's'
+ } remaining`}
+
+
+
+ Based only on completed assignments in this subject.
+
+
Last changed: {formatDateTime(subject.lastChanged)}
diff --git a/app/task/viewDetailsTask.tsx b/app/task/viewDetailsTask.tsx
index fda188f..7391c7e 100644
--- a/app/task/viewDetailsTask.tsx
+++ b/app/task/viewDetailsTask.tsx
@@ -31,65 +31,46 @@ function formatTrackedTime(totalSeconds: number) {
}
export default function ViewDetailsTask() {
-const { tId } = useLocalSearchParams<{ tId: string }>();
+ const { tId } = useLocalSearchParams<{ tId: string }>();
-const [task, SetTask] = useState(null);
-const [session, SetSession] = useState(null);
-const [completedFocusSessions, setCompletedFocusSessions] = useState(0);
-const [contextMeta, setContextMeta] = useState({
- subjectTitle: 'No Subject',
- assignmentTitle: 'No Assignment',
- subjectColor: 'slate' as SubjectColor,
-});
-
-useEffect(() => {
- supabase.auth.getSession().then(({ data }) => SetSession(data.session ?? null));
-
- const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => {
- SetSession(newSession);
+ const [task, SetTask] = useState(null);
+ const [session, SetSession] = useState(null);
+ const [isLoading, SetIsLoading] = useState(false);
+ const [completedFocusSessions, setCompletedFocusSessions] = useState(0);
+ const [contextMeta, setContextMeta] = useState({
+ subjectTitle: 'No Subject',
+ assignmentTitle: 'No Assignment',
+ subjectColor: 'slate' as SubjectColor,
});
- return () => sub.subscription.unsubscribe();
-}, []);
+ useEffect(() => {
+ supabase.auth.getSession().then(({ data }) => SetSession(data.session ?? null));
-const loadTaskStudyActivity = useCallback(async (taskId: string, userId: string) => {
- const { count, error } = await supabase
- .from('sprint_sessions')
- .select('sessionId', { count: 'exact', head: true })
- .eq('taskId', taskId)
- .eq('userId', userId)
- .eq('sessionType', 'focus')
- .eq('status', 'completed');
+ const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => {
+ SetSession(newSession);
+ });
- if (error) {
- setCompletedFocusSessions(0);
- return;
- }
+ return () => sub.subscription.unsubscribe();
+ }, []);
- setCompletedFocusSessions(count ?? 0);
-}, []);
+ const loadTaskStudyActivity = useCallback(async (taskId: string, userId: string) => {
+ const { count, error } = await supabase
+ .from('sprint_sessions')
+ .select('sessionId', { count: 'exact', head: true })
+ .eq('taskId', taskId)
+ .eq('userId', userId)
+ .eq('sessionType', 'focus')
+ .eq('status', 'completed');
-const GetTask = useCallback(async (taskId: string) => {
- const { data, error } = await supabase
- .from('tasks')
- .select('*')
- .eq('tId', taskId)
- .single();
+ if (error) {
+ setCompletedFocusSessions(0);
+ return;
+ }
- if (error || !data) {
- Alert.alert('Task could not be fetched, please try again');
- return;
- }
+ setCompletedFocusSessions(count ?? 0);
+ }, []);
- SetTask(data);
- await loadTaskStudyActivity(taskId, data.uId);
-
- if (data.aId) {
- const { data: assignmentData, error: assignmentError } = await supabase
- .from('assignments')
- .select('title, sId')
- .eq('aId', data.aId)
- const GetTask = async (taskId: string) => {
+ const GetTask = useCallback(async (taskId: string) => {
SetIsLoading(true);
const { data, error } = await supabase
@@ -97,130 +78,113 @@ const GetTask = useCallback(async (taskId: string) => {
.select('*')
.eq('tId', taskId)
.single();
-
- SetIsLoading(false);
- if (assignmentError || !assignmentData) {
+ if (error || !data) {
+ SetTask(null);
setContextMeta({
subjectTitle: 'Unknown Subject',
assignmentTitle: 'Unknown Assignment',
subjectColor: 'slate',
});
+ setCompletedFocusSessions(0);
+ SetIsLoading(false);
+ Alert.alert('Task could not be fetched, please try again');
return;
}
- if (assignmentData.sId) {
- const { data: subjectData, error: subjectError } = await supabase
- .from('subjects')
- .select('title, color')
- .eq('sId', assignmentData.sId)
- .single();
-
- if (subjectError || !subjectData) {
SetTask(data);
+ await loadTaskStudyActivity(taskId, data.uId);
+
+ let nextContextMeta = {
+ subjectTitle: 'Unknown Subject',
+ assignmentTitle: 'Unknown Assignment',
+ subjectColor: 'slate' as SubjectColor,
+ };
if (data.aId) {
- SetIsLoading(true);
-
const { data: assignmentData, error: assignmentError } = await supabase
.from('assignments')
.select('title, sId')
.eq('aId', data.aId)
.single();
- SetIsLoading(false);
+ if (!assignmentError && assignmentData) {
+ nextContextMeta.assignmentTitle = assignmentData.title ?? 'Unknown Assignment';
- if (assignmentError || !assignmentData) {
- setContextMeta({
- subjectTitle: 'Unknown Subject',
- assignmentTitle: assignmentData.title ?? 'Unknown Assignment',
- subjectColor: 'slate',
- });
- return;
- }
+ if (assignmentData.sId) {
+ const { data: subjectData, error: subjectError } = await supabase
+ .from('subjects')
+ .select('title, color')
+ .eq('sId', assignmentData.sId)
+ .single();
- setContextMeta({
- subjectTitle: subjectData.title ?? 'Unknown Subject',
- assignmentTitle: assignmentData.title ?? 'Unknown Assignment',
- subjectColor: (subjectData.color as SubjectColor | undefined) ?? 'slate',
- });
- }
- }
-}, [loadTaskStudyActivity]);
- if (assignmentData.sId) {
- SetIsLoading(true);
-
- const { data: subjectData, error: subjectError } = await supabase
- .from('subjects')
- .select('title, color')
- .eq('sId', assignmentData.sId)
- .single();
-
- SetIsLoading(false);
-
- if (subjectError || !subjectData) {
- setContextMeta({
- subjectTitle: 'Unknown Subject',
- assignmentTitle: assignmentData.title ?? 'Unknown Assignment',
- subjectColor: 'slate',
- });
- return;
+ if (!subjectError && subjectData) {
+ nextContextMeta = {
+ subjectTitle: subjectData.title ?? 'Unknown Subject',
+ assignmentTitle: assignmentData.title ?? 'Unknown Assignment',
+ subjectColor: (subjectData.color as SubjectColor | undefined) ?? 'slate',
+ };
+ }
}
-
-useFocusEffect(
- useCallback(() => {
- if (session && tId) {
- GetTask(tId);
- }
- }, [GetTask, session, tId])
-);
-
-const handleSprintStart = async () => {
- const activeSession = await GetActiveSession();
-
- if (!activeSession) {
- router.push({
- pathname: '/task/timer',
- params: {
- tId: task?.tId,
- durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
- },
- });
- return;
- }
-
-
-
- const secondsLeft = Math.ceil((activeSession.endTime - Date.now()) / 1000)
-
- if (secondsLeft <= 0) {
- await finalizeStoredSession('expired', activeSession);
- router.push({
- pathname: '/task/timer',
- params: {
- tId: task?.tId,
- durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
}
- });
- return;
}
-
- if (activeSession.taskId === task?.tId) {
- router.push({
- pathname: '/task/timer',
- params: {
- tId: activeSession.taskId ?? undefined,
- durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
- }});
- return;
+ setContextMeta(nextContextMeta);
+ SetIsLoading(false);
+ }, [loadTaskStudyActivity]);
+
+ useFocusEffect(
+ useCallback(() => {
+ if (session && tId) {
+ void GetTask(tId);
+ }
+ }, [GetTask, session, tId])
+ );
+
+ const handleSprintStart = async () => {
+ const activeSession = await GetActiveSession();
+
+ if (!activeSession) {
+ router.push({
+ pathname: '/task/timer',
+ params: {
+ tId: task?.tId,
+ durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
+ },
+ });
+ return;
+ }
+
+ const secondsLeft = Math.ceil((activeSession.endTime - Date.now()) / 1000);
+
+ if (secondsLeft <= 0) {
+ await finalizeStoredSession('expired', activeSession);
+ router.push({
+ pathname: '/task/timer',
+ params: {
+ tId: task?.tId,
+ durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
+ },
+ });
+ return;
+ }
+
+ if (activeSession.taskId === task?.tId) {
+ router.push({
+ pathname: '/task/timer',
+ params: {
+ tId: activeSession.taskId ?? undefined,
+ durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES),
+ },
+ });
+ return;
}
Alert.alert(
- 'Active session in progress',
+ 'Active session in progress',
`End the current session and start a new ${DEFAULT_FOCUS_DURATION_MINUTES} minute sprint on this task?`,
[
- { text: 'Cancel', style: 'cancel', },
+ { text: 'Cancel', style: 'cancel' },
{
text: 'Start new sprint',
style: 'destructive',
@@ -239,6 +203,47 @@ const handleSprintStart = async () => {
);
};
+ const DeleteTask = async (taskId: string) => {
+ Alert.alert(
+ 'Delete Task',
+ 'Are you sure you want to delete this task?',
+ [
+ {
+ text: 'Cancel',
+ style: 'cancel',
+ },
+ {
+ text: 'Delete',
+ style: 'destructive',
+ onPress: async () => {
+ const { error } = await supabase
+ .from('tasks')
+ .delete()
+ .eq('tId', taskId);
+
+ if (error) {
+ Alert.alert('Task could not be deleted, please try again');
+ return;
+ }
+
+ const aId = task?.aId;
+
+ if (aId) {
+ try {
+ await CheckAssignmentCompletion(aId);
+ } catch {
+ Alert.alert('Failed to update assignment completion state');
+ }
+ }
+
+ Alert.alert('Task deleted successfully!');
+ router.back();
+ },
+ },
+ ]
+ );
+ };
+
const colorSet = getSubjectColorSet(contextMeta.subjectColor);
if (isLoading) {
@@ -268,56 +273,41 @@ const handleSprintStart = async () => {
}}
/>
+
+
+ Task not found
+
+
+ The task could not be loaded.
+
-const DeleteTask = async (taskId: string) => {
- Alert.alert(
- 'Delete Task',
- 'Are you sure you want to delete this task?',
- [
- {
- text: 'Cancel',
- style: 'cancel',
- },
- {
- text: 'Delete',
- style: 'destructive',
- onPress: async () => {
- const { error } = await supabase
- .from('tasks')
- .delete()
- .eq('tId', taskId);
+ router.back()}
+ >
+
+ Go back
+
+
+
+
+ );
+ }
- if (error) {
- Alert.alert('Task could not be deleted, please try again');
- return;
- }
+ const isOwner = session?.user.id === task.uId;
- const aId = task?.aId;
-
- if (aId) {
- try {
- await CheckAssignmentCompletion(aId);
- } catch {
- Alert.alert('Failed to update assignment completion state');
- }
- }
-
- Alert.alert('Task deleted successfully!');
- router.back();
- },
- },
- ]
- );
-};
-
-const colorSet = getSubjectColorSet(contextMeta.subjectColor);
-
-if (!task) {
return (
(
-
- Task not found
-
-
- The task could not be loaded.
-
-
- router.back()}
- >
-
- Go back
-
-
-
-
- );
-}
-
-const isOwner = session?.user.id === task.uId;
-
-return (
-
- (
- await supabase.auth.signOut()}
+
+
-
- Logout
-
-
- ),
- }}
- />
+ {task.isCompleted ? (
+ ✓
+ ) : null}
+
-
-
-
- {task.isCompleted && (
- ✓
- )}
-
-
-
-
- {task.title}
-
-
- {task.description ? (
-
- {task.description}
-
- ) : (
-
- No description added.
-
- )}
-
-
-
+
-
+
+ {task.description ? (
+
+ {task.description}
+
+ ) : (
+
+ No description added.
+
+ )}
+
+
+
- {contextMeta.subjectTitle}
-
-
-
-
-
- {contextMeta.assignmentTitle}
-
-
-
-
-
- Status: {task.isCompleted ? 'Completed' : 'Not completed'}
-
-
-
-
-
-
- Study activity
-
-
- This tracks focused work on the task separately from whether the task is marked completed.
-
-
-
-
-
- Focus time
-
-
- {formatTrackedTime(task.totalTimeInSeconds ?? 0)}
+
+ {contextMeta.subjectTitle}
-
-
- Completed sessions
+
+
+ {contextMeta.assignmentTitle}
-
- {completedFocusSessions}
+
+
+
+
+ Status: {task.isCompleted ? 'Completed' : 'Not completed'}
+
+
+
+ Study activity
+
+
+ This tracks focused work on the task separately from whether the task is marked completed.
+
+
+
+
+
+ Focus time
+
+
+ {formatTrackedTime(task.totalTimeInSeconds ?? 0)}
+
+
+
+
+
+ Completed sessions
+
+
+ {completedFocusSessions}
+
+
+
+
+
+
+ Last changed: {formatDateTime(task.lastChanged)}
+
-
-
- Last changed: {formatDateTime(task.lastChanged)}
-
+
+ {isOwner ? (
+
+
+
+ Start Sprint
+
+
+
+
+ Starts a {DEFAULT_FOCUS_DURATION_MINUTES} minute focus sprint for this task.
+
+
+
+
+ router.push({
+ pathname: '/task/upsertTask',
+ params: { tId: task.tId },
+ })
+ }
+ >
+
+ Edit
+
+
+
+ DeleteTask(task.tId)}
+ >
+
+ Delete
+
+
+
+
+ ) : null}
-
- {isOwner && (
-
- handleSprintStart()}
- >
-
- Start Sprint
-
-
-
-
- Starts a {DEFAULT_FOCUS_DURATION_MINUTES} minute focus sprint for this task.
-
-
-
-
- router.push({
- pathname: '/task/upsertTask',
- params: { tId: task.tId },
- })
- }
- >
-
- Edit
-
-
- DeleteTask(task.tId)}
- >
-
- Delete
-
-
-
-
- )}
-
);
}