mirror of
https://github.com/urosran/cally.git
synced 2025-07-09 22:57:16 +00:00
Apple calendar sync, timezone, first day of week additions
This commit is contained in:
@ -1,10 +1,12 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<queries>
|
||||
<intent>
|
||||
@ -22,7 +24,7 @@
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://u.expo.dev/bdb8c57b-25bb-4d36-b3b8-5b09c5092f52"/>
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">Cally - Family Planner</string>
|
||||
<string name="app_name">Cally.</string>
|
||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||
<string name="expo_system_ui_user_interface_style" translatable="false">light</string>
|
||||
|
@ -1,4 +1,4 @@
|
||||
rootProject.name = 'Cally - Family Planner'
|
||||
rootProject.name = 'Cally.'
|
||||
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
|
17
app.json
17
app.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "Cally - Family Planner",
|
||||
"name": "Cally.",
|
||||
"slug": "cally",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
@ -16,7 +16,8 @@
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.cally.app",
|
||||
"googleServicesFile": "./ios/GoogleService-Info.plist",
|
||||
"buildNumber": "23"
|
||||
"buildNumber": "23",
|
||||
"usesAppleSignIn": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
@ -63,7 +64,17 @@
|
||||
"defaultChannel": "default"
|
||||
}
|
||||
],
|
||||
"expo-font"
|
||||
[
|
||||
"expo-calendar",
|
||||
{
|
||||
"calendarPermission": "The app needs to access your calendar."
|
||||
}
|
||||
],
|
||||
[
|
||||
"expo-apple-authentication"
|
||||
],
|
||||
"expo-font",
|
||||
"expo-localization"
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
|
41
calendar-integration/apple-calendar-utils.js
Normal file
41
calendar-integration/apple-calendar-utils.js
Normal file
@ -0,0 +1,41 @@
|
||||
import * as Calendar from 'expo-calendar';
|
||||
|
||||
export async function fetchiPhoneCalendarEvents(familyId, email, startDate, endDate) {
|
||||
try {
|
||||
const {status} = await Calendar.requestCalendarPermissionsAsync();
|
||||
if (status !== 'granted') {
|
||||
throw new Error("Calendar permission not granted");
|
||||
}
|
||||
|
||||
const defaultCalendarSource = await Calendar.getDefaultCalendarAsync();
|
||||
|
||||
if (!defaultCalendarSource) {
|
||||
throw new Error("No calendar found");
|
||||
}
|
||||
|
||||
const events = await Calendar.getEventsAsync(
|
||||
[defaultCalendarSource.id],
|
||||
startDate,
|
||||
endDate
|
||||
);
|
||||
|
||||
return events.map((event) => {
|
||||
let isAllDay = event.allDay || false;
|
||||
const startDateTime = new Date(event.startDate);
|
||||
const endDateTime = new Date(event.endDate);
|
||||
|
||||
return {
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
startDate: startDateTime,
|
||||
endDate: endDateTime,
|
||||
allDay: isAllDay,
|
||||
familyId,
|
||||
email
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching iPhone Calendar events: ", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export async function fetchGoogleCalendarEvents(token, email, startDate, endDate) {
|
||||
export async function fetchGoogleCalendarEvents(token, email, familyId, 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}`,
|
||||
@ -45,6 +45,7 @@ export async function fetchGoogleCalendarEvents(token, email, startDate, endDate
|
||||
startDate: startDateTime,
|
||||
endDate: endDateTime,
|
||||
allDay: isAllDay,
|
||||
familyId,
|
||||
email
|
||||
};
|
||||
googleEvents.push(googleEvent);
|
||||
|
@ -1,4 +1,4 @@
|
||||
export async function fetchMicrosoftCalendarEvents(token, email, startDate, endDate) {
|
||||
export async function fetchMicrosoftCalendarEvents(token, email, familyId, startDate, endDate) {
|
||||
const response = await fetch(
|
||||
`https://graph.microsoft.com/v1.0/me/calendar/calendarView?startDateTime=${startDate}&endDateTime=${endDate}`,
|
||||
{
|
||||
@ -34,6 +34,7 @@ export async function fetchMicrosoftCalendarEvents(token, email, startDate, endD
|
||||
startDate: startDateTime,
|
||||
endDate: endDateTime,
|
||||
allDay: item.isAllDay,
|
||||
familyId,
|
||||
email
|
||||
};
|
||||
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
selectedDateAtom,
|
||||
selectedNewEventDateAtom
|
||||
} from "@/components/pages/calendar/atoms";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
|
||||
interface EventCalendarProps {
|
||||
calendarHeight: number;
|
||||
@ -17,6 +18,7 @@ interface EventCalendarProps {
|
||||
|
||||
export const EventCalendar: React.FC<EventCalendarProps> = memo(({calendarHeight}) => {
|
||||
const {data: events} = useGetEvents();
|
||||
const {profileData} = useAuthContext()
|
||||
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom)
|
||||
const mode = useAtomValue(modeAtom)
|
||||
const setEditVisible = useSetAtom(editVisibleAtom)
|
||||
@ -35,6 +37,7 @@ export const EventCalendar: React.FC<EventCalendarProps> = memo(({calendarHeight
|
||||
setEditVisible(true);
|
||||
setEventForEdit(event);
|
||||
}}
|
||||
weekStartsOn={profileData?.firstDayOfWeek === "Mondays" ? 1 : 0}
|
||||
height={calendarHeight}
|
||||
activeDate={selectedDate}
|
||||
date={selectedDate}
|
||||
|
@ -20,7 +20,7 @@ export const InnerCalendar = () => {
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={{flex: 1, backgroundColor: "#fff", borderRadius: 30}}
|
||||
style={{flex: 1, backgroundColor: "#fff", borderRadius: 30, marginBottom: 60}}
|
||||
ref={calendarContainerRef}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
|
@ -1,12 +1,9 @@
|
||||
import {AntDesign, Ionicons} from "@expo/vector-icons";
|
||||
import React, {useCallback, useEffect, useState} from "react";
|
||||
import {Button, Checkbox, Text, View} from "react-native-ui-lib";
|
||||
import {ScrollView, StyleSheet} from "react-native";
|
||||
import {ActivityIndicator, ScrollView, StyleSheet} from "react-native";
|
||||
import {colorMap} from "@/contexts/SettingsContext";
|
||||
import {TouchableOpacity} from "react-native-gesture-handler";
|
||||
import {fetchGoogleCalendarEvents} from "@/calendar-integration/google-calendar-utils";
|
||||
import {fetchMicrosoftCalendarEvents} from "@/calendar-integration/microsoft-calendar-utils";
|
||||
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
|
||||
import debounce from "debounce";
|
||||
@ -17,6 +14,11 @@ import * as AuthSession from "expo-auth-session";
|
||||
import * as Google from "expo-auth-session/providers/google";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import {UserProfile} from "@firebase/auth";
|
||||
import {useFetchAndSaveGoogleEvents} from "@/hooks/useFetchAndSaveGoogleEvents";
|
||||
import {useFetchAndSaveOutlookEvents} from "@/hooks/useFetchAndSaveOutlookEvents";
|
||||
import {useFetchAndSaveAppleEvents} from "@/hooks/useFetchAndSaveAppleEvents";
|
||||
import * as AppleAuthentication from 'expo-apple-authentication';
|
||||
import ExpoLocalization from "expo-localization/src/ExpoLocalization";
|
||||
|
||||
const googleConfig = {
|
||||
androidClientId:
|
||||
@ -50,8 +52,8 @@ const microsoftConfig = {
|
||||
const CalendarSettingsPage = (props: {
|
||||
setSelectedPage: (page: number) => void;
|
||||
}) => {
|
||||
const [startDate, setStartDate] = useState<boolean>(false);
|
||||
const {profileData} = useAuthContext();
|
||||
const [firstDayOfWeek, setFirstDayOfWeek] = useState<string>(profileData?.firstDayOfWeek ?? ExpoLocalization.getCalendars()[0].firstWeekday === 1 ? "Mondays" : "Sundays");
|
||||
|
||||
const [selectedColor, setSelectedColor] = useState<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
@ -60,8 +62,11 @@ const CalendarSettingsPage = (props: {
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
|
||||
const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
|
||||
const {mutateAsync: updateUserData} = useUpdateUserData();
|
||||
const {mutateAsync: fetchAndSaveGoogleEvents, isLoading: isSyncingGoogle} = useFetchAndSaveGoogleEvents();
|
||||
const {mutateAsync: fetchAndSaveOutlookEvents, isLoading: isSyncingOutlook} = useFetchAndSaveOutlookEvents();
|
||||
const {mutateAsync: fetchAndSaveAppleEvents, isLoading: isSyncingApple} = useFetchAndSaveAppleEvents();
|
||||
|
||||
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
const [_, response, promptAsync] = Google.useAuthRequest(googleConfig);
|
||||
@ -70,49 +75,6 @@ const CalendarSettingsPage = (props: {
|
||||
signInWithGoogle();
|
||||
}, [response]);
|
||||
|
||||
const fetchAndSaveGoogleEvents = async (token?: string, email?: string) => {
|
||||
console.log("Fetching Google Calendar events...");
|
||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
|
||||
|
||||
console.log("Token: ", token ?? profileData?.googleToken)
|
||||
fetchGoogleCalendarEvents(
|
||||
token ?? profileData?.googleToken,
|
||||
email ?? profileData?.googleMail,
|
||||
timeMin.toISOString().slice(0, -5) + "Z",
|
||||
timeMax.toISOString().slice(0, -5) + "Z"
|
||||
).then(async (response) => {
|
||||
console.log("Google Calendar events fetched:", response);
|
||||
const items = response?.map((item) => {
|
||||
if (item.allDay) {
|
||||
item.startDate = new Date(new Date(item.startDate).setHours(0, 0, 0, 0))
|
||||
item.endDate = item.startDate
|
||||
}
|
||||
return item;
|
||||
}) || [];
|
||||
await createEventsFromProvider(items);
|
||||
}).catch((error) => {
|
||||
console.error("Error fetching Google Calendar events:", error);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchAndSaveMicrosoftEvents = async (token?: string, email?: string) => {
|
||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 3));
|
||||
|
||||
console.log("Token: ", token ?? profileData?.microsoftToken)
|
||||
fetchMicrosoftCalendarEvents(
|
||||
token ?? profileData?.microsoftToken,
|
||||
email ?? profileData?.outlookMail,
|
||||
timeMin.toISOString().slice(0, -5) + "Z",
|
||||
timeMax.toISOString().slice(0, -5) + "Z"
|
||||
).then(async (response) => {
|
||||
console.log(response);
|
||||
const items = response ?? [];
|
||||
await createEventsFromProvider(items);
|
||||
});
|
||||
};
|
||||
|
||||
const signInWithGoogle = async () => {
|
||||
try {
|
||||
if (response?.type === "success") {
|
||||
@ -214,7 +176,7 @@ const CalendarSettingsPage = (props: {
|
||||
newUserData: {microsoftToken: tokenData.access_token, outlookMail: outlookMail},
|
||||
});
|
||||
|
||||
await fetchAndSaveMicrosoftEvents(tokenData.access_token, outlookMail)
|
||||
await fetchAndSaveOutlookEvents(tokenData.access_token, outlookMail)
|
||||
console.log("User data updated successfully.");
|
||||
}
|
||||
}
|
||||
@ -226,6 +188,39 @@ const CalendarSettingsPage = (props: {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAppleSignIn = async () => {
|
||||
try {
|
||||
console.log("Starting Apple Sign-in...");
|
||||
|
||||
const credential = await AppleAuthentication.signInAsync({
|
||||
requestedScopes: [
|
||||
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
||||
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||
],
|
||||
});
|
||||
|
||||
console.log("Apple sign-in result:", credential);
|
||||
|
||||
const appleToken = credential.identityToken;
|
||||
const appleMail = credential.email;
|
||||
|
||||
if (appleToken) {
|
||||
console.log("Apple ID token received. Fetch user info if needed...");
|
||||
|
||||
// Example: Store user token and email
|
||||
await updateUserData({
|
||||
newUserData: {appleToken, appleMail},
|
||||
});
|
||||
|
||||
console.log("User data updated with Apple ID token.");
|
||||
} else {
|
||||
console.warn("Apple authentication was not successful or email was hidden.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during Apple Sign-in:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedUpdateUserData = useCallback(
|
||||
debounce(async (color: string) => {
|
||||
try {
|
||||
@ -242,6 +237,26 @@ const CalendarSettingsPage = (props: {
|
||||
[]
|
||||
);
|
||||
|
||||
const debouncedUpdateFirstDayOfWeek = useCallback(
|
||||
debounce(async (firstDayOfWeek: string) => {
|
||||
try {
|
||||
await updateUserData({
|
||||
newUserData: {
|
||||
firstDayOfWeek,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to update first day of week:", error);
|
||||
}
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleChangeFirstDayOfWeek = () => {
|
||||
setFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
|
||||
debouncedUpdateFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
|
||||
}
|
||||
|
||||
const handleChangeColor = (color: string) => {
|
||||
setPreviousSelectedColor(selectedColor);
|
||||
setSelectedColor(color);
|
||||
@ -333,10 +348,10 @@ const CalendarSettingsPage = (props: {
|
||||
<Text style={styles.cardTitle}>Weekly Start Date</Text>
|
||||
<View row marginV-5 marginT-20>
|
||||
<Checkbox
|
||||
value={startDate}
|
||||
value={firstDayOfWeek === "Sundays"}
|
||||
style={styles.checkbox}
|
||||
color="#ea156d"
|
||||
onValueChange={() => setStartDate(true)}
|
||||
onValueChange={() => handleChangeFirstDayOfWeek("Sundays")}
|
||||
/>
|
||||
<View row marginL-8>
|
||||
<Text text70>Sundays</Text>
|
||||
@ -348,10 +363,10 @@ const CalendarSettingsPage = (props: {
|
||||
</View>
|
||||
<View row marginV-5>
|
||||
<Checkbox
|
||||
value={!startDate}
|
||||
value={firstDayOfWeek === "Mondays"}
|
||||
style={styles.checkbox}
|
||||
color="#ea156d"
|
||||
onValueChange={() => setStartDate(false)}
|
||||
onValueChange={() => handleChangeFirstDayOfWeek("Mondays")}
|
||||
/>
|
||||
<Text text70 marginL-8>
|
||||
Mondays
|
||||
@ -379,7 +394,8 @@ const CalendarSettingsPage = (props: {
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
label="Connect Apple"
|
||||
onPress={() => !profileData?.appleToken ? handleAppleSignIn() : clearToken("google")}
|
||||
label={profileData?.appleToken ? `Disconnect ${profileData.appleMail}` : "Connect Apple"}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines: 2
|
||||
@ -416,40 +432,93 @@ const CalendarSettingsPage = (props: {
|
||||
Connected Calendars
|
||||
</Text>
|
||||
|
||||
<View style={styles.card}>
|
||||
<View style={styles.noPaddingCard}>
|
||||
<View style={{marginTop: 20}}>
|
||||
{!!profileData?.googleMail && (
|
||||
<Button
|
||||
onPress={() => fetchAndSaveGoogleEvents()}
|
||||
label={`Sync ${profileData?.googleMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{numberOfLines: 2}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() => fetchAndSaveGoogleEvents(undefined, undefined)}
|
||||
>
|
||||
<View row paddingR-20 center>
|
||||
<Button
|
||||
disabled={isSyncingGoogle}
|
||||
onPress={() => fetchAndSaveGoogleEvents(undefined, undefined)}
|
||||
label={`Sync ${profileData?.googleMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{numberOfLines: 3}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
|
||||
{isSyncingGoogle ? (
|
||||
<ActivityIndicator/>
|
||||
) : (
|
||||
<Ionicons name={"refresh"} size={20} color={"#000000"}/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
|
||||
{!!profileData?.appleMail && (
|
||||
<TouchableOpacity>
|
||||
<View row paddingR-20 center>
|
||||
<Button
|
||||
disabled={isSyncingApple}
|
||||
onPress={() => fetchAndSaveAppleEvents(undefined, undefined)}
|
||||
label={`Sync ${profileData?.appleMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{numberOfLines: 3}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<AppleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
{isSyncingApple ? (
|
||||
<ActivityIndicator/>
|
||||
) : (
|
||||
<Ionicons name={"refresh"} size={20} color={"#000000"}/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{!!profileData?.outlookMail && (
|
||||
<Button
|
||||
onPress={() => fetchAndSaveMicrosoftEvents()}
|
||||
label={`Sync ${profileData?.outlookMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{numberOfLines: 2}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() => fetchAndSaveOutlookEvents(undefined, undefined)}
|
||||
>
|
||||
<View row paddingR-20 center>
|
||||
<Button
|
||||
disabled={isSyncingOutlook}
|
||||
onPress={() => fetchAndSaveOutlookEvents(undefined, undefined)}
|
||||
label={`Sync ${profileData?.outlookMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{numberOfLines: 3}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
{isSyncingOutlook ? (
|
||||
<ActivityIndicator/>
|
||||
) : (
|
||||
<Ionicons name={"refresh"} size={20} color={"#000000"}/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
@ -480,6 +549,12 @@ const styles = StyleSheet.create({
|
||||
marginTop: 20,
|
||||
borderRadius: 12,
|
||||
},
|
||||
noPaddingCard: {
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
marginTop: 20,
|
||||
borderRadius: 12,
|
||||
},
|
||||
colorBox: {
|
||||
aspectRatio: 1,
|
||||
justifyContent: "center",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,122 +1,207 @@
|
||||
import { Text, TextField, View } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { ImageBackground, StyleSheet } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
|
||||
import {Colors, Picker, Text, TextField, View} from "react-native-ui-lib";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {ImageBackground, StyleSheet} from "react-native";
|
||||
import {ScrollView} from "react-native-gesture-handler";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
import * as tz from 'tzdata';
|
||||
import * as Localization from 'expo-localization';
|
||||
import debounce from "debounce";
|
||||
|
||||
const MyProfile = () => {
|
||||
const { user, profileData } = useAuthContext();
|
||||
const {user, profileData} = useAuthContext();
|
||||
|
||||
const [lastName, setLastName] = useState<string>(profileData?.lastName || "");
|
||||
const [firstName, setFirstName] = useState<string>(
|
||||
profileData?.firstName || ""
|
||||
);
|
||||
const [timeZone, setTimeZone] = useState<string>(profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone);
|
||||
const [lastName, setLastName] = useState<string>(profileData?.lastName || "");
|
||||
const [firstName, setFirstName] = useState<string>(
|
||||
profileData?.firstName || ""
|
||||
);
|
||||
|
||||
const { mutateAsync: updateUserData } = useUpdateUserData();
|
||||
return (
|
||||
<ScrollView style={{ paddingBottom: 100, flex: 1 }}>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.subTit}>Your Profile</Text>
|
||||
<View row spread paddingH-15 centerV marginV-15>
|
||||
<ImageBackground
|
||||
style={styles.pfp}
|
||||
source={require("../../../../assets/images/profile-picture.png")}
|
||||
/>
|
||||
const {mutateAsync: updateUserData} = useUpdateUserData();
|
||||
const isFirstRender = useRef(true);
|
||||
|
||||
<Text style={styles.photoSet} color="#50be0c">
|
||||
Change Photo
|
||||
</Text>
|
||||
<Text style={styles.photoSet}>Remove Photo</Text>
|
||||
</View>
|
||||
<View paddingH-15>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
First name
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="First name"
|
||||
style={styles.txtBox}
|
||||
value={firstName}
|
||||
onChangeText={async (value) => {
|
||||
setFirstName(value);
|
||||
await updateUserData({ newUserData: { firstName: value } });
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Last name
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="Last name"
|
||||
style={styles.txtBox}
|
||||
value={lastName}
|
||||
onChangeText={async (value) => {
|
||||
setLastName(value);
|
||||
await updateUserData({ newUserData: { lastName: value } });
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Email address
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="Email address"
|
||||
value={user?.email?.toString()}
|
||||
style={styles.txtBox}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.subTit}>Settings</Text>
|
||||
<Text text80 marginT-20 marginB-7 style={styles.label}>
|
||||
Time Zone
|
||||
</Text>
|
||||
<TextField text70 placeholder="Time Zone" style={styles.txtBox} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
const handleUpdateUserData = async () => {
|
||||
await updateUserData({newUserData: {firstName, lastName, timeZone}});
|
||||
}
|
||||
|
||||
const debouncedUserDataUpdate = debounce(handleUpdateUserData, 500);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFirstRender.current) {
|
||||
isFirstRender.current = false;
|
||||
return;
|
||||
}
|
||||
debouncedUserDataUpdate();
|
||||
}, [timeZone, lastName, firstName]);
|
||||
|
||||
return (
|
||||
<ScrollView style={{paddingBottom: 100, flex: 1}}>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.subTit}>Your Profile</Text>
|
||||
<View row spread paddingH-15 centerV marginV-15>
|
||||
<ImageBackground
|
||||
style={styles.pfp}
|
||||
source={require("../../../../assets/images/profile-picture.png")}
|
||||
/>
|
||||
|
||||
<Text style={styles.photoSet} color="#50be0c">
|
||||
Change Photo
|
||||
</Text>
|
||||
<Text style={styles.photoSet}>Remove Photo</Text>
|
||||
</View>
|
||||
<View paddingH-15>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
First name
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="First name"
|
||||
style={styles.txtBox}
|
||||
value={firstName}
|
||||
onChangeText={async (value) => {
|
||||
setFirstName(value);
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Last name
|
||||
</Text>
|
||||
<TextField
|
||||
text70
|
||||
placeholder="Last name"
|
||||
style={styles.txtBox}
|
||||
value={lastName}
|
||||
onChangeText={async (value) => {
|
||||
setLastName(value);
|
||||
}}
|
||||
/>
|
||||
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
||||
Email address
|
||||
</Text>
|
||||
<TextField
|
||||
editable={false}
|
||||
text70
|
||||
placeholder="Email address"
|
||||
value={user?.email?.toString()}
|
||||
style={styles.txtBox}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.subTit}>Settings</Text>
|
||||
<Text style={styles.jakarta12}>Time Zone</Text>
|
||||
<View style={styles.viewPicker}>
|
||||
<Picker
|
||||
// editable={!isLoading}
|
||||
value={timeZone}
|
||||
onChange={(item) => {
|
||||
setTimeZone(item as string)
|
||||
}}
|
||||
showSearch
|
||||
floatingPlaceholder
|
||||
style={styles.inViewPicker}
|
||||
trailingAccessory={
|
||||
<View style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
marginTop: -38,
|
||||
paddingRight: 15
|
||||
}}>
|
||||
<Ionicons name={"chevron-down"} style={{alignSelf: "center"}} size={20}
|
||||
color={"#000000"}/>
|
||||
</View>
|
||||
}
|
||||
>
|
||||
{timeZoneItems}
|
||||
</Picker>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const timeZoneItems = Object.keys(tz.zones).sort().map((zone) => (
|
||||
<Picker.Item key={zone} label={zone.replace("/", " / ").replace("_", " ")} value={zone}/>
|
||||
));
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
marginVertical: 15,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 21,
|
||||
},
|
||||
pfp: {
|
||||
aspectRatio: 1,
|
||||
width: 65.54,
|
||||
backgroundColor: "green",
|
||||
borderRadius: 20,
|
||||
},
|
||||
txtBox: {
|
||||
backgroundColor: "#fafafa",
|
||||
borderRadius: 50,
|
||||
borderWidth: 2,
|
||||
borderColor: "#cecece",
|
||||
padding: 15,
|
||||
height: 45,
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13
|
||||
},
|
||||
subTit: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
label: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 12,
|
||||
color: "#a1a1a1"
|
||||
},
|
||||
photoSet:{
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13.07
|
||||
}
|
||||
card: {
|
||||
marginVertical: 15,
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 21,
|
||||
},
|
||||
pfp: {
|
||||
aspectRatio: 1,
|
||||
width: 65.54,
|
||||
backgroundColor: "green",
|
||||
borderRadius: 20,
|
||||
},
|
||||
txtBox: {
|
||||
backgroundColor: "#fafafa",
|
||||
borderRadius: 50,
|
||||
borderWidth: 2,
|
||||
borderColor: "#cecece",
|
||||
padding: 15,
|
||||
height: 45,
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13
|
||||
},
|
||||
subTit: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
label: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 12,
|
||||
color: "#a1a1a1"
|
||||
},
|
||||
photoSet: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13.07
|
||||
},
|
||||
jakarta12: {
|
||||
paddingVertical: 10,
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 12,
|
||||
color: "#a1a1a1",
|
||||
},
|
||||
picker: {
|
||||
borderRadius: 50,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
backgroundColor: Colors.grey80,
|
||||
marginBottom: 16,
|
||||
borderColor: Colors.grey50,
|
||||
borderWidth: 1,
|
||||
marginTop: -20,
|
||||
height: 40,
|
||||
zIndex: 10,
|
||||
},
|
||||
viewPicker: {
|
||||
borderRadius: 50,
|
||||
backgroundColor: Colors.grey80,
|
||||
marginBottom: 16,
|
||||
borderColor: Colors.grey50,
|
||||
borderWidth: 1,
|
||||
marginTop: 0,
|
||||
height: 40,
|
||||
zIndex: 10,
|
||||
},
|
||||
inViewPicker: {
|
||||
borderRadius: 50,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
marginTop: -20,
|
||||
height: 40,
|
||||
zIndex: 10,
|
||||
},
|
||||
});
|
||||
|
||||
export default MyProfile;
|
||||
|
@ -24,6 +24,8 @@ export interface UserProfile {
|
||||
googleMail?: string | null;
|
||||
outlookMail?: string | null;
|
||||
appleMail?: string | null;
|
||||
timeZone?: string | null;
|
||||
firstDayOfWeek?: string | null;
|
||||
}
|
||||
|
||||
export interface ParentProfile extends UserProfile {
|
||||
|
@ -6,7 +6,7 @@ import {useAtomValue} from "jotai";
|
||||
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
|
||||
|
||||
export const useGetEvents = () => {
|
||||
const { user, profileData } = useAuthContext();
|
||||
const {user, profileData} = useAuthContext();
|
||||
const isFamilyView = useAtomValue(isFamilyViewAtom)
|
||||
|
||||
return useQuery({
|
||||
@ -43,5 +43,7 @@ export const useGetEvents = () => {
|
||||
};
|
||||
}));
|
||||
},
|
||||
staleTime: Infinity,
|
||||
cacheTime: Infinity
|
||||
});
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ import auth from "@react-native-firebase/auth";
|
||||
import { ProfileType } from "@/contexts/AuthContext";
|
||||
import { useSetUserData } from "./useSetUserData";
|
||||
import {uuidv4} from "@firebase/util";
|
||||
import * as Localization from "expo-localization";
|
||||
|
||||
export const useSignUp = () => {
|
||||
const { mutateAsync: setUserData } = useSetUserData();
|
||||
@ -30,6 +31,7 @@ export const useSignUp = () => {
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
familyId: uuidv4(),
|
||||
timeZone: Localization.getCalendars()[0].timeZone,
|
||||
},
|
||||
customUser: res.user,
|
||||
});
|
||||
|
@ -1,11 +1,11 @@
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
import { FirebaseAuthTypes } from "@react-native-firebase/auth";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { UserProfile } from "@/hooks/firebase/types/profileTypes";
|
||||
import {FirebaseAuthTypes} from "@react-native-firebase/auth";
|
||||
import {useMutation, useQueryClient} from "react-query";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||
|
||||
export const useUpdateUserData = () => {
|
||||
const { user: currentUser, refreshProfileData } = useAuthContext();
|
||||
const {user: currentUser, refreshProfileData} = useAuthContext();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
@ -17,7 +17,7 @@ export const useUpdateUserData = () => {
|
||||
newUserData: Partial<UserProfile>;
|
||||
customUser?: FirebaseAuthTypes.User;
|
||||
}) => {
|
||||
console.log("Mutation function called with data:", { newUserData, customUser });
|
||||
console.log("Mutation function called with data:", {newUserData, customUser});
|
||||
|
||||
const user = currentUser ?? customUser;
|
||||
|
||||
|
32
hooks/useFetchAndSaveAppleEvents.ts
Normal file
32
hooks/useFetchAndSaveAppleEvents.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {useMutation} from "react-query";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
|
||||
import {fetchiPhoneCalendarEvents} from "@/calendar-integration/apple-calendar-utils";
|
||||
|
||||
export const useFetchAndSaveAppleEvents = () => {
|
||||
const {profileData} = useAuthContext();
|
||||
const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["fetchAndSaveAppleEvents"],
|
||||
mutationFn: async (token?: string, email?: string) => {
|
||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
|
||||
try {
|
||||
const response = await fetchiPhoneCalendarEvents(
|
||||
profileData?.familyId!,
|
||||
email,
|
||||
timeMin,
|
||||
timeMax
|
||||
);
|
||||
|
||||
console.log(response);
|
||||
const items = response ?? [];
|
||||
await createEventsFromProvider(items);
|
||||
} catch (error) {
|
||||
console.error("Error fetching and saving Apple Calendar events: ", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
45
hooks/useFetchAndSaveGoogleEvents.ts
Normal file
45
hooks/useFetchAndSaveGoogleEvents.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {useMutation} from "react-query";
|
||||
import {fetchGoogleCalendarEvents} from "@/calendar-integration/google-calendar-utils";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
|
||||
|
||||
export const useFetchAndSaveGoogleEvents = () => {
|
||||
const {profileData} = useAuthContext();
|
||||
const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["fetchAndSaveGoogleEvents"],
|
||||
mutationFn: async (token?: string, email?: string) => {
|
||||
console.log("Fetching Google Calendar events...");
|
||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
|
||||
|
||||
console.log("Token: ", token ?? profileData?.googleToken);
|
||||
|
||||
try {
|
||||
const response = await fetchGoogleCalendarEvents(
|
||||
token ?? profileData?.googleToken,
|
||||
email ?? profileData?.googleMail,
|
||||
profileData?.familyId,
|
||||
timeMin.toISOString().slice(0, -5) + "Z",
|
||||
timeMax.toISOString().slice(0, -5) + "Z"
|
||||
);
|
||||
|
||||
console.log("Google Calendar events fetched:", response);
|
||||
|
||||
const items = response?.map((item) => {
|
||||
if (item.allDay) {
|
||||
item.startDate = new Date(new Date(item.startDate).setHours(0, 0, 0, 0));
|
||||
item.endDate = item.startDate;
|
||||
}
|
||||
return item;
|
||||
}) || [];
|
||||
|
||||
await createEventsFromProvider(items);
|
||||
} catch (error) {
|
||||
console.error("Error fetching Google Calendar events:", error);
|
||||
throw error; // Ensure errors are propagated to the mutation
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
36
hooks/useFetchAndSaveOutlookEvents.ts
Normal file
36
hooks/useFetchAndSaveOutlookEvents.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { useMutation } from "react-query";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useCreateEventsFromProvider } from "@/hooks/firebase/useCreateEvent";
|
||||
import { fetchMicrosoftCalendarEvents } from "@/calendar-integration/microsoft-calendar-utils";
|
||||
|
||||
export const useFetchAndSaveOutlookEvents = () => {
|
||||
const { profileData } = useAuthContext();
|
||||
const { mutateAsync: createEventsFromProvider } = useCreateEventsFromProvider();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["fetchAndSaveOutlookEvents"],
|
||||
mutationFn: async (token?: string, email?: string) => {
|
||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 3));
|
||||
|
||||
console.log("Token: ", token ?? profileData?.microsoftToken);
|
||||
|
||||
try {
|
||||
const response = await fetchMicrosoftCalendarEvents(
|
||||
token ?? profileData?.microsoftToken,
|
||||
email ?? profileData?.outlookMail,
|
||||
profileData?.familyId,
|
||||
timeMin.toISOString().slice(0, -5) + "Z",
|
||||
timeMax.toISOString().slice(0, -5) + "Z"
|
||||
);
|
||||
|
||||
console.log(response);
|
||||
const items = response ?? [];
|
||||
await createEventsFromProvider(items);
|
||||
} catch (error) {
|
||||
console.error("Error fetching and saving Outlook events: ", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
@ -1190,8 +1190,12 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- ExpoAppleAuthentication (6.4.2):
|
||||
- ExpoModulesCore
|
||||
- ExpoAsset (10.0.10):
|
||||
- ExpoModulesCore
|
||||
- ExpoCalendar (13.0.5):
|
||||
- ExpoModulesCore
|
||||
- ExpoCamera (15.0.16):
|
||||
- ExpoModulesCore
|
||||
- ZXingObjC/OneD
|
||||
@ -1210,6 +1214,8 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- ExpoKeepAwake (13.0.2):
|
||||
- ExpoModulesCore
|
||||
- ExpoLocalization (15.0.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoModulesCore (1.12.24):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
@ -2802,7 +2808,9 @@ DEPENDENCIES:
|
||||
- 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`)
|
||||
- ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`)
|
||||
- ExpoAsset (from `../node_modules/expo-asset/ios`)
|
||||
- ExpoCalendar (from `../node_modules/expo-calendar/ios`)
|
||||
- ExpoCamera (from `../node_modules/expo-camera/ios`)
|
||||
- ExpoCrypto (from `../node_modules/expo-crypto/ios`)
|
||||
- ExpoDevice (from `../node_modules/expo-device/ios`)
|
||||
@ -2811,6 +2819,7 @@ DEPENDENCIES:
|
||||
- ExpoHead (from `../node_modules/expo-router/ios`)
|
||||
- ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
|
||||
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||
- ExpoLocalization (from `../node_modules/expo-localization/ios`)
|
||||
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
|
||||
- ExpoSystemUI (from `../node_modules/expo-system-ui/ios`)
|
||||
- ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`)
|
||||
@ -2956,8 +2965,12 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/expo-dev-menu"
|
||||
expo-dev-menu-interface:
|
||||
:path: "../node_modules/expo-dev-menu-interface/ios"
|
||||
ExpoAppleAuthentication:
|
||||
:path: "../node_modules/expo-apple-authentication/ios"
|
||||
ExpoAsset:
|
||||
:path: "../node_modules/expo-asset/ios"
|
||||
ExpoCalendar:
|
||||
:path: "../node_modules/expo-calendar/ios"
|
||||
ExpoCamera:
|
||||
:path: "../node_modules/expo-camera/ios"
|
||||
ExpoCrypto:
|
||||
@ -2974,6 +2987,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/expo-image-picker/ios"
|
||||
ExpoKeepAwake:
|
||||
:path: "../node_modules/expo-keep-awake/ios"
|
||||
ExpoLocalization:
|
||||
:path: "../node_modules/expo-localization/ios"
|
||||
ExpoModulesCore:
|
||||
:path: "../node_modules/expo-modules-core"
|
||||
ExpoSystemUI:
|
||||
@ -3142,7 +3157,9 @@ SPEC CHECKSUMS:
|
||||
expo-dev-launcher: fe4f2c0a0aa627449eeaec5f9f11e04090f97c40
|
||||
expo-dev-menu: 5b14897ecce3a8cf9e9cf9109344c2c192a3766a
|
||||
expo-dev-menu-interface: be32c09f1e03833050f0ee290dcc86b3ad0e73e4
|
||||
ExpoAppleAuthentication: 265219fa0ba1110872079f55f56686b9737b0065
|
||||
ExpoAsset: 323700f291684f110fb55f0d4022a3362ea9f875
|
||||
ExpoCalendar: 135beb39ea3795f854a4ea287a49f74c9203ce51
|
||||
ExpoCamera: 929be541d1c1319fcf32f9f5d9df8b97804346b5
|
||||
ExpoCrypto: 156078f266bf28f80ecf5e2a9c3a0d6ffce07a1c
|
||||
ExpoDevice: fc94f0e42ecdfd897e7590f2874fc64dfa7e9b1c
|
||||
@ -3151,6 +3168,7 @@ SPEC CHECKSUMS:
|
||||
ExpoHead: fcb28a68ed4ba28f177394d2dfb8a0a8824cd103
|
||||
ExpoImagePicker: 12a420923383ae38dccb069847218f27a3b87816
|
||||
ExpoKeepAwake: 3b8815d9dd1d419ee474df004021c69fdd316d08
|
||||
ExpoLocalization: f04eeec2e35bed01ab61c72ee1768ec04d093d01
|
||||
ExpoModulesCore: db3e31e694684f08223d713e89f7648c6d3e04d0
|
||||
ExpoSystemUI: d4f065a016cae6721b324eb659cdee4d4cf0cb26
|
||||
ExpoWebBrowser: 7595ccac6938eb65b076385fd23d035db9ecdc8e
|
||||
|
@ -178,7 +178,9 @@
|
||||
LastUpgradeCheck = 1130;
|
||||
TargetAttributes = {
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
DevelopmentTeam = MV9C3PHV87;
|
||||
LastSwiftMigration = 1250;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -302,6 +304,7 @@
|
||||
"${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}/ExpoLocalization/ExpoLocalization_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseAuth/FirebaseAuth_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle",
|
||||
@ -337,6 +340,7 @@
|
||||
"${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}/ExpoLocalization_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseAuth_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle",
|
||||
@ -421,7 +425,10 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = cally/cally.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = MV9C3PHV87;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
@ -441,7 +448,7 @@
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
|
||||
PRODUCT_NAME = CallyFamilyPlanner;
|
||||
PRODUCT_NAME = "Cally";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@ -457,7 +464,10 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = cally/cally.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = MV9C3PHV87;
|
||||
INFOPLIST_FILE = cally/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -472,7 +482,7 @@
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
|
||||
PRODUCT_NAME = CallyFamilyPlanner;
|
||||
PRODUCT_NAME = "Cally";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
@ -4,10 +4,12 @@
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Cally - Family Planner</string>
|
||||
<string>Cally.</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@ -55,12 +57,20 @@
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSCalendarsFullAccessUsageDescription</key>
|
||||
<string>The app needs to access your calendar.</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>The app needs to access your calendar.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your camera</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your microphone</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your photos</string>
|
||||
<key>NSRemindersFullAccessUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your reminders</string>
|
||||
<key>NSRemindersUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your reminders</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
@ -92,6 +102,12 @@
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
|
@ -4,5 +4,9 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
@ -44,9 +44,11 @@
|
||||
"debounce": "^2.1.1",
|
||||
"expo": "~51.0.24",
|
||||
"expo-app-loading": "^2.1.1",
|
||||
"expo-apple-authentication": "~6.4.2",
|
||||
"expo-auth-session": "^5.5.2",
|
||||
"expo-barcode-scanner": "~13.0.1",
|
||||
"expo-build-properties": "~0.12.4",
|
||||
"expo-calendar": "~13.0.5",
|
||||
"expo-camera": "~15.0.16",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-dev-client": "~4.0.27",
|
||||
@ -54,6 +56,7 @@
|
||||
"expo-font": "~12.0.10",
|
||||
"expo-image-picker": "~15.0.7",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-localization": "~15.0.3",
|
||||
"expo-notifications": "~0.28.18",
|
||||
"expo-router": "~3.5.20",
|
||||
"expo-splash-screen": "~0.27.5",
|
||||
@ -84,7 +87,9 @@
|
||||
"react-native-toast-message": "^2.2.1",
|
||||
"react-native-ui-lib": "^7.27.0",
|
||||
"react-native-web": "~0.19.10",
|
||||
"react-query": "^3.39.3"
|
||||
"react-query": "^3.39.3",
|
||||
"timezonecomplete": "^5.13.1",
|
||||
"tzdata": "^1.0.42"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
|
39
yarn.lock
39
yarn.lock
@ -5001,6 +5001,11 @@ expo-app-loading@^2.1.1:
|
||||
dependencies:
|
||||
expo-splash-screen "~0.17.0"
|
||||
|
||||
expo-apple-authentication@~6.4.2:
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/expo-apple-authentication/-/expo-apple-authentication-6.4.2.tgz#1c2ea4fcbd1de5736483dccd370cdd6b8e3de15d"
|
||||
integrity sha512-X4u1n3Ql1hOpztXHbKNq4I1l4+Ff82gC6RmEeW43Eht7VE6E8PrQBpYKw+JJv8osrCJt7R5O1PZwed6WLN5oig==
|
||||
|
||||
expo-application@~5.9.0:
|
||||
version "5.9.1"
|
||||
resolved "https://registry.npmjs.org/expo-application/-/expo-application-5.9.1.tgz"
|
||||
@ -5042,6 +5047,11 @@ expo-build-properties@~0.12.4:
|
||||
ajv "^8.11.0"
|
||||
semver "^7.6.0"
|
||||
|
||||
expo-calendar@~13.0.5:
|
||||
version "13.0.5"
|
||||
resolved "https://registry.yarnpkg.com/expo-calendar/-/expo-calendar-13.0.5.tgz#cdc85978cb59d99d6fc9a4fcd2c33d56cc7c8008"
|
||||
integrity sha512-Wkk7eHvlyhWz2csxU6guYA2HFcLUfYpmlsdMy4a6bneBmFqIZG/ldnLKq/lcQ+BCrfI3fOULt3aNdF6SlZtLlw==
|
||||
|
||||
expo-camera@~15.0.16:
|
||||
version "15.0.16"
|
||||
resolved "https://registry.npmjs.org/expo-camera/-/expo-camera-15.0.16.tgz"
|
||||
@ -5153,6 +5163,13 @@ expo-linking@~6.3.0, expo-linking@~6.3.1:
|
||||
expo-constants "~16.0.0"
|
||||
invariant "^2.2.4"
|
||||
|
||||
expo-localization@~15.0.3:
|
||||
version "15.0.3"
|
||||
resolved "https://registry.yarnpkg.com/expo-localization/-/expo-localization-15.0.3.tgz#772c89b3ab9c925b7eca6911a11ca33980c2b674"
|
||||
integrity sha512-IfcmlKuKRlowR9qIzL0e+nGHBeNoF7l2GQaOJstc7HZiPjNJ4J1R4D53ZNf483dt7JSkTRJBihdTadOtOEjRdg==
|
||||
dependencies:
|
||||
rtl-detect "^1.0.2"
|
||||
|
||||
expo-manifests@~0.14.0:
|
||||
version "0.14.3"
|
||||
resolved "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.14.3.tgz"
|
||||
@ -9276,6 +9293,11 @@ rimraf@~2.6.2:
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rtl-detect@^1.0.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.1.2.tgz#ca7f0330af5c6bb626c15675c642ba85ad6273c6"
|
||||
integrity sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==
|
||||
|
||||
run-parallel@^1.1.9:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
|
||||
@ -10124,6 +10146,13 @@ through@2:
|
||||
resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
|
||||
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
|
||||
|
||||
timezonecomplete@^5.13.1:
|
||||
version "5.13.1"
|
||||
resolved "https://registry.yarnpkg.com/timezonecomplete/-/timezonecomplete-5.13.1.tgz#72c05e82b33013bacc7a38e5d554eafc7914b31f"
|
||||
integrity sha512-41o3TTExXQ03jQML12Tk64b5TYeEy968Umq5vya+08sFWCcYw5fNqrHubD1vj/JGN1NYhFFBCS09rOL3b7nM2w==
|
||||
dependencies:
|
||||
tzdata "1.0.25"
|
||||
|
||||
tinycolor2@^1.4.1, tinycolor2@^1.4.2:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz"
|
||||
@ -10313,6 +10342,16 @@ typical@^2.6.0:
|
||||
resolved "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz"
|
||||
integrity sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==
|
||||
|
||||
tzdata@1.0.25:
|
||||
version "1.0.25"
|
||||
resolved "https://registry.yarnpkg.com/tzdata/-/tzdata-1.0.25.tgz#e8839033c05761e04ef552242e779777becb13d0"
|
||||
integrity sha512-yAZ/Tv/tBFIPHJGYrOexxW8Swyjszt7rDhIjnIPSqLaP8mzrr3T7D0w4cxQBtToXnQrlFqkEU0stGC/auz0JcQ==
|
||||
|
||||
tzdata@^1.0.42:
|
||||
version "1.0.42"
|
||||
resolved "https://registry.yarnpkg.com/tzdata/-/tzdata-1.0.42.tgz#4f278809b50c50e9c865e44969aa7e746b165638"
|
||||
integrity sha512-hVA4V8g27yz1YB4Ty4UliwJlWrFOoFrFBYFMd9rKUlRlaF+9Fl3gyzxF/+MQOtCH50pPE+XZ/bYOYkRnBDscVQ==
|
||||
|
||||
ua-parser-js@^0.7.33:
|
||||
version "0.7.39"
|
||||
resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz"
|
||||
|
Reference in New Issue
Block a user