Added progress bar for assignments (UI fix needed)

This commit is contained in:
Teodor
2026-04-23 03:27:26 +02:00
parent e178ab0b4b
commit a35353f45b
17 changed files with 388 additions and 115 deletions

View File

@@ -19,7 +19,8 @@
"monochromeImage": "./assets/images/android-icon-monochrome.png" "monochromeImage": "./assets/images/android-icon-monochrome.png"
}, },
"edgeToEdgeEnabled": true, "edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false "predictiveBackGestureEnabled": false,
"package": "com.teodorsa.StudySprint"
}, },
"web": { "web": {
"output": "static", "output": "static",
@@ -44,6 +45,12 @@
"experiments": { "experiments": {
"typedRoutes": true, "typedRoutes": true,
"reactCompiler": true "reactCompiler": true
},
"extra": {
"router": {},
"eas": {
"projectId": "25652385-934a-4a29-8fa7-deff3281e03e"
}
} }
} }
} }

View File

@@ -1,5 +1,6 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import type { Assignment, Task } from '@/lib/types';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { Session } from '@supabase/supabase-js'; import { Session } from '@supabase/supabase-js';
import { router, Stack, useFocusEffect } from 'expo-router'; import { router, Stack, useFocusEffect } from 'expo-router';
@@ -12,19 +13,9 @@ import {
View, View,
} from 'react-native'; } from 'react-native';
type Assignment = {
aId: string;
title: string;
description: string;
deadline: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
sId: string;
};
export default function Assignments() { export default function Assignments() {
const [assignments, SetAssignments] = useState<Assignment[]>([]); const [assignments, SetAssignments] = useState<Assignment[]>([]);
const [tasksByAssignment, SetTasksByAssignment] = useState<Record<string, Task[]>>({});
const [session, SetSession] = useState<Session | null>(null); const [session, SetSession] = useState<Session | null>(null);
const assignmentSections = [ const assignmentSections = [
@@ -55,17 +46,47 @@ export default function Assignments() {
}, []); }, []);
const GetAssignments = async () => { const GetAssignments = async () => {
const { data, error } = await supabase const { data: assignmentsData, error: assignmentsError } = await supabase
.from('assignments') .from('assignments')
.select('*') .select('*')
.order('deadline', { ascending: false }); .order('deadline', { ascending: false });
if (error) { if (assignmentsError) {
Alert.alert('Assignments could not be fetched, please try again'); Alert.alert('Assignments could not be fetched, please try again');
return; return;
} }
SetAssignments(data ?? []); const assignmentRows = assignmentsData ?? [];
SetAssignments(assignmentRows);
if (assignmentRows.length === 0) {
SetTasksByAssignment({});
return;
}
const aIds = assignmentRows.map((assignment) => assignment.aId);
const { data: tasksData, error: tasksError } = await supabase
.from('tasks')
.select('*')
.in('aId', aIds);
if (tasksError) {
Alert.alert('Assignment tasks could not be fetched, please try again');
SetTasksByAssignment({});
return;
}
const groupedTasks: Record<string, Task[]> = {};
for (const task of tasksData ?? []) {
if (!groupedTasks[task.aId]) {
groupedTasks[task.aId] = [];
}
groupedTasks[task.aId].push(task);
}
SetTasksByAssignment(groupedTasks);
}; };
useFocusEffect( useFocusEffect(
@@ -178,6 +199,9 @@ export default function Assignments() {
renderItem={({ item }) => { renderItem={({ item }) => {
const isOwner = session?.user.id === item.uId; const isOwner = session?.user.id === item.uId;
const assignmentTasks = tasksByAssignment[item.aId] ?? [];
const progress = assignmentTasks.length === 0 ? 0 : Math.round((assignmentTasks.filter(task => task.isCompleted).length / assignmentTasks.length) * 100);
return ( return (
<View className="mb-4 rounded-3xl border border-app-border bg-app-surface p-4 shadow-sm"> <View className="mb-4 rounded-3xl border border-app-border bg-app-surface p-4 shadow-sm">
<Pressable <Pressable
@@ -228,6 +252,28 @@ export default function Assignments() {
Deadline: {item.deadline || 'No deadline'} Deadline: {item.deadline || 'No deadline'}
</Text> </Text>
</View> </View>
<View style={{ marginTop: 10 }}>
<Text style={{ marginBottom: 4 }}>{progress}%</Text>
<View
style={{
width: "100%",
height: 12,
backgroundColor: "#D9D9D9",
borderRadius: 999,
overflow: "hidden",
}}
>
<View
style={{
width: `${progress}%`,
height: "100%",
backgroundColor: "#4CAF50",
}}
/>
</View>
</View>
</View> </View>
</View> </View>
</Pressable> </Pressable>

View File

@@ -1,5 +1,6 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import type { Subject } from '@/lib/types';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { Session } from '@supabase/supabase-js'; import { Session } from '@supabase/supabase-js';
import { router, Stack, useFocusEffect } from 'expo-router'; import { router, Stack, useFocusEffect } from 'expo-router';
@@ -12,15 +13,6 @@ import {
View, View,
} from 'react-native'; } from 'react-native';
type Subject = {
sId: string;
title: string;
description: string;
isActive: boolean;
lastChanged: string;
uId: string;
};
export default function Subjects() { export default function Subjects() {
const [subjects, SetSubjects] = useState<Subject[]>([]); const [subjects, SetSubjects] = useState<Subject[]>([]);
const [session, SetSession] = useState<Session | null>(null); const [session, SetSession] = useState<Session | null>(null);

View File

@@ -1,5 +1,6 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import type { Task } from '@/lib/types';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { Session } from '@supabase/supabase-js'; import { Session } from '@supabase/supabase-js';
import { router, Stack, useFocusEffect } from 'expo-router'; import { router, Stack, useFocusEffect } from 'expo-router';
@@ -12,16 +13,6 @@ import {
View, View,
} from 'react-native'; } from 'react-native';
type Task = {
tId: string;
title: string;
description: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
aId: string;
};
export default function Tasks() { export default function Tasks() {
const [tasks, SetTasks] = useState<Task[]>([]); const [tasks, SetTasks] = useState<Task[]>([]);
const [session, SetSession] = useState<Session | null>(null); const [session, SetSession] = useState<Session | null>(null);

View File

@@ -1,22 +1,12 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { GetAssignmentNotificationId, RemoveAssignmentNotificationId, SaveAssignmentNotificationId } from '@/lib/asyncStorage'; import { GetAssignmentNotificationId, RemoveAssignmentNotificationId, SaveAssignmentNotificationId } from '@/lib/asyncStorage';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import type { Assignment } from '@/lib/types';
import * as Notifications from 'expo-notifications'; import * as Notifications from 'expo-notifications';
import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router'; import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native'; import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
type Assignment = {
aId: string;
title: string;
description: string;
deadline: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
sId: string;
}
export default function EditAssignment() { export default function EditAssignment() {
const { aId } = useLocalSearchParams<{ aId: string }>(); const { aId } = useLocalSearchParams<{ aId: string }>();
const [assignment, SetAssignment] = useState<Assignment | null>(null) const [assignment, SetAssignment] = useState<Assignment | null>(null)

View File

@@ -1,31 +1,11 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import type { Assignment, Task } from '@/lib/types';
import { Session } from '@supabase/supabase-js'; import { Session } from '@supabase/supabase-js';
import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router'; import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Alert, Button, Pressable, SectionList, Text, View } from "react-native"; import { Alert, Button, Pressable, SectionList, Text, View } from "react-native";
type Assignment = {
aId: string;
title: string;
description: string;
deadline: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
sId: string;
}
type Task = {
tId: string;
title: string;
description: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
aId: string;
}
export default function ViewDetailsAssignment() { export default function ViewDetailsAssignment() {
const { aId } = useLocalSearchParams<{ aId: string }>(); const { aId } = useLocalSearchParams<{ aId: string }>();
const [assignment, SetAssignment] = useState<Assignment | null>(null) const [assignment, SetAssignment] = useState<Assignment | null>(null)
@@ -133,6 +113,8 @@ export default function ViewDetailsAssignment() {
) )
} }
const progress = tasks.length === 0 ? 0 : Math.round((tasks.filter(task => task.isCompleted).length / tasks.length) * 100);
return ( return (
<View style={defaultStyles.container}> <View style={defaultStyles.container}>
<Stack.Screen <Stack.Screen
@@ -171,6 +153,27 @@ export default function ViewDetailsAssignment() {
<View style={defaultStyles.checkbox}> <View style={defaultStyles.checkbox}>
{assignment.isCompleted && <Text style={defaultStyles.checkboxMark}></Text>} {assignment.isCompleted && <Text style={defaultStyles.checkboxMark}></Text>}
</View> </View>
<View style={{ width: "100%", marginTop: 8 }}>
<Text style={{ marginBottom: 4 }}>{progress}%</Text>
<View
style={{
width: "100%",
height: 12,
backgroundColor: "#D9D9D9",
borderRadius: 999,
overflow: "hidden",
}}
>
<View
style={{
width: `${progress}%`,
height: "100%",
backgroundColor: "#4CAF50",
}}
/>
</View>
</View>
<Text style={defaultStyles.body}>{assignment.lastChanged}</Text> <Text style={defaultStyles.body}>{assignment.lastChanged}</Text>
<Button title="Edit" onPress={() => router.push({pathname: "/assignment/editAssignment", params: { aId: assignment.aId }})} /> <Button title="Edit" onPress={() => router.push({pathname: "/assignment/editAssignment", params: { aId: assignment.aId }})} />

View File

@@ -1,18 +1,10 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import type { Subject } from '@/lib/types';
import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router'; import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native'; import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
type Subject = {
sId: string;
title: string;
description: string;
isActive: boolean;
lastChanged: string;
uId: string;
}
export default function EditSubject() { export default function EditSubject() {
const { sId } = useLocalSearchParams<{ sId: string }>(); const { sId } = useLocalSearchParams<{ sId: string }>();
const [subject, SetSubject] = useState<Subject | null>(null) const [subject, SetSubject] = useState<Subject | null>(null)

View File

@@ -1,30 +1,11 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import type { Assignment, Subject } from '@/lib/types';
import { Session } from '@supabase/supabase-js'; import { Session } from '@supabase/supabase-js';
import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router'; import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Alert, Button, Pressable, SectionList, Text, View } from "react-native"; import { Alert, Button, Pressable, SectionList, Text, View } from "react-native";
type Subject = {
sId: string;
title: string;
description: string;
isActive: boolean;
lastChanged: string;
uId: string;
}
type Assignment = {
aId: string;
title: string;
description: string;
deadline: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
sId: string;
}
export default function ViewDetailsSubject() { export default function ViewDetailsSubject() {
const { sId } = useLocalSearchParams<{ sId: string }>(); const { sId } = useLocalSearchParams<{ sId: string }>();
const [subject, SetSubject] = useState<Subject | null>(null) const [subject, SetSubject] = useState<Subject | null>(null)
@@ -132,6 +113,10 @@ export default function ViewDetailsSubject() {
) )
} }
const CalculateSubjectCompletion = async () => {
}
return ( return (
<View style={defaultStyles.container}> <View style={defaultStyles.container}>
<Stack.Screen <Stack.Screen

View File

@@ -1,4 +1,5 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { CheckAssignmentCompletion } from '@/lib/progress';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import { router, Stack, useLocalSearchParams } from 'expo-router'; import { router, Stack, useLocalSearchParams } from 'expo-router';
import { useState } from 'react'; import { useState } from 'react';
@@ -56,6 +57,14 @@ export default function CreateTask() {
Alert.alert('Task successfully created!'); Alert.alert('Task successfully created!');
if (aId) {
try {
await CheckAssignmentCompletion(aId);
} catch {
Alert.alert("Failed to update assignment completion state");
}
}
SetTitle(''); SetTitle('');
SetDescription(''); SetDescription('');
SetIsCompleted(false); SetIsCompleted(false);

View File

@@ -1,19 +1,11 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { CheckAssignmentCompletion } from '@/lib/progress';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import type { Task } from '@/lib/types';
import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router'; import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native'; import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
type Task = {
tId: string;
title: string;
description: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
aId: string;
}
export default function EditTask() { export default function EditTask() {
const { tId } = useLocalSearchParams<{ tId: string }>(); const { tId } = useLocalSearchParams<{ tId: string }>();
const [task, SetTask] = useState<Task | null>(null) const [task, SetTask] = useState<Task | null>(null)
@@ -73,6 +65,14 @@ export default function EditTask() {
Alert.alert("Task successfully edited!"); Alert.alert("Task successfully edited!");
if (task.aId) {
try {
await CheckAssignmentCompletion(task.aId);
} catch {
Alert.alert("Failed to update assignment completion state");
}
}
router.back(); router.back();
} }

View File

@@ -1,20 +1,12 @@
import { defaultStyles } from '@/constants/defaultStyles'; import { defaultStyles } from '@/constants/defaultStyles';
import { CheckAssignmentCompletion } from '@/lib/progress';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import type { Task } from '@/lib/types';
import { Session } from '@supabase/supabase-js'; import { Session } from '@supabase/supabase-js';
import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router'; import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Alert, Button, Text, View } from "react-native"; import { Alert, Button, Text, View } from "react-native";
type Task = {
tId: string;
title: string;
description: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
aId: string;
}
export default function ViewDetailsTask() { export default function ViewDetailsTask() {
const { tId } = useLocalSearchParams<{ tId: string }>(); const { tId } = useLocalSearchParams<{ tId: string }>();
const [task, SetTask] = useState<Task | null>(null) const [task, SetTask] = useState<Task | null>(null)
@@ -69,6 +61,17 @@ export default function ViewDetailsTask() {
} }
Alert.alert("Task deleted successfully!"); Alert.alert("Task deleted successfully!");
const aId = task?.aId;
if (aId) {
try {
await CheckAssignmentCompletion(aId);
} catch {
Alert.alert("Failed to update assignment completion state");
}
}
router.back(); router.back();
} }
} }

21
eas.json Normal file
View File

@@ -0,0 +1,21 @@
{
"cli": {
"version": ">= 18.8.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}

15
lib/progress.ts Normal file
View File

@@ -0,0 +1,15 @@
import { supabase } from '@/lib/supabase';
export async function CheckAssignmentCompletion(aId: string) {
const { data, error } = await supabase.from("tasks").select("tId, isCompleted").eq("aId", aId);
if (error) throw error;
const tasks = data ?? [];
const allCompleted = tasks.length > 0 && tasks.every((task) => task.isCompleted === true);
const { error: updateError } = await supabase.from("assignments").update({ isCompleted: allCompleted, lastChanged: new Date().toISOString()}).eq("aId", aId);
if (updateError) throw updateError;
}

29
lib/types.ts Normal file
View File

@@ -0,0 +1,29 @@
export type Task = {
tId: string;
title: string;
description: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
aId: string;
};
export type Assignment = {
aId: string;
title: string;
description: string;
deadline: string;
isCompleted: boolean;
lastChanged: string;
uId: string;
sId: string;
};
export type Subject = {
sId: string;
title: string;
description: string;
isActive: boolean;
lastChanged: string;
uId: string;
};

71
notes/new-build-guide.md Normal file
View File

@@ -0,0 +1,71 @@
# DevelopmentBuildGuide
This project uses an **Expo development build** for features that are not fully supported in Expo Go, such as our notification setup.
---
## WhyWeUseThis
We do **not** use Expo Go for this project when testing notifications or other native features.
Reason:
- Expo Go has limitations for some native modules
- `expo-notifications` on Android requires a development build for reliable testing
- a development build works like a custom Expo Go app made specifically for this project
---
## #ImportantRule
You do **not** need to rebuild the APK for every code change.
### Rebuild is **not needed** for:
- changing React components
- changing screen layouts
- changing styles
- changing Supabase queries
- changing JS/TS functions
- changing form logic
- changing routing logic
- changing notification scheduling logic in JavaScript only
### Rebuild **is needed** for:
- adding a new native dependency
- removing a native dependency
- changing `app.json`
- changing Expo plugins
- changing Android/iOS permissions
- changing native notification config
- anything that affects the native app shell
---
## OneTimeSetup
Install EAS CLI globally if needed:
npm install -g eas-cli
Log in to Expo:
eas login
Install the Expo development client in the project:
npx expo install expo-dev-client
# BuildTheDevelopmentAPK
Run this command from the project root:
eas build --platform android --profile development
This sends the build to Expo's cloud build service.
When the build is finished:
open the build link
click Install
install the APK on your Android phone or emulator
Do not place the APK inside the project folder.
The APK is something you install on the device, not a source file.
# DailyWorkflow
After the development build APK is installed:
Start the project:
npx expo start

118
package-lock.json generated
View File

@@ -17,6 +17,7 @@
"@supabase/supabase-js": "^2.103.1", "@supabase/supabase-js": "^2.103.1",
"expo": "~54.0.33", "expo": "~54.0.33",
"expo-constants": "~18.0.13", "expo-constants": "~18.0.13",
"expo-dev-client": "~6.0.20",
"expo-font": "~14.0.11", "expo-font": "~14.0.11",
"expo-haptics": "~15.0.8", "expo-haptics": "~15.0.8",
"expo-image": "~3.0.11", "expo-image": "~3.0.11",
@@ -6394,6 +6395,79 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/expo-dev-client": {
"version": "6.0.20",
"resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-6.0.20.tgz",
"integrity": "sha512-5XjoVlj1OxakNxy55j/AUaGPrDOlQlB6XdHLLWAw61w5ffSpUDHDnuZzKzs9xY1eIaogOqTOQaAzZ2ddBkdXLA==",
"license": "MIT",
"dependencies": {
"expo-dev-launcher": "6.0.20",
"expo-dev-menu": "7.0.18",
"expo-dev-menu-interface": "2.0.0",
"expo-manifests": "~1.0.10",
"expo-updates-interface": "~2.0.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-dev-launcher": {
"version": "6.0.20",
"resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-6.0.20.tgz",
"integrity": "sha512-a04zHEeT9sB0L5EB38fz7sNnUKJ2Ar1pXpcyl60Ki8bXPNCs9rjY7NuYrDkP/irM8+1DklMBqHpyHiLyJ/R+EA==",
"license": "MIT",
"dependencies": {
"ajv": "^8.11.0",
"expo-dev-menu": "7.0.18",
"expo-manifests": "~1.0.10"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-dev-launcher/node_modules/ajv": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/expo-dev-launcher/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/expo-dev-menu": {
"version": "7.0.18",
"resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-7.0.18.tgz",
"integrity": "sha512-4kTdlHrnZCAWCT6tZRQHSSjZ7vECFisL4T+nsG/GJDo/jcHNaOVGV5qPV9wzlTxyMk3YOPggRw4+g7Ownrg5eA==",
"license": "MIT",
"dependencies": {
"expo-dev-menu-interface": "2.0.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-dev-menu-interface": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-2.0.0.tgz",
"integrity": "sha512-BvAMPt6x+vyXpThsyjjOYyjwfjREV4OOpQkZ0tNl+nGpsPfcY9mc6DRACoWnH9KpLzyIt3BOgh3cuy/h/OxQjw==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-file-system": { "node_modules/expo-file-system": {
"version": "19.0.21", "version": "19.0.21",
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.21.tgz", "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.21.tgz",
@@ -6444,6 +6518,12 @@
} }
} }
}, },
"node_modules/expo-json-utils": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.15.0.tgz",
"integrity": "sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==",
"license": "MIT"
},
"node_modules/expo-keep-awake": { "node_modules/expo-keep-awake": {
"version": "15.0.8", "version": "15.0.8",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-15.0.8.tgz", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-15.0.8.tgz",
@@ -6468,6 +6548,19 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/expo-manifests": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-1.0.10.tgz",
"integrity": "sha512-oxDUnURPcL4ZsOBY6X1DGWGuoZgVAFzp6PISWV7lPP2J0r8u1/ucuChBgpK7u1eLGFp6sDIPwXyEUCkI386XSQ==",
"license": "MIT",
"dependencies": {
"@expo/config": "~12.0.11",
"expo-json-utils": "~0.15.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-modules-autolinking": { "node_modules/expo-modules-autolinking": {
"version": "3.0.24", "version": "3.0.24",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.24.tgz", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.24.tgz",
@@ -6846,6 +6939,15 @@
} }
} }
}, },
"node_modules/expo-updates-interface": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-2.0.0.tgz",
"integrity": "sha512-pTzAIufEZdVPKql6iMi5ylVSPqV1qbEopz9G6TSECQmnNde2nwq42PxdFBaUEd8IZJ/fdJLQnOT3m6+XJ5s7jg==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-web-browser": { "node_modules/expo-web-browser": {
"version": "15.0.10", "version": "15.0.10",
"resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-15.0.10.tgz", "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-15.0.10.tgz",
@@ -7101,6 +7203,22 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.20.1", "version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",

View File

@@ -19,6 +19,7 @@
"@supabase/supabase-js": "^2.103.1", "@supabase/supabase-js": "^2.103.1",
"expo": "~54.0.33", "expo": "~54.0.33",
"expo-constants": "~18.0.13", "expo-constants": "~18.0.13",
"expo-dev-client": "~6.0.20",
"expo-font": "~14.0.11", "expo-font": "~14.0.11",
"expo-haptics": "~15.0.8", "expo-haptics": "~15.0.8",
"expo-image": "~3.0.11", "expo-image": "~3.0.11",