diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6c2d8b6..3a5ebac 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -14,9 +14,14 @@ - + + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index f387b90..ebe53de 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -3,4 +3,5 @@ #ffffff #023c69 #ffffff + #ffffff \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 5c7a9f1..4713052 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -3,4 +3,5 @@ contain false light + 1.0.0 \ No newline at end of file diff --git a/app.json b/app.json index 795c6b6..a73d188 100644 --- a/app.json +++ b/app.json @@ -25,6 +25,10 @@ }, "package": "com.cally.app", "googleServicesFile": "./android/app/google-services.json", + "permissions": [ + "android.permission.CAMERA", + "android.permission.RECORD_AUDIO" + ] }, "web": { "bundler": "metro", @@ -51,6 +55,13 @@ "microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone", "recordAudioAndroid": true } + ], + [ + "expo-notifications", + { + "color": "#ffffff", + "defaultChannel": "default" + } ] ], "experiments": { @@ -63,6 +74,10 @@ "eas": { "projectId": "bdb8c57b-25bb-4d36-b3b8-5b09c5092f52" } + }, + "runtimeVersion": "1.0.0", + "updates": { + "url": "https://u.expo.dev/bdb8c57b-25bb-4d36-b3b8-5b09c5092f52" } } } diff --git a/app/(auth)/_layout.tsx b/app/(auth)/_layout.tsx index b6e8aee..2edbd97 100644 --- a/app/(auth)/_layout.tsx +++ b/app/(auth)/_layout.tsx @@ -18,6 +18,11 @@ import { } from "@expo/vector-icons"; import MenuIcon from "@/assets/svgs/MenuIcon"; import { router } from "expo-router"; +import NavGroceryIcon from "@/assets/svgs/NavGroceryIcon"; +import NavToDosIcon from "@/assets/svgs/NavToDosIcon"; +import NavBrainDumpIcon from "@/assets/svgs/NavBrainDumpIcon"; +import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon"; +import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon"; export default function TabLayout() { const { mutateAsync: signOut } = useSignOut(); @@ -38,7 +43,7 @@ export default function TabLayout() { return ( - Welcome to Kali + Welcome to Cally props.navigation.navigate("calendar")} - icon={ - - } + icon={} /> props.navigation.navigate("grocery")} - icon={ - - } + icon={} /> @@ -90,19 +83,17 @@ export default function TabLayout() { />*/} props.navigation.navigate("todos")} - icon={ - - } + icon={} /> props.navigation.navigate("brain_dump")} - icon={} + icon={} /> {/* signOut()} />*/} @@ -120,7 +111,7 @@ export default function TabLayout() { centerV centerH > - + )} backgroundColor="white" @@ -142,7 +133,7 @@ export default function TabLayout() { borderWidth: 2, borderColor: "#fd1775", }} - label="Sign out of Kali" + label="Sign out of Cally" color="#fd1775" onPress={() => signOut()} /> @@ -197,7 +188,7 @@ export default function TabLayout() { name="todos" options={{ drawerLabel: "To-Do", - title: "To-Do", + title: "To-Do's", }} /> diff --git a/app/_layout.tsx b/app/_layout.tsx index 47d1e50..1b0128e 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,30 +1,16 @@ -import {DarkTheme, DefaultTheme, ThemeProvider} from '@react-navigation/native'; +import {DefaultTheme, ThemeProvider} from '@react-navigation/native'; import {useFonts} from 'expo-font'; import {Stack} from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; import {useEffect} from 'react'; import 'react-native-reanimated'; - -import {useColorScheme} from '@/hooks/useColorScheme'; import {AuthContextProvider} from "@/contexts/AuthContext"; - -import functions from "@react-native-firebase/functions"; -import firestore from "@react-native-firebase/firestore"; -import auth from "@react-native-firebase/auth"; import {QueryClient, QueryClientProvider} from "react-query"; import {Toast} from "react-native-ui-lib"; -// Prevent the splash screen from auto-hiding before asset loading is complete. SplashScreen.preventAutoHideAsync(); -const queryClient = new QueryClient() - - - if (__DEV__) { - functions().useEmulator('localhost', 5001); - firestore().useEmulator("localhost", 5471); - auth().useEmulator("http://localhost:9099"); - } +const queryClient = new QueryClient(); export default function RootLayout() { const [loaded] = useFonts({ @@ -50,7 +36,7 @@ export default function RootLayout() { - + diff --git a/assets/svgs/CalendarIcon.tsx b/assets/svgs/CalendarIcon.tsx new file mode 100644 index 0000000..a688729 --- /dev/null +++ b/assets/svgs/CalendarIcon.tsx @@ -0,0 +1,19 @@ +import * as React from "react" +import Svg, { Path, SvgProps } from "react-native-svg" +const CalendarIcon: React.FC = (props) => ( + + + +) +export default CalendarIcon diff --git a/assets/svgs/NavBrainDumpIcon.tsx b/assets/svgs/NavBrainDumpIcon.tsx new file mode 100644 index 0000000..ea4aa72 --- /dev/null +++ b/assets/svgs/NavBrainDumpIcon.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import Svg, { Path, SvgProps } from "react-native-svg" +const NavBrainDumpIcon: React.FC = (props) => ( + + + + + +) +export default NavBrainDumpIcon diff --git a/assets/svgs/NavCalendarIcon.tsx b/assets/svgs/NavCalendarIcon.tsx new file mode 100644 index 0000000..19ea7d8 --- /dev/null +++ b/assets/svgs/NavCalendarIcon.tsx @@ -0,0 +1,19 @@ +import * as React from "react" +import Svg, { Path, SvgProps } from "react-native-svg" +const NavCalendarIcon: React.FC = (props) => ( + + + +) +export default NavCalendarIcon diff --git a/assets/svgs/NavGroceryIcon.tsx b/assets/svgs/NavGroceryIcon.tsx new file mode 100644 index 0000000..cb219b8 --- /dev/null +++ b/assets/svgs/NavGroceryIcon.tsx @@ -0,0 +1,20 @@ +import * as React from "react"; +import Svg, { Path, SvgProps } from "react-native-svg"; +const NavGroceryIcon: React.FC = (props) => ( + + + + +); +export default NavGroceryIcon; diff --git a/assets/svgs/NavSettingsIcon.tsx b/assets/svgs/NavSettingsIcon.tsx new file mode 100644 index 0000000..8027111 --- /dev/null +++ b/assets/svgs/NavSettingsIcon.tsx @@ -0,0 +1,28 @@ +import * as React from "react"; +import Svg, { Path, SvgProps } from "react-native-svg"; +const NavSettingsIcon: React.FC = (props) => ( + + + + +); +export default NavSettingsIcon; diff --git a/assets/svgs/NavToDosIcon.tsx b/assets/svgs/NavToDosIcon.tsx new file mode 100644 index 0000000..2cbb70a --- /dev/null +++ b/assets/svgs/NavToDosIcon.tsx @@ -0,0 +1,18 @@ +import * as React from "react" +import Svg, { Path, SvgProps } from "react-native-svg" +const NavToDosIcon: React.FC = (props) => ( + + + +) +export default NavToDosIcon diff --git a/assets/svgs/PrivacyPolicyIcon.tsx b/assets/svgs/PrivacyPolicyIcon.tsx new file mode 100644 index 0000000..f0ed5db --- /dev/null +++ b/assets/svgs/PrivacyPolicyIcon.tsx @@ -0,0 +1,23 @@ +import * as React from "react" +import Svg, { Path, SvgProps } from "react-native-svg" +const PrivacyPolicyIcon: React.FC = (props) => ( + + + + +) +export default PrivacyPolicyIcon diff --git a/assets/svgs/ProfileIcon.tsx b/assets/svgs/ProfileIcon.tsx new file mode 100644 index 0000000..7ba0c76 --- /dev/null +++ b/assets/svgs/ProfileIcon.tsx @@ -0,0 +1,26 @@ +import * as React from "react" +import Svg, { Path, SvgProps } from "react-native-svg" +const ProfileIcon: React.FC = (props) => ( + + + + +) +export default ProfileIcon diff --git a/calendar-integration/google-calendar-utils.js b/calendar-integration/google-calendar-utils.js index 428cb42..c7d332c 100644 --- a/calendar-integration/google-calendar-utils.js +++ b/calendar-integration/google-calendar-utils.js @@ -1,4 +1,5 @@ export async function fetchGoogleCalendarEvents(token, startDate, endDate) { + console.log(token); const response = await fetch( `https://www.googleapis.com/calendar/v3/calendars/primary/events?single_events=true&time_min=${startDate}&time_max=${endDate}`, { @@ -9,6 +10,7 @@ export async function fetchGoogleCalendarEvents(token, startDate, endDate) { ); const data = await response.json(); + console.log(data); const googleEvents = []; data.items?.forEach((item) => { let isAllDay = false; diff --git a/components/pages/calendar/CalendarPage.tsx b/components/pages/calendar/CalendarPage.tsx index 7f1c6aa..fc34398 100644 --- a/components/pages/calendar/CalendarPage.tsx +++ b/components/pages/calendar/CalendarPage.tsx @@ -47,7 +47,7 @@ export default function CalendarPage() { }, }); - const [isFamilyView, setIsFamilyView] = useState(true); + const [isFamilyView, setIsFamilyView] = useState(false); const [calendarHeight, setCalendarHeight] = useState(0); const [mode, setMode] = useState<"week" | "month" | "day">("week"); const [selectedDate, setSelectedDate] = useState(new Date()); diff --git a/components/pages/calendar/EditEventDialog.tsx b/components/pages/calendar/EditEventDialog.tsx index fddeb91..2bd7282 100644 --- a/components/pages/calendar/EditEventDialog.tsx +++ b/components/pages/calendar/EditEventDialog.tsx @@ -1,7 +1,5 @@ import { View, Text, Button, Switch } from "react-native-ui-lib"; import React, { useEffect, useState } from "react"; -import PointsSlider from "@/components/shared/PointsSlider"; -import { repeatOptions, useToDosContext } from "@/contexts/ToDosContext"; import { Feather, AntDesign, Ionicons } from "@expo/vector-icons"; import { Dialog, @@ -13,11 +11,11 @@ import { import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView"; import { StyleSheet } from "react-native"; import DropModalIcon from "@/assets/svgs/DropModalIcon"; -import { CalendarEvent, useCalendarContext } from "@/contexts/CalendarContext"; +import { CalendarEvent } from "@/contexts/CalendarContext"; import ClockIcon from "@/assets/svgs/ClockIcon"; import LockIcon from "@/assets/svgs/LockIcon"; import MenuIcon from "@/assets/svgs/MenuIcon"; -import { eventCellCss } from "react-native-big-calendar"; +import { useUpdateEvent } from "@/hooks/firebase/useUpdateEvent"; interface IEditEventDialog { event: CalendarEvent; @@ -25,9 +23,10 @@ interface IEditEventDialog { setIsVisible: (value: boolean) => void; } const EditEventDialog = (editEventProps: IEditEventDialog) => { - const { updateEvent } = useCalendarContext(); const [event, setEvent] = useState(editEventProps.event); + const { mutateAsync: updateEvent } = useUpdateEvent(); + useEffect(() => { setEvent(editEventProps.event); }, [editEventProps.isVisible]); @@ -72,8 +71,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => { onPress={() => { try { if (event.id) { - updateEvent(event, event.id); - editEventProps.setIsVisible(false); + updateEvent(event).then(() => editEventProps.setIsVisible(false)); } } catch (error) { console.error(error); diff --git a/components/pages/calendar/ManuallyAddEventModal.tsx b/components/pages/calendar/ManuallyAddEventModal.tsx index d1cb54c..b0db46b 100644 --- a/components/pages/calendar/ManuallyAddEventModal.tsx +++ b/components/pages/calendar/ManuallyAddEventModal.tsx @@ -81,8 +81,9 @@ export const ManuallyAddEventModal = ({ const { mutateAsync: createEvent, isLoading, isError } = useCreateEvent(); - const formatDateTime = (date: Date) => { - return date.toLocaleDateString("en-US", { + const formatDateTime = (date?: Date | string) => { + if(!date) return undefined + return new Date(date).toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric", @@ -98,7 +99,7 @@ export const ManuallyAddEventModal = ({ return combined; }; - const handleSave = () => { + const handleSave = async () => { let finalStartDate: Date; let finalEndDate: Date; @@ -113,15 +114,14 @@ export const ManuallyAddEventModal = ({ finalEndDate = combineDateAndTime(endDate, endTime); } - const eventData: CalendarEvent = { + const eventData: Partial = { title: title, - start: finalStartDate, - end: finalEndDate, + startDate: finalStartDate, + endDate: finalEndDate, allDay: isAllDay, - private: isPrivate, }; - addEvent(eventData); + await createEvent(eventData); close(); }; diff --git a/components/pages/grocery/EditGroceryFrequency.tsx b/components/pages/grocery/EditGroceryFrequency.tsx index 83a542b..59003fb 100644 --- a/components/pages/grocery/EditGroceryFrequency.tsx +++ b/components/pages/grocery/EditGroceryFrequency.tsx @@ -1,5 +1,5 @@ import { StyleSheet } from "react-native"; -import React, { useState } from "react"; +import React from "react"; import { Dialog, Text, @@ -11,9 +11,9 @@ import { } from "react-native-ui-lib"; import { GroceryFrequency, - IGrocery, useGroceryContext, } from "@/contexts/GroceryContext"; +import { IGrocery } from "@/hooks/firebase/types/groceryData"; interface EditGroceryFrequencyProps { visible: boolean; onClose: () => void; @@ -42,7 +42,7 @@ const EditGroceryFrequency = (props: EditGroceryFrequencyProps) => { - updateGroceryItem(props.item.id, { recurring: value }) + updateGroceryItem({id: props.item.id, recurring: value}) } onColor={"lime"} /> @@ -56,8 +56,9 @@ const EditGroceryFrequency = (props: EditGroceryFrequencyProps) => { const selectedFrequency = GroceryFrequency[item as keyof typeof GroceryFrequency]; if (selectedFrequency) { - updateGroceryItem(props.item.id, { - frequency: selectedFrequency, + updateGroceryItem({ + id: props.item.id, + frequency: selectedFrequency, }); } else { console.error("Invalid frequency selected"); diff --git a/components/pages/grocery/GroceryItem.tsx b/components/pages/grocery/GroceryItem.tsx index 97e5ade..893d835 100644 --- a/components/pages/grocery/GroceryItem.tsx +++ b/components/pages/grocery/GroceryItem.tsx @@ -1,53 +1,58 @@ -import { - View, - Text, - Button, - TouchableOpacity, - Checkbox, - ButtonSize, -} from "react-native-ui-lib"; -import React, { useEffect, useState } from "react"; -import { ProfileType, useAuthContext } from "@/contexts/AuthContext"; -import { MaterialCommunityIcons, AntDesign } from "@expo/vector-icons"; -import { ListItem } from "react-native-ui-lib"; -import { - GroceryCategory, - IGrocery, - useGroceryContext, -} from "@/contexts/GroceryContext"; +import {Checkbox, Text, TouchableOpacity, View,} from "react-native-ui-lib"; +import React, {useEffect, useState} from "react"; +import {AntDesign} from "@expo/vector-icons"; +import {GroceryCategory, useGroceryContext,} from "@/contexts/GroceryContext"; import EditGroceryFrequency from "./EditGroceryFrequency"; import EditGroceryItem from "./EditGroceryItem"; -import { StyleSheet } from "react-native"; +import {StyleSheet} from "react-native"; +import {IGrocery} from "@/hooks/firebase/types/groceryData"; +import firestore from "@react-native-firebase/firestore"; +import {UserProfile} from "@/hooks/firebase/types/profileTypes"; const GroceryItem = ({ item, handleItemApproved, }: { item: IGrocery; - handleItemApproved: (id: number, changes: Partial) => void; + handleItemApproved: (id: string, changes: Partial) => void; }) => { - const { updateGroceryItem, groceries } = useGroceryContext(); + const { updateGroceryItem } = useGroceryContext(); - const { profileType } = useAuthContext(); const [openFreqEdit, setOpenFreqEdit] = useState(false); const [isEditingTitle, setIsEditingTitle] = useState(false); const [newTitle, setNewTitle] = useState(""); const [category, setCategory] = useState( GroceryCategory.None ); + const [itemCreator, setItemCreator] = useState(null); const handleTitleChange = (newTitle: string) => { - updateGroceryItem(item.id, { title: newTitle }); + updateGroceryItem({id: item?.id, title: newTitle}); }; const handleCategoryChange = (newCategory: GroceryCategory) => { - updateGroceryItem(item.id, { category: newCategory }); + updateGroceryItem({id: item?.id, category: newCategory}); }; useEffect(() => { setNewTitle(item.title); + + getItemCreator(item?.creatorId); }, []); + const getItemCreator = async (uid: string | undefined) => { + if (uid) { + const documentSnapshot = await firestore() + .collection("Profiles") + .doc(uid) + .get(); + + if (documentSnapshot.exists) { + setItemCreator(documentSnapshot.data() as UserProfile) + } + } + } + return ( - updateGroceryItem(item.id, { bought: !item.bought }) + updateGroceryItem({ id: item.id, bought: !item.bought }) } /> )} @@ -135,7 +140,7 @@ const GroceryItem = ({ }} > - Requested by Austin + Requested by {itemCreator?.firstName} diff --git a/components/pages/grocery/GroceryList.tsx b/components/pages/grocery/GroceryList.tsx index 3d69679..35adb0a 100644 --- a/components/pages/grocery/GroceryList.tsx +++ b/components/pages/grocery/GroceryList.tsx @@ -1,18 +1,13 @@ -import { FlatList, StyleSheet } from "react-native"; -import React, { RefObject, useEffect, useState } from "react"; -import { View, Text, ListItem, Button, TextField, TextFieldRef } from "react-native-ui-lib"; +import {FlatList, StyleSheet} from "react-native"; +import React, {useEffect, useState} from "react"; +import {Button, Text, View} from "react-native-ui-lib"; import GroceryItem from "./GroceryItem"; -import { - IGrocery, - GroceryCategory, - GroceryFrequency, - useGroceryContext, -} from "@/contexts/GroceryContext"; +import {GroceryCategory, GroceryFrequency, useGroceryContext,} from "@/contexts/GroceryContext"; import HeaderTemplate from "@/components/shared/HeaderTemplate"; -import CategoryDropdown from "./CategoryDropdown"; -import { AntDesign, MaterialIcons } from "@expo/vector-icons"; +import {AntDesign, MaterialIcons} from "@expo/vector-icons"; import EditGroceryItem from "./EditGroceryItem"; -import { ProfileType, useAuthContext } from "@/contexts/AuthContext"; +import {ProfileType, useAuthContext} from "@/contexts/AuthContext"; +import {IGrocery} from "@/hooks/firebase/types/groceryData"; const GroceryList = () => { const { @@ -24,10 +19,10 @@ const GroceryList = () => { } = useGroceryContext(); const { profileData } = useAuthContext(); const [approvedGroceries, setapprovedGroceries] = useState( - groceries.filter((item) => item.approved === true) + groceries?.filter((item) => item.approved === true) ); const [pendingGroceries, setPendingGroceries] = useState( - groceries.filter((item) => item.approved !== true) + groceries?.filter((item) => item.approved !== true) ); const [category, setCategory] = useState( GroceryCategory.Bakery @@ -39,7 +34,7 @@ const GroceryList = () => { const [approvedVisible, setApprovedVisible] = useState(true); // Group approved groceries by category - const approvedGroceriesByCategory = approvedGroceries.reduce( + const approvedGroceriesByCategory = approvedGroceries?.reduce( (groups: any, item: IGrocery) => { const category = item.category || "Uncategorized"; if (!groups[category]) { @@ -55,10 +50,10 @@ const GroceryList = () => { if (submit) { if (title?.length > 2 && title?.length <= 25) { addGrocery({ - id: 0, + id: "", title: title, category: category, - approved: profileData?.userType === ProfileType.PARENT ? true : false, + approved: profileData?.userType === ProfileType.PARENT, recurring: false, frequency: GroceryFrequency.Never, bought: false, @@ -76,8 +71,8 @@ const GroceryList = () => { }, [category]); useEffect(() => { - setapprovedGroceries(groceries.filter((item) => item.approved === true)); - setPendingGroceries(groceries.filter((item) => item.approved !== true)); + setapprovedGroceries(groceries?.filter((item) => item.approved === true)); + setPendingGroceries(groceries?.filter((item) => item.approved !== true)); }, [groceries]); return ( @@ -93,8 +88,8 @@ const GroceryList = () => { style={{ borderRadius: 50 }} > - {approvedGroceries.length} list{" "} - {approvedGroceries.length === 1 ? ( + {approvedGroceries?.length} list{" "} + {approvedGroceries?.length === 1 ? ( item @@ -111,7 +106,7 @@ const GroceryList = () => { style={{ borderRadius: 50 }} > - {pendingGroceries.length} pending + {pendingGroceries?.length} pending diff --git a/components/pages/todos/AddChoreDialog.tsx b/components/pages/todos/AddChoreDialog.tsx index 7b34109..e38180d 100644 --- a/components/pages/todos/AddChoreDialog.tsx +++ b/components/pages/todos/AddChoreDialog.tsx @@ -1,5 +1,5 @@ import { View, Text, Button, Switch } from "react-native-ui-lib"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import PointsSlider from "@/components/shared/PointsSlider"; import { repeatOptions, useToDosContext } from "@/contexts/ToDosContext"; import { Feather, AntDesign, Ionicons } from "@expo/vector-icons"; @@ -27,6 +27,15 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => { const [rotate, setRotate] = useState(false); const [repeatType, setRepeatType] = useState("Every week"); + const handleCLose = () => { + setNewTitle(""); + setPoints(10); + setChoreDate(new Date()); + setRotate(false); + setRepeatType("every week"); + addChoreDialogProps.setIsVisible(false); + }; + const handleChange = (text: string) => { const numericValue = parseInt(text, 10); @@ -41,7 +50,7 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => { bottom={true} height={"90%"} panDirection={PanningDirectionsEnum.DOWN} - onDismiss={() => addChoreDialogProps.setIsVisible(false)} + onDismiss={() => handleCLose} containerStyle={{ borderRadius: 10, backgroundColor: "white", @@ -59,13 +68,13 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => { style={styles.topBtn} label="Cancel" onPress={() => { - addChoreDialogProps.setIsVisible(false); + handleCLose(); }} /> { - addChoreDialogProps.setIsVisible(false); + handleCLose() }} /> @@ -84,7 +93,7 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => { rotate: rotate, repeatType: repeatType, }); - addChoreDialogProps.setIsVisible(false); + handleCLose(); console.log(toDos); } catch (error) { console.error(error); diff --git a/components/pages/todos/ToDosList.tsx b/components/pages/todos/ToDosList.tsx index 1571c3f..5cb8d30 100644 --- a/components/pages/todos/ToDosList.tsx +++ b/components/pages/todos/ToDosList.tsx @@ -1,8 +1,10 @@ -import { View, Text } from "react-native-ui-lib"; -import React from "react"; +import { View, Text, TouchableOpacity, Icon } from "react-native-ui-lib"; +import React, { useState } from "react"; import { IToDo, useToDosContext } from "@/contexts/ToDosContext"; import ToDoItem from "./ToDoItem"; -import { format, isToday, isTomorrow } from "date-fns"; +import { format, isToday, isTomorrow } from "date-fns"; +import DropModalIcon from "@/assets/svgs/DropModalIcon"; +import { AntDesign } from "@expo/vector-icons"; const groupToDosByDate = (toDos: IToDo[]) => { return toDos.reduce((groups, toDo) => { @@ -31,23 +33,53 @@ const ToDosList = () => { const { toDos } = useToDosContext(); const groupedToDos = groupToDosByDate(toDos); + const [expandedGroups, setExpandedGroups] = useState<{ + [key: string]: boolean; + }>({}); + + const [expandNoDate, setExpandNoDate] = useState(true); + + const toggleExpand = (dateKey: string) => { + setExpandedGroups((prev) => ({ + ...prev, + [dateKey]: !prev[dateKey], + })); + }; + const noDateToDos = groupedToDos["No Date"] || []; - const datedToDos = Object.keys(groupedToDos).filter((key) => key !== "No Date"); + const datedToDos = Object.keys(groupedToDos).filter( + (key) => key !== "No Date" + ); return ( - - + {noDateToDos.length > 0 && ( - {noDateToDos - .sort((a, b) => Number(a.done) - Number(b.done)) - .map((item) => ( - - ))} + + + Unscheduled + + {!expandNoDate && ( + {setExpandNoDate(!expandNoDate)}}/> + )} + {expandNoDate && ( + {setExpandNoDate(!expandNoDate)}}/> + )} + + {expandNoDate && + noDateToDos + .sort((a, b) => Number(a.done) - Number(b.done)) + .map((item) => )} )} {datedToDos.map((dateKey) => { + const isExpanded = expandedGroups[dateKey] || false; const sortedToDos = [ ...groupedToDos[dateKey].filter((toDo) => !toDo.done), ...groupedToDos[dateKey].filter((toDo) => toDo.done), @@ -55,19 +87,34 @@ const ToDosList = () => { return ( - toggleExpand(dateKey)} style={{ - fontWeight: "bold", - marginVertical: 8, + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", paddingHorizontal: 20, + marginVertical: 8, }} > - {dateKey} - - {sortedToDos.map((item) => ( - - ))} + + {dateKey} + + {!isExpanded && ( + + )} + {isExpanded && ( + + )} + + + {isExpanded && + sortedToDos.map((item) => )} ); })} diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx index a679f90..6b01656 100644 --- a/contexts/AuthContext.tsx +++ b/contexts/AuthContext.tsx @@ -5,6 +5,11 @@ import {useRouter} from "expo-router"; import firestore from "@react-native-firebase/firestore"; import {UserProfile} from "@/hooks/firebase/types/profileTypes"; +import * as Notifications from 'expo-notifications'; +import * as Device from 'expo-device'; +import Constants from 'expo-constants'; +import { Platform } from 'react-native'; + export enum ProfileType { "PARENT" = "parent", "CHILD" = "child", @@ -19,48 +24,118 @@ interface IAuthContext { refreshProfileData: () => Promise } +Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: true, + shouldSetBadge: true, + }), +}); + +Notifications.addNotificationReceivedListener(notification => { + console.log('Notification received:', notification); +}); + +async function registerForPushNotificationsAsync() { + if (Platform.OS === 'android') { + await Notifications.setNotificationChannelAsync('default', { + name: 'default', + importance: Notifications.AndroidImportance.MAX, + vibrationPattern: [0, 250, 250, 250], + lightColor: '#FF231F7C', + }); + } + + if (Device.isDevice) { + const { status: existingStatus } = await Notifications.getPermissionsAsync(); + let finalStatus = existingStatus; + + if (existingStatus !== 'granted') { + const { status } = await Notifications.requestPermissionsAsync(); + finalStatus = status; + } + + if (finalStatus !== 'granted') { + alert('Failed to get push token for push notification!'); + return; + } + + const projectId = + Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId; + + if (!projectId) { + alert('Project ID not found'); + return; + } + + try { + const token = (await Notifications.getExpoPushTokenAsync({ projectId })).data; + console.log('Push Token:', token); + return token; + } catch (error) { + alert(`Error getting push token: ${error}`); + throw error; + } + } else { + alert('Must use a physical device for push notifications'); + } +} + const AuthContext = createContext(undefined!) export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) => { - const [user, setUser] = useState(null) + const [user, setUser] = useState(null); const [initializing, setInitializing] = useState(true); const [profileType, setProfileType] = useState(undefined); const [profileData, setProfileData] = useState(undefined); + const {replace} = useRouter(); + const ready = !initializing; - const {replace} = useRouter() - const ready = !initializing + const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => { + setUser(authUser); - const onAuthStateChanged = async (user: FirebaseAuthTypes.User | null) => { - setUser(user); - - if (user) { - await refreshProfileData(); + if (authUser) { + await refreshProfileData(authUser); + const pushToken = await registerForPushNotificationsAsync(); + if (pushToken) { + await savePushTokenToFirestore(authUser.uid, pushToken); + } } if (initializing) setInitializing(false); - } + }; - const refreshProfileData = async () => { - if (user) { + const refreshProfileData = async (user?: FirebaseAuthTypes.User) => { + const authUser = user ?? auth().currentUser + if (authUser) { try { const documentSnapshot = await firestore() .collection("Profiles") - .doc(user.uid) + .doc(authUser.uid) .get(); if (documentSnapshot.exists) { setProfileType(documentSnapshot.data()?.userType); setProfileData(documentSnapshot.data() as UserProfile); } } catch (error) { - console.error("Error fetching user profile data:", error); setProfileType(undefined); setProfileData(undefined); } } }; + const savePushTokenToFirestore = async (uid: string, token: string) => { + try { + await firestore().collection("Profiles").doc(uid).update({ + pushToken: token, + }); + } catch (error) { + console.error('Error saving push token to Firestore:', error); + } + }; + useEffect(() => { - const subscriber = auth().onAuthStateChanged(onAuthStateChanged); + const subscriber = auth().onAuthStateChanged(onAuthStateChangedHandler); return subscriber; }, []); @@ -72,9 +147,9 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) => useEffect(() => { if (ready && user) { - replace({pathname: "/(auth)/calendar"}) + replace({pathname: "/(auth)/calendar"}); } else if (ready && !user) { - replace({pathname: "/(unauth)"}) + replace({pathname: "/(unauth)"}); } }, [user, ready]); @@ -86,7 +161,8 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) => {children} - ) -} + ); +}; + export const useAuthContext = () => useContext(AuthContext)!; diff --git a/contexts/CalendarContext.tsx b/contexts/CalendarContext.tsx index 4e5551b..b084caa 100644 --- a/contexts/CalendarContext.tsx +++ b/contexts/CalendarContext.tsx @@ -3,7 +3,7 @@ import React, { createContext, useContext, useState, ReactNode } from "react"; // Define the CalendarEvent interface export interface CalendarEvent { - id?: number; // Unique identifier for the event + id?: number | string; // Unique identifier for the event user?: string; title: string; // Event title or name description?: string; // Optional description for the event diff --git a/contexts/GroceryContext.tsx b/contexts/GroceryContext.tsx index 5f4c752..3299e0e 100644 --- a/contexts/GroceryContext.tsx +++ b/contexts/GroceryContext.tsx @@ -1,6 +1,9 @@ -import { MaterialCommunityIcons } from "@expo/vector-icons"; -import { createContext, useContext, useState } from "react"; +import {createContext, useContext, useState} from "react"; import fuzzysort from "fuzzysort"; +import {useCreateGrocery} from "@/hooks/firebase/useCreateGrocery"; +import {IGrocery} from "@/hooks/firebase/types/groceryData"; +import {useUpdateGrocery} from "@/hooks/firebase/useUpdateGrocery"; +import {useGetGroceries} from "@/hooks/firebase/useGetGroceries"; export enum GroceryFrequency { Never = "Never", @@ -11,16 +14,6 @@ export enum GroceryFrequency { Quarterly = "Quarterly", } -export interface IGrocery { - id: number; - title: string; - category: GroceryCategory; - approved: boolean; - recurring: boolean; - frequency: GroceryFrequency; - bought: boolean; -} - export enum GroceryCategory { Fruit = "Fruit", Dairy = "Dairy", @@ -52,10 +45,136 @@ const iconMapping: { [key in GroceryCategory]: MaterialIconNames } = { [GroceryCategory.Frozen]: "snowflake", };*/ +const initialGroceryList = [ + { + id: 0, + title: "Carrots", + category: GroceryCategory.Vegetables, + approved: false, + bought: false, + recurring: false, + frequency: GroceryFrequency.Never, + }, + { + id: 1, + title: "Steak", + category: GroceryCategory.Meat, + approved: true, + bought: false, + recurring: false, + frequency: GroceryFrequency.Never, + }, + { + id: 2, + title: "Chicken Breast", + category: GroceryCategory.Poultry, + approved: true, + bought: false, + recurring: false, + frequency: GroceryFrequency.Never, + }, + { + id: 3, + title: "Greek Yoghurt", + category: GroceryCategory.Dairy, + approved: false, + bought: false, + recurring: false, + frequency: GroceryFrequency.Never, + }, +]; + +const groceryExamples = { + [GroceryCategory.Fruit]: [ + 'apple', 'apples', 'banana', 'bananas', 'orange', 'oranges', 'grape', 'grapes', + 'pear', 'pears', 'pineapple', 'pineapples', 'kiwi', 'kiwis', 'strawberry', + 'strawberries', 'blueberry', 'blueberries', 'mango', 'mangoes', 'watermelon', + 'watermelons', 'peach', 'peaches', 'plum', 'plums', 'cherry', 'cherries', + 'raspberry', 'raspberries', 'blackberry', 'blackberries', 'pomegranate', + 'pomegranates', 'lemon', 'lemons', 'lime', 'limes', 'coconut', 'coconuts' + ], + [GroceryCategory.Dairy]: [ + 'milk', 'whole milk', 'skim milk', 'almond milk', 'soy milk', 'cheese', + 'cheeses', 'yoghurt', 'yogurt', 'greek yoghurt', 'greek yogurt', 'butter', + 'margarine', 'cream', 'whipping cream', 'heavy cream', 'ice cream', 'ice creams', + 'sour cream', 'whipped cream', 'cream cheese', 'cream cheeses', 'buttermilk', + 'cottage cheese', 'ghee', 'kefir' + ], + [GroceryCategory.Vegetables]: [ + 'carrot', 'carrots', 'broccoli', 'lettuce', 'lettuces', 'spinach', 'kale', + 'cabbage', 'cabbages', 'cauliflower', 'zucchini', 'zucchinis', 'onion', 'onions', + 'garlic', 'pepper', 'peppers', 'bell pepper', 'bell peppers', 'tomato', 'tomatoes', + 'cucumber', 'cucumbers', 'potato', 'potatoes', 'sweet potato', 'sweet potatoes', + 'beet', 'beets', 'eggplant', 'eggplants', 'celery', 'radish', 'radishes', + 'asparagus', 'mushroom', 'mushrooms' + ], + [GroceryCategory.Meat]: [ + 'steak', 'steaks', 'beef', 'pork', 'lamb', 'veal', 'ribeye', 'tenderloin', + 'filet mignon', 'bacon', 'ham', 'sausage', 'sausages', 'salami', 'ground beef', + 'beef mince', 'hamburger meat', 'prosciutto', 'brisket', 'pork chop', 'pork chops' + ], + [GroceryCategory.Poultry]: [ + 'chicken', 'whole chicken', 'chickens', 'chicken breast', 'chicken breasts', + 'breast of chicken', 'chicken thigh', 'chicken thighs', 'chicken leg', 'chicken legs', + 'chicken wing', 'chicken wings', 'wing', 'wings', 'drumsticks', 'chicken drumstick', + 'turkey', 'whole turkey', 'ground turkey', 'turkey breast', 'turkey breasts', + 'turkey leg', 'duck', 'ducks', 'goose', 'quail', 'pheasant' + ], + [GroceryCategory.Bakery]: [ + 'bread', 'breads', 'whole wheat bread', 'sourdough bread', 'bagel', 'bagels', + 'croissant', 'croissants', 'muffin', 'muffins', 'buns', 'hamburger bun', + 'hamburger buns', 'hotdog bun', 'hotdog buns', 'donut', 'donuts', 'roll', 'rolls', + 'dinner roll', 'dinner rolls', 'scone', 'scones', 'toast', 'ciabatta', + 'focaccia', 'brioche', 'pita', 'pitas', 'naan', 'baguette', 'baguettes', + 'pastry', 'pastries', 'pretzel', 'pretzels' + ], + [GroceryCategory.Beverages]: [ + 'coffee', 'decaf coffee', 'iced coffee', 'cold brew', 'tea', 'iced tea', + 'black tea', 'green tea', 'herbal tea', 'juice', 'apple juice', 'orange juice', + 'grape juice', 'water', 'sparkling water', 'flavored water', 'soda', 'cola', + 'diet soda', 'beer', 'lager', 'ale', 'wine', 'red wine', 'white wine', + 'whiskey', 'whisky', 'vodka', 'rum', 'gin', 'smoothie', 'smoothies', 'milkshake', + 'milkshakes', 'energy drink', 'energy drinks', 'sports drink', 'sports drinks', + 'lemonade', 'sparkling lemonade', 'iced lemonade', 'sparkling water', 'cider', + 'hard cider', 'kombucha' + ], + [GroceryCategory.Snacks]: [ + 'chips', 'potato chips', 'tortilla chips', 'corn chips', 'candy', 'candies', + 'chocolate', 'chocolates', 'dark chocolate', 'milk chocolate', 'white chocolate', + 'cookies', 'popcorn', 'pretzel', 'pretzels', 'nuts', 'mixed nuts', 'almonds', + 'cashews', 'trail mix', 'granola bar', 'granola bars', 'protein bar', 'protein bars', + 'crackers', 'gummies', 'gummy bears', 'fruit snacks', 'dried fruit', + 'peanut butter', 'rice cakes', 'snack cakes' + ], + [GroceryCategory.Household]: [ + 'detergent', 'laundry detergent', 'dish soap', 'dishwasher detergent', + 'toilet paper', 'paper towels', 'trash bags', 'bin liners', 'broom', 'mop', + 'cleaner', 'surface cleaner', 'multi-purpose cleaner', 'disinfectant', 'bleach', + 'fabric softener', 'vacuum bags', 'aluminum foil', 'plastic wrap', 'cling wrap', + 'light bulbs', 'batteries', 'laundry soap', 'sponges', 'scrubbers', + 'dishwashing liquid' + ], + [GroceryCategory.PersonalCare]: [ + 'shampoo', 'conditioner', 'hair shampoo', 'hair conditioner', 'soap', 'bar soap', + 'liquid soap', 'toothpaste', 'toothbrush', 'toothbrushes', 'electric toothbrush', + 'deodorant', 'antiperspirant', 'lotion', 'moisturizer', 'razor', 'shaving razor', + 'shaving cream', 'body wash', 'face wash', 'sunscreen', 'hair gel', 'hair spray', + 'nail polish', 'cotton swabs', 'lip balm', 'chapstick', 'hand cream', 'sanitizer' + ], + [GroceryCategory.Frozen]: [ + 'ice cream', 'ice creams', 'frozen pizza', 'frozen pizzas', 'frozen vegetables', + 'frozen veg', 'frozen peas', 'frozen fruit', 'frozen fish', 'frozen chicken', + 'frozen wings', 'frozen fries', 'frozen french fries', 'frozen shrimp', + 'frozen prawns', 'frozen dumplings', 'frozen waffles', 'frozen pancakes', + 'frozen pie', 'frozen pies', 'frozen lasagna', 'frozen burrito', 'frozen burritos', + 'frozen nuggets', 'frozen pastry', 'frozen pastries', 'frozen meals' + ], +}; + interface IGroceryContext { groceries: IGrocery[]; //iconMapping: { [key in GroceryCategory]: MaterialIconNames }; - updateGroceryItem: (id: number, changes: Partial) => void; + updateGroceryItem: (changes: Partial) => void; isAddingGrocery: boolean; setIsAddingGrocery: (value: boolean) => void; addGrocery: (grocery: IGrocery) => void; @@ -68,142 +187,14 @@ export const GroceryProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { const [isAddingGrocery, setIsAddingGrocery] = useState(false); - const [groceries, setGroceries] = useState([ - { - id: 0, - title: "Carrots", - category: GroceryCategory.Vegetables, - approved: false, - bought: false, - recurring: false, - frequency: GroceryFrequency.Never, - }, - { - id: 1, - title: "Steak", - category: GroceryCategory.Meat, - approved: true, - bought: false, - recurring: false, - frequency: GroceryFrequency.Never, - }, - { - id: 2, - title: "Chicken Breast", - category: GroceryCategory.Poultry, - approved: true, - bought: false, - recurring: false, - frequency: GroceryFrequency.Never, - }, - { - id: 3, - title: "Greek Yoghurt", - category: GroceryCategory.Dairy, - approved: false, - bought: false, - recurring: false, - frequency: GroceryFrequency.Never, - }, - ]); + + const { mutateAsync: createGrocery } = useCreateGrocery(); + const { mutateAsync: updateGrocery } = useUpdateGrocery(); + const { data: groceries } = useGetGroceries(); + const addGrocery = (grocery: IGrocery) => { - setGroceries((prevGroceries) => [ - ...prevGroceries, - { - ...grocery, - id: prevGroceries.length - ? prevGroceries[prevGroceries.length - 1].id + 1 - : 0, - }, - ]); - }; - - const groceryExamples = { - [GroceryCategory.Fruit]: [ - 'apple', 'apples', 'banana', 'bananas', 'orange', 'oranges', 'grape', 'grapes', - 'pear', 'pears', 'pineapple', 'pineapples', 'kiwi', 'kiwis', 'strawberry', - 'strawberries', 'blueberry', 'blueberries', 'mango', 'mangoes', 'watermelon', - 'watermelons', 'peach', 'peaches', 'plum', 'plums', 'cherry', 'cherries', - 'raspberry', 'raspberries', 'blackberry', 'blackberries', 'pomegranate', - 'pomegranates', 'lemon', 'lemons', 'lime', 'limes', 'coconut', 'coconuts' - ], - [GroceryCategory.Dairy]: [ - 'milk', 'whole milk', 'skim milk', 'almond milk', 'soy milk', 'cheese', - 'cheeses', 'yoghurt', 'yogurt', 'greek yoghurt', 'greek yogurt', 'butter', - 'margarine', 'cream', 'whipping cream', 'heavy cream', 'ice cream', 'ice creams', - 'sour cream', 'whipped cream', 'cream cheese', 'cream cheeses', 'buttermilk', - 'cottage cheese', 'ghee', 'kefir' - ], - [GroceryCategory.Vegetables]: [ - 'carrot', 'carrots', 'broccoli', 'lettuce', 'lettuces', 'spinach', 'kale', - 'cabbage', 'cabbages', 'cauliflower', 'zucchini', 'zucchinis', 'onion', 'onions', - 'garlic', 'pepper', 'peppers', 'bell pepper', 'bell peppers', 'tomato', 'tomatoes', - 'cucumber', 'cucumbers', 'potato', 'potatoes', 'sweet potato', 'sweet potatoes', - 'beet', 'beets', 'eggplant', 'eggplants', 'celery', 'radish', 'radishes', - 'asparagus', 'mushroom', 'mushrooms' - ], - [GroceryCategory.Meat]: [ - 'steak', 'steaks', 'beef', 'pork', 'lamb', 'veal', 'ribeye', 'tenderloin', - 'filet mignon', 'bacon', 'ham', 'sausage', 'sausages', 'salami', 'ground beef', - 'beef mince', 'hamburger meat', 'prosciutto', 'brisket', 'pork chop', 'pork chops' - ], - [GroceryCategory.Poultry]: [ - 'chicken', 'whole chicken', 'chickens', 'chicken breast', 'chicken breasts', - 'breast of chicken', 'chicken thigh', 'chicken thighs', 'chicken leg', 'chicken legs', - 'chicken wing', 'chicken wings', 'wing', 'wings', 'drumsticks', 'chicken drumstick', - 'turkey', 'whole turkey', 'ground turkey', 'turkey breast', 'turkey breasts', - 'turkey leg', 'duck', 'ducks', 'goose', 'quail', 'pheasant' - ], - [GroceryCategory.Bakery]: [ - 'bread', 'breads', 'whole wheat bread', 'sourdough bread', 'bagel', 'bagels', - 'croissant', 'croissants', 'muffin', 'muffins', 'buns', 'hamburger bun', - 'hamburger buns', 'hotdog bun', 'hotdog buns', 'donut', 'donuts', 'roll', 'rolls', - 'dinner roll', 'dinner rolls', 'scone', 'scones', 'toast', 'ciabatta', - 'focaccia', 'brioche', 'pita', 'pitas', 'naan', 'baguette', 'baguettes', - 'pastry', 'pastries', 'pretzel', 'pretzels' - ], - [GroceryCategory.Beverages]: [ - 'coffee', 'decaf coffee', 'iced coffee', 'cold brew', 'tea', 'iced tea', - 'black tea', 'green tea', 'herbal tea', 'juice', 'apple juice', 'orange juice', - 'grape juice', 'water', 'sparkling water', 'flavored water', 'soda', 'cola', - 'diet soda', 'beer', 'lager', 'ale', 'wine', 'red wine', 'white wine', - 'whiskey', 'whisky', 'vodka', 'rum', 'gin', 'smoothie', 'smoothies', 'milkshake', - 'milkshakes', 'energy drink', 'energy drinks', 'sports drink', 'sports drinks', - 'lemonade', 'sparkling lemonade', 'iced lemonade', 'sparkling water', 'cider', - 'hard cider', 'kombucha' - ], - [GroceryCategory.Snacks]: [ - 'chips', 'potato chips', 'tortilla chips', 'corn chips', 'candy', 'candies', - 'chocolate', 'chocolates', 'dark chocolate', 'milk chocolate', 'white chocolate', - 'cookies', 'popcorn', 'pretzel', 'pretzels', 'nuts', 'mixed nuts', 'almonds', - 'cashews', 'trail mix', 'granola bar', 'granola bars', 'protein bar', 'protein bars', - 'crackers', 'gummies', 'gummy bears', 'fruit snacks', 'dried fruit', - 'peanut butter', 'rice cakes', 'snack cakes' - ], - [GroceryCategory.Household]: [ - 'detergent', 'laundry detergent', 'dish soap', 'dishwasher detergent', - 'toilet paper', 'paper towels', 'trash bags', 'bin liners', 'broom', 'mop', - 'cleaner', 'surface cleaner', 'multi-purpose cleaner', 'disinfectant', 'bleach', - 'fabric softener', 'vacuum bags', 'aluminum foil', 'plastic wrap', 'cling wrap', - 'light bulbs', 'batteries', 'laundry soap', 'sponges', 'scrubbers', - 'dishwashing liquid' - ], - [GroceryCategory.PersonalCare]: [ - 'shampoo', 'conditioner', 'hair shampoo', 'hair conditioner', 'soap', 'bar soap', - 'liquid soap', 'toothpaste', 'toothbrush', 'toothbrushes', 'electric toothbrush', - 'deodorant', 'antiperspirant', 'lotion', 'moisturizer', 'razor', 'shaving razor', - 'shaving cream', 'body wash', 'face wash', 'sunscreen', 'hair gel', 'hair spray', - 'nail polish', 'cotton swabs', 'lip balm', 'chapstick', 'hand cream', 'sanitizer' - ], - [GroceryCategory.Frozen]: [ - 'ice cream', 'ice creams', 'frozen pizza', 'frozen pizzas', 'frozen vegetables', - 'frozen veg', 'frozen peas', 'frozen fruit', 'frozen fish', 'frozen chicken', - 'frozen wings', 'frozen fries', 'frozen french fries', 'frozen shrimp', - 'frozen prawns', 'frozen dumplings', 'frozen waffles', 'frozen pancakes', - 'frozen pie', 'frozen pies', 'frozen lasagna', 'frozen burrito', 'frozen burritos', - 'frozen nuggets', 'frozen pastry', 'frozen pastries', 'frozen meals' - ], + createGrocery(grocery); }; const fuzzyMatchGroceryCategory = (groceryTitle: string): GroceryCategory => { @@ -221,12 +212,8 @@ export const GroceryProvider: React.FC<{ children: React.ReactNode }> = ({ return bestMatchCategory; }; - const updateGroceryItem = (id: number, changes: Partial) => { - setGroceries((prevGroceries) => - prevGroceries.map((grocery) => - grocery.id === id ? { ...grocery, ...changes } : grocery - ) - ); + const updateGroceryItem = (changes: Partial) => { + updateGrocery(changes); }; return ( diff --git a/eas.json b/eas.json index a45666d..e022e28 100644 --- a/eas.json +++ b/eas.json @@ -5,12 +5,16 @@ "build": { "development": { "developmentClient": true, - "distribution": "internal" + "distribution": "internal", + "channel": "development" }, "preview": { - "distribution": "internal" + "distribution": "internal", + "channel": "preview" }, - "production": {} + "production": { + "channel": "production" + } }, "submit": { "production": { diff --git a/firebase/functions/index.js b/firebase/functions/index.js index a592e3d..37e0c53 100644 --- a/firebase/functions/index.js +++ b/firebase/functions/index.js @@ -1,14 +1,109 @@ const {onRequest} = require("firebase-functions/v2/https"); const {getAuth} = require("firebase-admin/auth"); const {getFirestore} = require("firebase-admin/firestore"); -const admin = require("firebase-admin"); const logger = require("firebase-functions/logger"); +const functions = require('firebase-functions'); +const admin = require('firebase-admin'); +const {Expo} = require('expo-server-sdk'); -try { - admin.initializeApp(); -} catch (error) { - console.error(error) -} +admin.initializeApp(); +const db = admin.firestore(); + +let expo = new Expo({accessToken: process.env.EXPO_ACCESS_TOKEN}); + +// Firestore trigger that listens for new events in the 'Events' collection +exports.sendNotificationOnEventCreation = functions.firestore + .document('Events/{eventId}') + .onCreate(async (snapshot, context) => { + const eventData = snapshot.data(); + const { familyId, creatorId } = eventData; + + if (!familyId || !creatorId) { + console.error('Missing familyId or creatorId in event data'); + return; + } + + const pushTokens = await getPushTokensForFamilyExcludingCreator(familyId, creatorId); + + if (!pushTokens.length) { + console.log('No push tokens available for the event.'); + return; + } + + let messages = []; + for (let pushToken of pushTokens) { + if (!Expo.isExpoPushToken(pushToken)) { + console.error(`Push token ${pushToken} is not a valid Expo push token`); + continue; + } + + messages.push({ + to: pushToken, + sound: 'default', + title: 'New Event Added!', + body: `An event "${eventData.title}" has been added. Check it out!`, + data: { eventId: context.params.eventId }, + }); + } + + let chunks = expo.chunkPushNotifications(messages); + let tickets = []; + for (let chunk of chunks) { + try { + let ticketChunk = await expo.sendPushNotificationsAsync(chunk); + tickets.push(...ticketChunk); + + for (let ticket of ticketChunk) { + if (ticket.status === 'ok') { + console.log('Notification successfully sent:', ticket.id); + } else if (ticket.status === 'error') { + console.error(`Notification error: ${ticket.message}`); + if (ticket.details && ticket.details.error) { + console.error('Error details:', ticket.details.error); + if (ticket.details.error === 'DeviceNotRegistered') { + console.log(`Removing invalid push token: ${ticket.to}`); + await removeInvalidPushToken(ticket.to); + } + } + } + } + } catch (error) { + console.error('Error sending notification:', error); + } + } + + // Retrieve and handle notification receipts + let receiptIds = []; + for (let ticket of tickets) { + if (ticket.id) { + receiptIds.push(ticket.id); + } + } + + let receiptIdChunks = expo.chunkPushNotificationReceiptIds(receiptIds); + for (let chunk of receiptIdChunks) { + try { + let receipts = await expo.getPushNotificationReceiptsAsync(chunk); + console.log('Receipts:', receipts); + + for (let receiptId in receipts) { + let { status, message, details } = receipts[receiptId]; + if (status === 'ok') { + console.log(`Notification with receipt ID ${receiptId} was delivered successfully`); + } else if (status === 'error') { + console.error(`Notification error: ${message}`); + if (details && details.error) { + console.error(`Error details: ${details.error}`); + } + } + } + } catch (error) { + console.error('Error retrieving receipts:', error); + } + } + + return null; + }); exports.createSubUser = onRequest(async (request, response) => { const authHeader = request.get('Authorization'); @@ -97,3 +192,35 @@ exports.generateCustomToken = onRequest(async (request, response) => { response.status(500).json({error: "Failed to generate custom token"}); } }); + +async function getPushTokensForEvent() { + const usersRef = db.collection('Profiles'); + const snapshot = await usersRef.get(); + let pushTokens = []; + + snapshot.forEach(doc => { + const data = doc.data(); + if (data.pushToken) { + pushTokens.push(data.pushToken); + } + }); + + console.log('Push Tokens:', pushTokens); + return pushTokens; +} + +async function getPushTokensForFamilyExcludingCreator(familyId, creatorId) { + const usersRef = db.collection('Profiles'); + const snapshot = await usersRef.where('familyId', '==', familyId).get(); + let pushTokens = []; + + snapshot.forEach(doc => { + const data = doc.data(); + // Exclude the creator + if (data.uid !== creatorId && data.pushToken) { + pushTokens.push(data.pushToken); + } + }); + + return pushTokens; +} diff --git a/firebase/functions/package-lock.json b/firebase/functions/package-lock.json index 535835a..8f16530 100644 --- a/firebase/functions/package-lock.json +++ b/firebase/functions/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "functions", "dependencies": { + "expo-server-sdk": "^3.11.0", "firebase-admin": "^12.1.0", "firebase-functions": "^5.0.0" }, @@ -3115,6 +3116,12 @@ "once": "^1.4.0" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3425,6 +3432,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expo-server-sdk": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-3.11.0.tgz", + "integrity": "sha512-EGH82ZcdAFjKq+6daDE8Xj7BjaSeP1VDvZ3Hmtn/KzEQ3ffqHkauMsgXL2wLEPlvatLq3EsYNcejXRBV54WnFQ==", + "dependencies": { + "node-fetch": "^2.6.0", + "promise-limit": "^2.7.0", + "promise-retry": "^2.0.1" + } + }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -5754,7 +5771,6 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", - "optional": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -6183,6 +6199,34 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -7048,8 +7092,7 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/ts-api-utils": { "version": "1.3.0", @@ -7268,8 +7311,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "optional": true + "license": "BSD-2-Clause" }, "node_modules/websocket-driver": { "version": "0.7.4", @@ -7299,7 +7341,6 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "license": "MIT", - "optional": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" diff --git a/firebase/functions/package.json b/firebase/functions/package.json index 379e7e2..7f49cb0 100644 --- a/firebase/functions/package.json +++ b/firebase/functions/package.json @@ -14,6 +14,7 @@ }, "main": "index.js", "dependencies": { + "expo-server-sdk": "^3.11.0", "firebase-admin": "^12.1.0", "firebase-functions": "^5.0.0" }, diff --git a/hooks/firebase/types/groceryData.ts b/hooks/firebase/types/groceryData.ts new file mode 100644 index 0000000..193d1d8 --- /dev/null +++ b/hooks/firebase/types/groceryData.ts @@ -0,0 +1,13 @@ +import {GroceryCategory, GroceryFrequency} from "@/contexts/GroceryContext"; + +export interface IGrocery { + id: string; + title: string; + category: GroceryCategory; + approved: boolean; + recurring: boolean; + frequency: GroceryFrequency; + bought: boolean; + familyId?: string, + creatorId?: string +} \ No newline at end of file diff --git a/hooks/firebase/useCreateEvent.ts b/hooks/firebase/useCreateEvent.ts index 33bdaa6..09eceaf 100644 --- a/hooks/firebase/useCreateEvent.ts +++ b/hooks/firebase/useCreateEvent.ts @@ -4,16 +4,17 @@ import firestore from "@react-native-firebase/firestore"; import { EventData } from "@/hooks/firebase/types/eventData"; export const useCreateEvent = () => { - const {user: currentUser} = useAuthContext() + const {user: currentUser, profileData} = useAuthContext() const queryClients = useQueryClient() return useMutation({ mutationKey: ["createEvent"], mutationFn: async (eventData: Partial) => { try { + console.log("CALLLLL") await firestore() .collection("Events") - .add({...eventData, creatorId: currentUser?.uid}) + .add({...eventData, creatorId: currentUser?.uid, familyId: profileData?.familyId}) } catch (e) { console.error(e) } diff --git a/hooks/firebase/useCreateGrocery.ts b/hooks/firebase/useCreateGrocery.ts new file mode 100644 index 0000000..b286dd3 --- /dev/null +++ b/hooks/firebase/useCreateGrocery.ts @@ -0,0 +1,26 @@ +import { useMutation, useQueryClient } from "react-query"; +import firestore from "@react-native-firebase/firestore"; +import { useAuthContext } from "@/contexts/AuthContext"; +import {IGrocery} from "@/hooks/firebase/types/groceryData"; + +export const useCreateGrocery = () => { + const { user: currentUser, profileData } = useAuthContext(); + const queryClients = useQueryClient(); + + return useMutation({ + mutationKey: ["createGrocery"], + mutationFn: async (groceryData: Partial) => { + try { + const newDoc = firestore().collection('Groceries').doc(); + await firestore() + .collection("Groceries") + .add({...groceryData, id: newDoc.id, familyId: profileData?.familyId, creatorId: currentUser?.uid}) + } catch (e) { + console.error(e) + } + }, + onSuccess: () => { + queryClients.invalidateQueries("groceries") + } + }) +} \ No newline at end of file diff --git a/hooks/firebase/useGetEvents.ts b/hooks/firebase/useGetEvents.ts index d823103..88a6d10 100644 --- a/hooks/firebase/useGetEvents.ts +++ b/hooks/firebase/useGetEvents.ts @@ -31,6 +31,7 @@ export const useGetEvents = (isFamilyView: boolean) => { const eventColor: string = profileData?.eventColor || colorMap.pink // Default color if not found return { + id: doc.id, title: data.title, start: new Date(data.startDate.seconds * 1000), end: new Date(data.endDate.seconds * 1000), diff --git a/hooks/firebase/useGetGroceries.ts b/hooks/firebase/useGetGroceries.ts new file mode 100644 index 0000000..91ac256 --- /dev/null +++ b/hooks/firebase/useGetGroceries.ts @@ -0,0 +1,32 @@ +import {useQuery} from "react-query"; +import firestore from "@react-native-firebase/firestore"; +import {useAuthContext} from "@/contexts/AuthContext"; + +export const useGetGroceries = () => { + const { user, profileData } = useAuthContext(); + + return useQuery({ + queryKey: ["groceries", user?.uid], + queryFn: async () => { + const snapshot = await firestore() + .collection("Groceries") + .where("familyId", "==", profileData?.familyId) + .get(); + + return snapshot.docs.map((doc) => { + const data = doc.data(); + + return { + id: doc.id, + title: data.title, + category: data.category, + approved: data.approved, + bought: data.bought, + recurring: data.recurring, + frequency: data.frequency, + creatorId: data.creatorId + }; + }); + } + }) +}; \ No newline at end of file diff --git a/hooks/firebase/useUpdateEvent.ts b/hooks/firebase/useUpdateEvent.ts new file mode 100644 index 0000000..7cf4537 --- /dev/null +++ b/hooks/firebase/useUpdateEvent.ts @@ -0,0 +1,26 @@ +import {useAuthContext} from "@/contexts/AuthContext"; +import {useMutation, useQueryClient} from "react-query"; +import firestore from "@react-native-firebase/firestore"; +import {EventData} from "@/hooks/firebase/types/eventData"; + +export const useUpdateEvent = () => { + const {user: currentUser} = useAuthContext() + const queryClients = useQueryClient() + + return useMutation({ + mutationKey: ["updateEvent"], + mutationFn: async (eventData: Partial) => { + try { + await firestore() + .collection("Events") + .doc(eventData.id) + .update(eventData); + } catch (e) { + console.error(e) + } + }, + onSuccess: () => { + queryClients.invalidateQueries("events") + } + }) +} \ No newline at end of file diff --git a/hooks/firebase/useUpdateGrocery.ts b/hooks/firebase/useUpdateGrocery.ts new file mode 100644 index 0000000..e5143df --- /dev/null +++ b/hooks/firebase/useUpdateGrocery.ts @@ -0,0 +1,24 @@ +import {useMutation, useQueryClient} from "react-query"; +import firestore from "@react-native-firebase/firestore"; +import {IGrocery} from "@/hooks/firebase/types/groceryData"; + +export const useUpdateGrocery = () => { + const queryClients = useQueryClient() + + return useMutation({ + mutationKey: ["updateGrocery"], + mutationFn: async (groceryData: Partial) => { + try { + await firestore() + .collection("Groceries") + .doc(groceryData.id) + .update(groceryData); + } catch (e) { + console.error(e) + } + }, + onSuccess: () => { + queryClients.invalidateQueries("groceries") + } + }) +} \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 96c5cc1..8f5a0f0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -962,6 +962,8 @@ PODS: - BVLinearGradient (2.8.3): - React-Core - DoubleConversion (1.1.6) + - EASClient (0.12.0): + - ExpoModulesCore - EXApplication (5.9.1): - ExpoModulesCore - EXBarCodeScanner (13.0.1): @@ -977,6 +979,8 @@ PODS: - EXJSONUtils (0.13.1) - EXManifests (0.14.3): - ExpoModulesCore + - EXNotifications (0.28.18): + - ExpoModulesCore - Expo (51.0.34): - ExpoModulesCore - expo-dev-client (4.0.27): @@ -1186,10 +1190,6 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - ExpoAdapterGoogleSignIn (13.1.0): - - ExpoModulesCore - - GoogleSignIn (~> 7.1) - - React-Core - ExpoAsset (10.0.10): - ExpoModulesCore - ExpoCamera (15.0.16): @@ -1198,6 +1198,8 @@ PODS: - ZXingObjC/PDF417 - ExpoCrypto (13.0.2): - ExpoModulesCore + - ExpoDevice (6.0.2): + - ExpoModulesCore - ExpoFileSystem (17.0.1): - ExpoModulesCore - ExpoFont (12.0.10): @@ -1257,6 +1259,35 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - EXStructuredHeaders (3.8.0) + - EXUpdates (0.25.27): + - DoubleConversion + - EASClient + - EXManifests + - ExpoModulesCore + - EXStructuredHeaders + - EXUpdatesInterface + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - ReachabilitySwift + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - "sqlite3 (~> 3.45.3+1)" + - Yoga - EXUpdatesInterface (0.16.2): - ExpoModulesCore - FBLazyVector (0.74.3) @@ -1351,10 +1382,6 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - - GoogleSignIn (7.1.0): - - AppAuth (< 2.0, >= 1.7.3) - - GTMAppAuth (< 5.0, >= 4.1.1) - - GTMSessionFetcher/Core (~> 3.3) - GoogleUtilities/AppDelegateSwizzler (7.13.3): - GoogleUtilities/Environment - GoogleUtilities/Logger @@ -1458,9 +1485,6 @@ PODS: - gRPC-Core/Privacy (= 1.62.5) - gRPC-Core/Interface (1.62.5) - gRPC-Core/Privacy (1.62.5) - - GTMAppAuth (4.1.1): - - AppAuth/Core (~> 1.7) - - GTMSessionFetcher/Core (< 4.0, >= 3.3) - GTMSessionFetcher/Core (3.5.0) - hermes-engine (0.74.3): - hermes-engine/Pre-built (= 0.74.3) @@ -1496,6 +1520,7 @@ PODS: - FBLazyVector (= 0.74.3) - RCTRequired (= 0.74.3) - React-Core (= 0.74.3) + - ReachabilitySwift (5.2.3) - React (0.74.3): - React-Core (= 0.74.3) - React-Core/DevSupport (= 0.74.3) @@ -2704,9 +2729,6 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNGoogleSignin (13.1.0): - - GoogleSignIn (~> 7.1) - - React-Core - RNReanimated (3.10.1): - DoubleConversion - glog @@ -2753,6 +2775,9 @@ PODS: - RNSVG (15.7.1): - React-Core - SocketRocket (0.7.0) + - "sqlite3 (3.45.3+1)": + - "sqlite3/common (= 3.45.3+1)" + - "sqlite3/common (3.45.3+1)" - Yoga (0.0.0) - ZXingObjC/Core (3.6.9) - ZXingObjC/OneD (3.6.9): @@ -2764,21 +2789,23 @@ DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - EASClient (from `../node_modules/expo-eas-client/ios`) - EXApplication (from `../node_modules/expo-application/ios`) - EXBarCodeScanner (from `../node_modules/expo-barcode-scanner/ios`) - EXConstants (from `../node_modules/expo-constants/ios`) - EXImageLoader (from `../node_modules/expo-image-loader/ios`) - EXJSONUtils (from `../node_modules/expo-json-utils/ios`) - EXManifests (from `../node_modules/expo-manifests/ios`) + - EXNotifications (from `../node_modules/expo-notifications/ios`) - Expo (from `../node_modules/expo`) - expo-dev-client (from `../node_modules/expo-dev-client/ios`) - expo-dev-launcher (from `../node_modules/expo-dev-launcher`) - expo-dev-menu (from `../node_modules/expo-dev-menu`) - expo-dev-menu-interface (from `../node_modules/expo-dev-menu-interface/ios`) - - "ExpoAdapterGoogleSignIn (from `../node_modules/@react-native-google-signin/google-signin/expo/ios`)" - ExpoAsset (from `../node_modules/expo-asset/ios`) - ExpoCamera (from `../node_modules/expo-camera/ios`) - ExpoCrypto (from `../node_modules/expo-crypto/ios`) + - ExpoDevice (from `../node_modules/expo-device/ios`) - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) - ExpoFont (from `../node_modules/expo-font/ios`) - ExpoHead (from `../node_modules/expo-router/ios`) @@ -2788,6 +2815,8 @@ DEPENDENCIES: - ExpoSystemUI (from `../node_modules/expo-system-ui/ios`) - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) - EXSplashScreen (from `../node_modules/expo-splash-screen/ios`) + - EXStructuredHeaders (from `../node_modules/expo-structured-headers/ios`) + - EXUpdates (from `../node_modules/expo-updates/ios`) - EXUpdatesInterface (from `../node_modules/expo-updates-interface/ios`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) @@ -2853,7 +2882,6 @@ DEPENDENCIES: - "RNFBFirestore (from `../node_modules/@react-native-firebase/firestore`)" - "RNFBFunctions (from `../node_modules/@react-native-firebase/functions`)" - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - - "RNGoogleSignin (from `../node_modules/@react-native-google-signin/google-signin`)" - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) - RNSVG (from `../node_modules/react-native-svg`) @@ -2881,18 +2909,18 @@ SPEC REPOS: - FirebaseSessions - FirebaseSharedSwift - GoogleDataTransport - - GoogleSignIn - GoogleUtilities - "gRPC-C++" - gRPC-Core - - GTMAppAuth - GTMSessionFetcher - leveldb-library - nanopb - PromisesObjC - PromisesSwift + - ReachabilitySwift - RecaptchaInterop - SocketRocket + - sqlite3 - ZXingObjC EXTERNAL SOURCES: @@ -2902,6 +2930,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-linear-gradient" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + EASClient: + :path: "../node_modules/expo-eas-client/ios" EXApplication: :path: "../node_modules/expo-application/ios" EXBarCodeScanner: @@ -2914,6 +2944,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-json-utils/ios" EXManifests: :path: "../node_modules/expo-manifests/ios" + EXNotifications: + :path: "../node_modules/expo-notifications/ios" Expo: :path: "../node_modules/expo" expo-dev-client: @@ -2924,14 +2956,14 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-dev-menu" expo-dev-menu-interface: :path: "../node_modules/expo-dev-menu-interface/ios" - ExpoAdapterGoogleSignIn: - :path: "../node_modules/@react-native-google-signin/google-signin/expo/ios" ExpoAsset: :path: "../node_modules/expo-asset/ios" ExpoCamera: :path: "../node_modules/expo-camera/ios" ExpoCrypto: :path: "../node_modules/expo-crypto/ios" + ExpoDevice: + :path: "../node_modules/expo-device/ios" ExpoFileSystem: :path: "../node_modules/expo-file-system/ios" ExpoFont: @@ -2950,6 +2982,10 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-web-browser/ios" EXSplashScreen: :path: "../node_modules/expo-splash-screen/ios" + EXStructuredHeaders: + :path: "../node_modules/expo-structured-headers/ios" + EXUpdates: + :path: "../node_modules/expo-updates/ios" EXUpdatesInterface: :path: "../node_modules/expo-updates-interface/ios" FBLazyVector: @@ -3077,8 +3113,6 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-firebase/functions" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" - RNGoogleSignin: - :path: "../node_modules/@react-native-google-signin/google-signin" RNReanimated: :path: "../node_modules/react-native-reanimated" RNScreens: @@ -3095,21 +3129,23 @@ SPEC CHECKSUMS: BoringSSL-GRPC: 1e2348957acdbcad360b80a264a90799984b2ba6 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 + EASClient: 1509a9a6b48b932ec61667644634daf2562983b8 EXApplication: c08200c34daca7af7fd76ac4b9d606077410e8ad EXBarCodeScanner: e2dd9b42c1b522a2adc9202b1dfbc64cb34456d1 EXConstants: 409690fbfd5afea964e5e9d6c4eb2c2b59222c59 EXImageLoader: ab589d67d6c5f2c33572afea9917304418566334 EXJSONUtils: 30c17fd9cc364d722c0946a550dfbf1be92ef6a4 EXManifests: c1fab4c3237675e7b0299ea8df0bcb14baca4f42 + EXNotifications: dd289340c26bc5388e440fc90d0b2c661cbd0285 Expo: 963ef4ae102e4f4ac114a8510d70127c44adffbf expo-dev-client: 85deba11af998ea86e62093b17fce56aa2c6f26b expo-dev-launcher: fe4f2c0a0aa627449eeaec5f9f11e04090f97c40 expo-dev-menu: 5b14897ecce3a8cf9e9cf9109344c2c192a3766a expo-dev-menu-interface: be32c09f1e03833050f0ee290dcc86b3ad0e73e4 - ExpoAdapterGoogleSignIn: da10ae7e7c1d73a10c2facebcdfe5ebea8e073ce ExpoAsset: 323700f291684f110fb55f0d4022a3362ea9f875 ExpoCamera: 929be541d1c1319fcf32f9f5d9df8b97804346b5 ExpoCrypto: 156078f266bf28f80ecf5e2a9c3a0d6ffce07a1c + ExpoDevice: fc94f0e42ecdfd897e7590f2874fc64dfa7e9b1c ExpoFileSystem: 80bfe850b1f9922c16905822ecbf97acd711dc51 ExpoFont: 00756e6c796d8f7ee8d211e29c8b619e75cbf238 ExpoHead: fcb28a68ed4ba28f177394d2dfb8a0a8824cd103 @@ -3119,6 +3155,8 @@ SPEC CHECKSUMS: ExpoSystemUI: d4f065a016cae6721b324eb659cdee4d4cf0cb26 ExpoWebBrowser: 7595ccac6938eb65b076385fd23d035db9ecdc8e EXSplashScreen: d8b3c547b9b18a41d80c6f6b274c4c26664febd4 + EXStructuredHeaders: cb8d1f698e144f4c5547b4c4963e1552f5d2b457 + EXUpdates: fe42e1cdace89a8db450351fbc38577b237af76b EXUpdatesInterface: 996527fd7d1a5d271eb523258d603f8f92038f24 FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 Firebase: cec914dab6fd7b1bd8ab56ea07ce4e03dd251c2d @@ -3140,11 +3178,9 @@ SPEC CHECKSUMS: fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a - GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 "gRPC-C++": e725ef63c4475d7cdb7e2cf16eb0fde84bd9ee51 gRPC-Core: eee4be35df218649fe66d721a05a7f27a28f069b - GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 hermes-engine: 1f547997900dd0752dc0cc0ae6dd16173c49e09b leveldb-library: e8eadf9008a61f9e1dde3978c086d2b6d9b9dc28 @@ -3155,6 +3191,7 @@ SPEC CHECKSUMS: RCTDeprecation: 4c7eeb42be0b2e95195563c49be08d0b839d22b4 RCTRequired: d530a0f489699c8500e944fde963102c42dcd0c2 RCTTypeSafety: b20878506b094fa3d9007d7b9e4be0faa3562499 + ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979 React: 2f9da0177233f60fa3462d83fcccde245759f81a React-callinvoker: d0205f0dcebf72ec27263ab41e3a5ad827ed503f React-Codegen: 27212e14727ad7d6d9fd1b1967fbf21929e4ce29 @@ -3210,11 +3247,11 @@ SPEC CHECKSUMS: RNFBFirestore: e47cdde04ea3d9e73e58e037e1aa1d0b1141c316 RNFBFunctions: 738cc9e2177d060d29b5d143ef2f9ed0eda4bb1f RNGestureHandler: 20a4307fd21cbff339abfcfa68192f3f0a6a518b - RNGoogleSignin: 9e68b9bcc3888219357924e32ee563624745647d RNReanimated: d51431fd3597a8f8320319dce8e42cee82a5445f RNScreens: 30249f9331c3b00ae7cb7922e11f58b3ed369c07 RNSVG: 4590aa95758149fa27c5c83e54a6a466349a1688 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d + sqlite3: 02d1f07eaaa01f80a1c16b4b31dfcbb3345ee01a Yoga: bd92064a0d558be92786820514d74fc4dddd1233 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 diff --git a/ios/cally.xcodeproj/project.pbxproj b/ios/cally.xcodeproj/project.pbxproj index f1636ec..aae8a40 100644 --- a/ios/cally.xcodeproj/project.pbxproj +++ b/ios/cally.xcodeproj/project.pbxproj @@ -298,6 +298,9 @@ "${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseAuth/FirebaseAuth_Privacy.bundle", @@ -308,13 +311,12 @@ "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseFirestore/FirebaseFirestore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseFirestoreInternal/FirebaseFirestoreInternal_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth/GTMAppAuth_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher_Core_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/ReachabilitySwift.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/abseil/xcprivacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-launcher/EXDevLauncher.bundle", @@ -331,6 +333,9 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseAuth_Privacy.bundle", @@ -341,13 +346,12 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseFirestore_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseFirestoreInternal_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMAppAuth_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMSessionFetcher_Core_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ReachabilitySwift.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/xcprivacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle", @@ -437,7 +441,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.cally.app; - PRODUCT_NAME = "CallyFamilyPlanner"; + PRODUCT_NAME = CallyFamilyPlanner; SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -468,7 +472,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.cally.app; - PRODUCT_NAME = "CallyFamilyPlanner"; + PRODUCT_NAME = CallyFamilyPlanner; SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ios/cally/Info.plist b/ios/cally/Info.plist index 3163324..0bab191 100644 --- a/ios/cally/Info.plist +++ b/ios/cally/Info.plist @@ -84,6 +84,11 @@ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route + $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route + $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route + $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route + $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route + $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route UILaunchStoryboardName SplashScreen diff --git a/ios/cally/Supporting/Expo.plist b/ios/cally/Supporting/Expo.plist index 750be02..a0a9a33 100644 --- a/ios/cally/Supporting/Expo.plist +++ b/ios/cally/Supporting/Expo.plist @@ -5,8 +5,12 @@ EXUpdatesCheckOnLaunch ALWAYS EXUpdatesEnabled - + EXUpdatesLaunchWaitMs 0 + EXUpdatesRuntimeVersion + 1.0.0 + EXUpdatesURL + https://u.expo.dev/bdb8c57b-25bb-4d36-b3b8-5b09c5092f52 \ No newline at end of file diff --git a/ios/cally/cally.entitlements b/ios/cally/cally.entitlements index f683276..018a6e2 100644 --- a/ios/cally/cally.entitlements +++ b/ios/cally/cally.entitlements @@ -1,5 +1,8 @@ - + + aps-environment + development + \ No newline at end of file diff --git a/package.json b/package.json index 2273b53..e69425e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "lint": "expo lint", "submit": "npx eas-cli submit -p ios", "prebuild": "npx expo prebuild -p ios", - "prebuild-build-submit-ios": "npm run prebuild && npm run build-ios && npm run submit", + "prebuild-build-submit-ios": "yarn run prebuild && yarn run build-ios && yarn run submit", "prebuild-build-submit-ios-cicd": "yarn build-ios-cicd", "prebuild-build-submit-cicd": "yarn build-cicd" }, @@ -35,7 +35,6 @@ "@react-native-firebase/crashlytics": "^20.3.0", "@react-native-firebase/firestore": "^20.4.0", "@react-native-firebase/functions": "^20.4.0", - "@react-native-google-signin/google-signin": "^13.1.0", "@react-navigation/drawer": "^6.7.2", "@react-navigation/native": "^6.0.2", "date-fns": "^3.6.0", @@ -47,13 +46,16 @@ "expo-camera": "~15.0.16", "expo-constants": "~16.0.2", "expo-dev-client": "~4.0.27", + "expo-device": "~6.0.2", "expo-font": "~12.0.9", "expo-image-picker": "~15.0.7", "expo-linking": "~6.3.1", + "expo-notifications": "~0.28.18", "expo-router": "~3.5.20", "expo-splash-screen": "~0.27.5", "expo-status-bar": "~1.12.1", "expo-system-ui": "~3.0.7", + "expo-updates": "~0.25.27", "expo-web-browser": "~13.0.3", "firebase-admin": "^12.3.1", "firebase-functions": "^5.1.0", diff --git a/yarn.lock b/yarn.lock index 2154ead..830d3ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1025,6 +1025,20 @@ dotenv-expand "~11.0.6" getenv "^1.0.0" +"@expo/fingerprint@^0.10.2": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.10.3.tgz#87c2811fe7773ec7d00cae86ab041d578f9041b5" + integrity sha512-h/BnnyloJyMSrzeXonKLE6HfiMpRg3e9m8CAv+eUaAozG9heKMG9ftHW4cfm2StDYj/rWjFc5YK6MSIX6qd+xg== + dependencies: + "@expo/spawn-async" "^1.7.2" + chalk "^4.1.2" + debug "^4.3.4" + find-up "^5.0.0" + minimatch "^3.0.4" + p-limit "^3.1.0" + resolve-from "^5.0.0" + semver "^7.6.0" + "@expo/image-utils@^0.5.0": version "0.5.1" resolved "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.5.1.tgz" @@ -1686,6 +1700,11 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@ide/backoff@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@ide/backoff/-/backoff-1.0.0.tgz#466842c25bd4a4833e0642fab41ccff064010176" + integrity sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" @@ -2283,11 +2302,6 @@ resolved "https://registry.npmjs.org/@react-native-firebase/functions/-/functions-20.4.0.tgz" integrity sha512-g4kAWZboTE9cTdT7KT6k1haHDmEBA36bPCvrh2MJ2RACo2JxotB2MIOEPZ5U/cT94eIAlgI5YtxQQGQfC+VcBQ== -"@react-native-google-signin/google-signin@^13.1.0": - version "13.1.0" - resolved "https://registry.npmjs.org/@react-native-google-signin/google-signin/-/google-signin-13.1.0.tgz" - integrity sha512-C2/sqb0/s0c+Dwc/mykASZsRuHxGqn7SFrCxCY9D8p8IOQO05haInhCc7lzraJshRixGva5c/4usQZ71HMYSEQ== - "@react-native/assets-registry@0.74.85": version "0.74.85" resolved "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.85.tgz" @@ -3247,6 +3261,11 @@ application-config-path@^0.1.0: resolved "https://registry.npmjs.org/application-config-path/-/application-config-path-0.1.1.tgz" integrity sha512-zy9cHePtMP0YhwG+CfHm0bgwdnga2X3gZexpdCwEj//dpb+TKajtiC8REEUJUSq6Ab4f9cgNy2l8ObXzCXFkEw== +arg@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.0.tgz#583c518199419e0037abb74062c37f8519e575f0" + integrity sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg== + arg@5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz" @@ -3313,6 +3332,17 @@ asap@~2.0.3, asap@~2.0.6: resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +assert@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd" + integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw== + dependencies: + call-bind "^1.0.2" + is-nan "^1.3.2" + object-is "^1.1.5" + object.assign "^4.1.4" + util "^0.12.5" + ast-types@0.15.2: version "0.15.2" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz" @@ -3492,6 +3522,11 @@ babel-preset-jest@^29.6.3: babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" +badgin@^1.1.5: + version "1.2.3" + resolved "https://registry.yarnpkg.com/badgin/-/badgin-1.2.3.tgz#994b5f519827d7d5422224825b2c8faea2bc43ad" + integrity sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" @@ -3721,7 +3756,7 @@ calendarize@^1.1.1: resolved "https://registry.npmjs.org/calendarize/-/calendarize-1.1.1.tgz" integrity sha512-C2JyBAtNp2NG4DX4fA1EILggLt/5PlYzvQR0crHktoAPBc9TlIfdhzg7tWekCbe+pH6+9qoK+FhPbi+vYJJlqw== -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== @@ -4413,7 +4448,7 @@ define-lazy-prop@^2.0.0: resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -4964,6 +4999,18 @@ expo-dev-menu@5.0.21: expo-dev-menu-interface "1.8.3" semver "^7.5.4" +expo-device@~6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/expo-device/-/expo-device-6.0.2.tgz#9bc3eccd16509c2819c225cc2ca8f7c3e3bdd11e" + integrity sha512-sCt91CuTmAuMXX4SlFOn4lIos2UIr8vb0jDstDDZXys6kErcj0uynC7bQAMreU5uRUTKMAl4MAMpKt9ufCXPBw== + dependencies: + ua-parser-js "^0.7.33" + +expo-eas-client@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/expo-eas-client/-/expo-eas-client-0.12.0.tgz#e8b6f7d33873e6f630f37f7bfc41646ae7b0b2a9" + integrity sha512-Jkww9Cwpv0z7DdLYiRX0r4fqBEcI9cKqTn7cHx63S09JaZ2rcwEE4zYHgrXwjahO+tU2VW8zqH+AJl6RhhW4zA== + expo-file-system@~17.0.1: version "17.0.1" resolved "https://registry.npmjs.org/expo-file-system/-/expo-file-system-17.0.1.tgz" @@ -5034,6 +5081,20 @@ expo-modules-core@1.12.24: dependencies: invariant "^2.2.4" +expo-notifications@~0.28.18: + version "0.28.18" + resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.28.18.tgz#a3e5488429079d664885e975985dd2d6bdb52a5b" + integrity sha512-oRvr8rYhbbKNhVgcO+fj5g5g6vS0umGcElpeMSWa0KudUfOOgV6nNLvv5M89393z2Ahd7wPK4bnK8lygc0nCPQ== + dependencies: + "@expo/image-utils" "^0.5.0" + "@ide/backoff" "^1.0.0" + abort-controller "^3.0.0" + assert "^2.0.0" + badgin "^1.1.5" + expo-application "~5.9.0" + expo-constants "~16.0.0" + fs-extra "^9.1.0" + expo-router@~3.5.20: version "3.5.23" resolved "https://registry.npmjs.org/expo-router/-/expo-router-3.5.23.tgz" @@ -5061,6 +5122,11 @@ expo-status-bar@~1.12.1: resolved "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.12.1.tgz" integrity sha512-/t3xdbS8KB0prj5KG5w7z+wZPFlPtkgs95BsmrP/E7Q0xHXTcDcQ6Cu2FkFuRM+PKTb17cJDnLkawyS5vDLxMA== +expo-structured-headers@~3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/expo-structured-headers/-/expo-structured-headers-3.8.0.tgz#11797a4c3a7a6770b21126cecffcda148030e361" + integrity sha512-R+gFGn0x5CWl4OVlk2j1bJTJIz4KO8mPoCHpRHmfqMjmrMvrOM0qQSY3V5NHXwp1yT/L2v8aUmFQsBRIdvi1XA== + expo-system-ui@~3.0.7: version "3.0.7" resolved "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-3.0.7.tgz" @@ -5074,6 +5140,27 @@ expo-updates-interface@~0.16.2: resolved "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-0.16.2.tgz" integrity sha512-929XBU70q5ELxkKADj1xL0UIm3HvhYhNAOZv5DSk7rrKvLo7QDdPyl+JVnwZm9LrkNbH4wuE2rLoKu1KMgZ+9A== +expo-updates@~0.25.27: + version "0.25.27" + resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.25.27.tgz#4aff889fea2aa221d8341a902646288f84c48b9e" + integrity sha512-1hyYZqBEXcAiEuSRPJ6dINTndGlWi6/bwlyYGjSnyoYfu/vzZQrJ+XA8JUP4EvJ3b0g8a0UOIjlDJ9ke9kMcfg== + dependencies: + "@expo/code-signing-certificates" "0.0.5" + "@expo/config" "~9.0.0-beta.0" + "@expo/config-plugins" "~8.0.8" + "@expo/fingerprint" "^0.10.2" + "@expo/spawn-async" "^1.7.2" + arg "4.1.0" + chalk "^4.1.2" + expo-eas-client "~0.12.0" + expo-manifests "~0.14.0" + expo-structured-headers "~3.8.0" + expo-updates-interface "~0.16.2" + fast-glob "^3.3.2" + fbemitter "^3.0.0" + ignore "^5.3.1" + resolve-from "^5.0.0" + expo-web-browser@~13.0.0, expo-web-browser@~13.0.3: version "13.0.3" resolved "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-13.0.3.tgz" @@ -5997,7 +6084,7 @@ ieee754@^1.1.13: resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0: +ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -6240,6 +6327,14 @@ is-invalid-path@^0.1.0: dependencies: is-glob "^2.0.0" +is-nan@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-negative-zero@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz" @@ -7959,12 +8054,20 @@ object-inspect@^1.13.1: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz" integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== +object-is@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.5: +object.assign@^4.1.4, object.assign@^4.1.5: version "4.1.5" resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz" integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== @@ -10149,6 +10252,11 @@ typical@^2.6.0: resolved "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz" integrity sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg== +ua-parser-js@^0.7.33: + version "0.7.39" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.39.tgz#c71efb46ebeabc461c4612d22d54f88880fabe7e" + integrity sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w== + ua-parser-js@^1.0.35: version "1.0.38" resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz" @@ -10313,7 +10421,7 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util@^0.12.3: +util@^0.12.3, util@^0.12.5: version "0.12.5" resolved "https://registry.npmjs.org/util/-/util-0.12.5.tgz" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==