Configure Expo mobile project and update Android/iOS bundles
Initialize iOS and Android native projects for the Time to Leave app. Rename Android package to com.floegger.timetoleave and add iOS project files. Add FontAwesome icons, calendar types, and dependencies.
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
> Why do I have a folder named ".expo" in my project?
|
||||||
|
|
||||||
|
The ".expo" folder is created when an Expo project is started using "expo start" command.
|
||||||
|
|
||||||
|
> What do the files contain?
|
||||||
|
|
||||||
|
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
|
||||||
|
- "settings.json": contains the server configuration that is used to serve the application manifest.
|
||||||
|
|
||||||
|
> Should I commit the ".expo" folder?
|
||||||
|
|
||||||
|
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
|
||||||
|
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"devices": []
|
||||||
|
}
|
||||||
@@ -87,13 +87,13 @@ android {
|
|||||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||||
compileSdk rootProject.ext.compileSdkVersion
|
compileSdk rootProject.ext.compileSdkVersion
|
||||||
|
|
||||||
namespace "com.timetoleave"
|
namespace 'com.floegger.timetoleave'
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.timetoleave"
|
applicationId 'com.floegger.timetoleave'
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0.0"
|
||||||
|
|
||||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<!-- OPTIONAL PERMISSIONS, REMOVE WHATEVER YOU DO NOT NEED -->
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<!-- These require runtime permissions on M -->
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
<!-- END OPTIONAL PERMISSIONS -->
|
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<!-- Support checking for http(s) links via the Linking API -->
|
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https"/>
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false">
|
||||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" android:supportsRtl="true">
|
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||||
|
<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"/>
|
||||||
<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">
|
<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">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
<data android:scheme="exp+time-to-leave"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.timetoleave
|
package com.floegger.timetoleave
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.timetoleave
|
package com.floegger.timetoleave
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<resources/>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<resources>
|
<resources>
|
||||||
<color name="splashscreen_background">#FFFFFF</color>
|
<color name="splashscreen_background">#FFFFFF</color>
|
||||||
|
<color name="colorPrimary">#023c69</color>
|
||||||
|
<color name="colorPrimaryDark">#ffffff</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">time-to-leave</string>
|
<string name="app_name">time-to-leave</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||||
|
<item name="android:enforceNavigationBarContrast" tools:targetApi="29">true</item>
|
||||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="android:statusBarColor">#ffffff</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="Theme.App.SplashScreen" parent="AppTheme">
|
<style name="Theme.App.SplashScreen" parent="AppTheme">
|
||||||
<item name="android:windowBackground">@drawable/splashscreen_logo</item>
|
<item name="android:windowBackground">@drawable/ic_launcher_background</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -59,3 +59,7 @@ EX_DEV_CLIENT_NETWORK_INSPECTOR=true
|
|||||||
|
|
||||||
# Use legacy packaging to compress native libraries in the resulting APK.
|
# Use legacy packaging to compress native libraries in the resulting APK.
|
||||||
expo.useLegacyPackaging=false
|
expo.useLegacyPackaging=false
|
||||||
|
|
||||||
|
# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
|
||||||
|
# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
|
||||||
|
expo.edgeToEdgeEnabled=true
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
|
|||||||
}
|
}
|
||||||
expoAutolinking.useExpoModules()
|
expoAutolinking.useExpoModules()
|
||||||
|
|
||||||
rootProject.name = 'timetoleave'
|
rootProject.name = 'time-to-leave'
|
||||||
|
|
||||||
expoAutolinking.useExpoVersionCatalog()
|
expoAutolinking.useExpoVersionCatalog()
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
||||||
|
"@fortawesome/react-native-fontawesome": "^1.0.0",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-navigation/native": "^7.2.4",
|
"@react-navigation/native": "^7.2.4",
|
||||||
"@react-navigation/native-stack": "^7.14.14",
|
"@react-navigation/native-stack": "^7.14.14",
|
||||||
@@ -27,7 +29,8 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.5",
|
"react-native": "0.81.5",
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
"react-native-screens": "~4.16.0"
|
"react-native-screens": "~4.16.0",
|
||||||
|
"react-native-svg": "^15.15.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.4",
|
"@eslint/js": "^9.39.4",
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { Image, Text, View } from 'react-native';
|
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
|
||||||
|
import { faBars } from '@fortawesome/free-solid-svg-icons/faBars';
|
||||||
|
import { Image, Modal, Pressable, StyleSheet, Text, View } from 'react-native';
|
||||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
import { useState } from 'react';
|
||||||
import { EventListScreen } from '../screens/EventListScreen';
|
import { EventListScreen } from '../screens/EventListScreen';
|
||||||
import { EventDetailScreen } from '../screens/EventDetailScreen';
|
import { EventDetailScreen } from '../screens/EventDetailScreen';
|
||||||
import { AddEventScreen } from '../screens/AddEventScreen';
|
import { AddEventScreen } from '../screens/AddEventScreen';
|
||||||
@@ -14,6 +17,57 @@ import navLogo from '../../assets/nav-logo.png';
|
|||||||
// ── Root Stack ──
|
// ── Root Stack ──
|
||||||
|
|
||||||
const Root = createNativeStackNavigator<RootStack>();
|
const Root = createNativeStackNavigator<RootStack>();
|
||||||
|
const byPrefixAndName = { fas: { bars: faBars } };
|
||||||
|
|
||||||
|
type HeaderMenuProps = {
|
||||||
|
navigation: {
|
||||||
|
navigate: (screen: 'CalendarImport' | 'Settings') => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function HeaderMenu({ navigation }: HeaderMenuProps) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const navigateTo = (screen: 'CalendarImport' | 'Settings') => {
|
||||||
|
setIsOpen(false);
|
||||||
|
navigation.navigate(screen);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Pressable
|
||||||
|
accessibilityLabel="Open menu"
|
||||||
|
accessibilityRole="button"
|
||||||
|
hitSlop={12}
|
||||||
|
onPress={() => setIsOpen(true)}
|
||||||
|
style={({ pressed }) => [styles.menuButton, pressed && styles.menuButtonPressed]}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={byPrefixAndName.fas['bars']} color="#F4F1EA" size={22} />
|
||||||
|
</Pressable>
|
||||||
|
|
||||||
|
<Modal animationType="fade" transparent visible={isOpen} onRequestClose={() => setIsOpen(false)}>
|
||||||
|
<Pressable style={styles.menuOverlay} onPress={() => setIsOpen(false)}>
|
||||||
|
<View style={styles.menuPanel}>
|
||||||
|
<Pressable
|
||||||
|
accessibilityRole="menuitem"
|
||||||
|
onPress={() => navigateTo('CalendarImport')}
|
||||||
|
style={({ pressed }) => [styles.menuItem, pressed && styles.menuItemPressed]}
|
||||||
|
>
|
||||||
|
<Text style={styles.menuItemText}>Calendar</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
accessibilityRole="menuitem"
|
||||||
|
onPress={() => navigateTo('Settings')}
|
||||||
|
style={({ pressed }) => [styles.menuItem, pressed && styles.menuItemPressed]}
|
||||||
|
>
|
||||||
|
<Text style={styles.menuItemText}>Settings</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root native stack navigator for the app.
|
* Root native stack navigator for the app.
|
||||||
@@ -27,16 +81,17 @@ export default function AppNavigator() {
|
|||||||
<NavigationContainer>
|
<NavigationContainer>
|
||||||
<Root.Navigator
|
<Root.Navigator
|
||||||
initialRouteName="EventList"
|
initialRouteName="EventList"
|
||||||
screenOptions={{
|
screenOptions={({ navigation }) => ({
|
||||||
headerStyle: { backgroundColor: '#17112A' },
|
headerStyle: { backgroundColor: '#17112A' },
|
||||||
headerTintColor: '#F4F1EA',
|
headerTintColor: '#F4F1EA',
|
||||||
|
headerRight: () => <HeaderMenu navigation={navigation} />,
|
||||||
headerTitle: () => (
|
headerTitle: () => (
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||||
<Image source={navLogo} style={{ width: 28, height: 28, borderRadius: 6 }} resizeMode="contain" />
|
<Image source={navLogo} style={{ width: 28, height: 28, borderRadius: 6 }} resizeMode="contain" />
|
||||||
<Text style={{ color: '#F4F1EA', fontSize: 18, fontWeight: '600' }}>Time To Leave</Text>
|
<Text style={{ color: '#F4F1EA', fontSize: 18, fontWeight: '600' }}>Time To Leave</Text>
|
||||||
</View>
|
</View>
|
||||||
),
|
),
|
||||||
}}
|
})}
|
||||||
>
|
>
|
||||||
<Root.Screen name="EventList" component={EventListScreen} />
|
<Root.Screen name="EventList" component={EventListScreen} />
|
||||||
<Root.Screen name="AddEvent" component={AddEventScreen} />
|
<Root.Screen name="AddEvent" component={AddEventScreen} />
|
||||||
@@ -49,3 +104,42 @@ export default function AppNavigator() {
|
|||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
menuButton: {
|
||||||
|
alignItems: 'center',
|
||||||
|
height: 40,
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 40,
|
||||||
|
},
|
||||||
|
menuButtonPressed: {
|
||||||
|
opacity: 0.65,
|
||||||
|
},
|
||||||
|
menuOverlay: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
backgroundColor: 'rgba(9, 8, 22, 0.45)',
|
||||||
|
flex: 1,
|
||||||
|
paddingRight: 12,
|
||||||
|
paddingTop: 72,
|
||||||
|
},
|
||||||
|
menuPanel: {
|
||||||
|
backgroundColor: '#17112A',
|
||||||
|
borderColor: '#3B3157',
|
||||||
|
borderRadius: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
minWidth: 168,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
menuItem: {
|
||||||
|
paddingHorizontal: 18,
|
||||||
|
paddingVertical: 14,
|
||||||
|
},
|
||||||
|
menuItemPressed: {
|
||||||
|
backgroundColor: '#2A2140',
|
||||||
|
},
|
||||||
|
menuItemText: {
|
||||||
|
color: '#F4F1EA',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
@@ -11,9 +11,9 @@ import {
|
|||||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import type { RouteProp } from '@react-navigation/native';
|
import type { RouteProp } from '@react-navigation/native';
|
||||||
import { api } from '../services/api';
|
import { api } from '../services/api';
|
||||||
import { fetchNativeEvents } from '../services/calendar';
|
import { fetchNativeEvents, getSelectableCalendars, groupCalendarsByType } from '../services/calendar';
|
||||||
import { addEvent, loadEvents } from '../store/eventStore';
|
import { addEvent, getSelectedCalendarIds, hasCalendarSelection, loadEvents, saveSelectedCalendarIds } from '../store/eventStore';
|
||||||
import type { Event as CalendarEvent } from '@timetoleave/core';
|
import type { Event as CalendarEvent, CalendarAccountType, SelectableCalendar } from '@timetoleave/core';
|
||||||
import type { RootStack } from '../types/navigation';
|
import type { RootStack } from '../types/navigation';
|
||||||
import { useColors } from '../hooks/useColors';
|
import { useColors } from '../hooks/useColors';
|
||||||
|
|
||||||
@@ -22,9 +22,81 @@ type ScreenProps = {
|
|||||||
route: RouteProp<RootStack, 'CalendarImport'>;
|
route: RouteProp<RootStack, 'CalendarImport'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display order for calendar source type groups.
|
||||||
|
*/
|
||||||
|
const GROUP_ORDER: CalendarAccountType[] = [
|
||||||
|
'caldav',
|
||||||
|
'mobileme',
|
||||||
|
'google',
|
||||||
|
'exchange',
|
||||||
|
'subscriptions',
|
||||||
|
'local',
|
||||||
|
'carddav',
|
||||||
|
'activesync',
|
||||||
|
'other',
|
||||||
|
'none',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Badge color for each account type. */
|
||||||
|
const BADGE_COLORS: Record<string, string> = {
|
||||||
|
caldav: '#34C759',
|
||||||
|
mobileme: '#FF9500',
|
||||||
|
google: '#4285F4',
|
||||||
|
exchange: '#0078D4',
|
||||||
|
subscriptions: '#AF52DE',
|
||||||
|
local: '#8E8E93',
|
||||||
|
carddav: '#5AC8FA',
|
||||||
|
activesync: '#FF2D55',
|
||||||
|
other: '#8E8E93',
|
||||||
|
none: '#8E8E93',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Calendar checkbox row component. */
|
||||||
|
function CalendarCheckbox({
|
||||||
|
calendar,
|
||||||
|
selected,
|
||||||
|
onToggle,
|
||||||
|
colors,
|
||||||
|
}: {
|
||||||
|
calendar: SelectableCalendar;
|
||||||
|
selected: boolean;
|
||||||
|
onToggle: (id: string) => void;
|
||||||
|
colors: ReturnType<typeof useColors>;
|
||||||
|
}) {
|
||||||
|
const badgeColor = BADGE_COLORS[calendar.accountType] ?? BADGE_COLORS.other;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.calendarRow, { borderColor: selected ? colors.accent : colors.border }]}
|
||||||
|
onPress={() => onToggle(calendar.id)}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel={`${calendar.name} – ${calendar.sourceInfo.label}`}
|
||||||
|
>
|
||||||
|
<View style={[styles.checkBox, selected && styles.checkBoxSelected]}>
|
||||||
|
{selected && <Text style={styles.checkMark}>✓</Text>}
|
||||||
|
</View>
|
||||||
|
<View style={styles.calendarInfo}>
|
||||||
|
<Text style={[styles.calendarName, { color: colors.text }]}>
|
||||||
|
{calendar.sourceInfo.emoji} {calendar.name}
|
||||||
|
</Text>
|
||||||
|
<View style={styles.badgeRow}>
|
||||||
|
<View style={[styles.badge, { backgroundColor: badgeColor }]}>
|
||||||
|
<Text style={styles.badgeText}>{calendar.sourceInfo.badge}</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={[styles.accountTypeText, { color: colors.subtext }]}>
|
||||||
|
{calendar.sourceInfo.label}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Screen for importing events from either an ICS calendar URL or the device's
|
* Screen for importing events from either an ICS calendar URL or the device's
|
||||||
* native calendar. Deduplicates against already-imported events by ID.
|
* native calendar. Includes calendar selection so the user can choose which
|
||||||
|
* calendars to sync. Deduplicates against already-imported events by ID.
|
||||||
*/
|
*/
|
||||||
export function CalendarImportScreen({ navigation }: ScreenProps) {
|
export function CalendarImportScreen({ navigation }: ScreenProps) {
|
||||||
const colors = useColors();
|
const colors = useColors();
|
||||||
@@ -33,6 +105,57 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [count, setCount] = useState<number | null>(null);
|
const [count, setCount] = useState<number | null>(null);
|
||||||
|
|
||||||
|
// Calendar selection state
|
||||||
|
const [availableCalendars, setAvailableCalendars] = useState<SelectableCalendar[]>([]);
|
||||||
|
const [selectedCalendarIds, setSelectedCalendarIds] = useState<Set<string>>(new Set());
|
||||||
|
const [calendarsLoaded, setCalendarsLoaded] = useState(false);
|
||||||
|
const [hasSelection, setHasSelection] = useState(false);
|
||||||
|
const initialLoadRef = useRef(false);
|
||||||
|
|
||||||
|
// Load available calendars and persisted selection on mount
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialLoadRef.current) return;
|
||||||
|
initialLoadRef.current = true;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const [calendars, persistedIds, hasSel] = await Promise.all([
|
||||||
|
getSelectableCalendars(),
|
||||||
|
getSelectedCalendarIds(),
|
||||||
|
hasCalendarSelection(),
|
||||||
|
]);
|
||||||
|
setAvailableCalendars(calendars);
|
||||||
|
setSelectedCalendarIds(new Set(persistedIds));
|
||||||
|
setHasSelection(hasSel);
|
||||||
|
setCalendarsLoaded(true);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleCalendar = useCallback((id: string) => {
|
||||||
|
setSelectedCalendarIds((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(id)) {
|
||||||
|
next.delete(id);
|
||||||
|
} else {
|
||||||
|
next.add(id);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectAll = useCallback(() => {
|
||||||
|
setSelectedCalendarIds(new Set(availableCalendars.map((c) => c.id)));
|
||||||
|
}, [availableCalendars]);
|
||||||
|
|
||||||
|
const deselectAll = useCallback(() => {
|
||||||
|
setSelectedCalendarIds(new Set());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const saveSelection = useCallback(async () => {
|
||||||
|
const ids = Array.from(selectedCalendarIds);
|
||||||
|
await saveSelectedCalendarIds(ids);
|
||||||
|
setHasSelection(ids.length > 0);
|
||||||
|
}, [selectedCalendarIds]);
|
||||||
|
|
||||||
const handleImport = async () => {
|
const handleImport = async () => {
|
||||||
if (!url.trim()) {
|
if (!url.trim()) {
|
||||||
setError('Bitte ICS-URL eingeben');
|
setError('Bitte ICS-URL eingeben');
|
||||||
@@ -77,11 +200,19 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
setCount(null);
|
setCount(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Save the current selection before syncing
|
||||||
|
await saveSelection();
|
||||||
|
|
||||||
// Fetch events from the next 30 days
|
// Fetch events from the next 30 days
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
|
const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
const nativeEvents = await fetchNativeEvents(now, thirtyDaysLater);
|
// If a selection was made, sync only selected calendars
|
||||||
|
const calendarIds = hasSelection
|
||||||
|
? Array.from(selectedCalendarIds)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const nativeEvents = await fetchNativeEvents(now, thirtyDaysLater, calendarIds);
|
||||||
|
|
||||||
// Load existing events to avoid duplicates
|
// Load existing events to avoid duplicates
|
||||||
const existing = await loadEvents();
|
const existing = await loadEvents();
|
||||||
@@ -103,6 +234,9 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Group calendars by account type for display
|
||||||
|
const grouped = groupCalendarsByType(availableCalendars);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView style={[styles.container, { backgroundColor: colors.background }]}>
|
<ScrollView style={[styles.container, { backgroundColor: colors.background }]}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
@@ -111,6 +245,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
Importiere Termine über eine ICS-URL oder sync mit dem Geräte-Kalender.
|
Importiere Termine über eine ICS-URL oder sync mit dem Geräte-Kalender.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{/* ── ICS URL Import ── */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>ICS-URL Import</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>ICS-URL Import</Text>
|
||||||
|
|
||||||
@@ -137,6 +272,75 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* ── Calendar Selection ── */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Kalender auswählen</Text>
|
||||||
|
<View style={styles.selectionActions}>
|
||||||
|
<TouchableOpacity onPress={selectAll} accessibilityLabel="Alle auswählen">
|
||||||
|
<Text style={[styles.linkText, { color: colors.accent }]}>Alle</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={[styles.dividerText, { color: colors.subtext }]}> · </Text>
|
||||||
|
<TouchableOpacity onPress={deselectAll} accessibilityLabel="Alle abwählen">
|
||||||
|
<Text style={[styles.linkText, { color: colors.accent }]}>Keine</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={[styles.sectionDesc, { color: colors.subtext }]}>
|
||||||
|
Wähle die Kalender aus, die gesynct werden sollen. Ohne Auswahl werden alle Kalender verwendet.
|
||||||
|
{'\n'}
|
||||||
|
CalDAV-Quellen (DAVx5, Apple Calendar, etc.) werden automatisch erkannt.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{!calendarsLoaded ? (
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator color={colors.accent} />
|
||||||
|
<Text style={[styles.loadingText, { color: colors.subtext }]}>Kalender werden geladen…</Text>
|
||||||
|
</View>
|
||||||
|
) : availableCalendars.length === 0 ? (
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<Text style={[styles.emptyText, { color: colors.subtext }]}>
|
||||||
|
Keine Kalender auf diesem Gerät gefunden.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Render groups in defined order, then any remaining types */}
|
||||||
|
{GROUP_ORDER.map((type) => {
|
||||||
|
const group = grouped.get(type);
|
||||||
|
if (!group || group.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View key={type} style={styles.group}>
|
||||||
|
<Text style={[styles.groupLabel, { color: colors.subtext }]}>
|
||||||
|
{group[0].sourceInfo.emoji} {group[0].sourceInfo.label} ({group.length})
|
||||||
|
</Text>
|
||||||
|
{group.map((cal) => (
|
||||||
|
<CalendarCheckbox
|
||||||
|
key={cal.id}
|
||||||
|
calendar={cal}
|
||||||
|
selected={selectedCalendarIds.has(cal.id)}
|
||||||
|
onToggle={toggleCalendar}
|
||||||
|
colors={colors}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{hasSelection && (
|
||||||
|
<View style={[styles.selectionInfo, { backgroundColor: colors.highlight }]}>
|
||||||
|
<Text style={[styles.selectionInfoText, { color: colors.text }]}>
|
||||||
|
{selectedCalendarIds.size} von {availableCalendars.length} Kalendern ausgewählt
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ── Device Calendar Sync ── */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>Geräte-Kalender Sync</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Geräte-Kalender Sync</Text>
|
||||||
<Text style={[styles.sectionDesc, { color: colors.subtext }]}>
|
<Text style={[styles.sectionDesc, { color: colors.subtext }]}>
|
||||||
@@ -183,6 +387,12 @@ const styles = StyleSheet.create({
|
|||||||
heading: { fontSize: 22, fontWeight: '700', marginBottom: 4 },
|
heading: { fontSize: 22, fontWeight: '700', marginBottom: 4 },
|
||||||
description: { fontSize: 14, marginBottom: 20, lineHeight: 20 },
|
description: { fontSize: 14, marginBottom: 20, lineHeight: 20 },
|
||||||
section: { marginBottom: 24 },
|
section: { marginBottom: 24 },
|
||||||
|
sectionHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
sectionTitle: { fontSize: 16, fontWeight: '600', marginBottom: 8 },
|
sectionTitle: { fontSize: 16, fontWeight: '600', marginBottom: 8 },
|
||||||
sectionDesc: { fontSize: 13, marginBottom: 12, lineHeight: 18 },
|
sectionDesc: { fontSize: 13, marginBottom: 12, lineHeight: 18 },
|
||||||
input: {
|
input: {
|
||||||
@@ -210,4 +420,107 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
backBtnText: { fontSize: 15 },
|
backBtnText: { fontSize: 15 },
|
||||||
|
// Calendar selection
|
||||||
|
selectionActions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
linkText: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
dividerText: {
|
||||||
|
fontSize: 13,
|
||||||
|
marginHorizontal: 4,
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
groupLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
marginBottom: 6,
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
calendarRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
borderRadius: 10,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
checkBox: {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 6,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#8E8E93',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
checkBoxSelected: {
|
||||||
|
backgroundColor: '#8B5CF6',
|
||||||
|
borderColor: '#8B5CF6',
|
||||||
|
},
|
||||||
|
checkMark: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '700',
|
||||||
|
},
|
||||||
|
calendarInfo: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
calendarName: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '500',
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
badgeRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
badge: {
|
||||||
|
paddingHorizontal: 6,
|
||||||
|
paddingVertical: 1,
|
||||||
|
borderRadius: 4,
|
||||||
|
marginRight: 6,
|
||||||
|
},
|
||||||
|
badgeText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: '700',
|
||||||
|
},
|
||||||
|
accountTypeText: {
|
||||||
|
fontSize: 11,
|
||||||
|
},
|
||||||
|
loadingContainer: {
|
||||||
|
padding: 20,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
loadingText: {
|
||||||
|
marginTop: 8,
|
||||||
|
fontSize: 13,
|
||||||
|
},
|
||||||
|
emptyContainer: {
|
||||||
|
padding: 20,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
fontSize: 13,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
selectionInfo: {
|
||||||
|
padding: 10,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
selectionInfoText: {
|
||||||
|
fontSize: 13,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as Calendar from 'expo-calendar';
|
import * as Calendar from 'expo-calendar';
|
||||||
import type { Event as CoreEvent } from '@timetoleave/core';
|
import type { Event as CoreEvent, CalendarAccountType, CalendarSourceInfo, SelectableCalendar } from '@timetoleave/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Native calendar integration for the mobile app.
|
* Native calendar integration for the mobile app.
|
||||||
@@ -7,33 +7,166 @@ import type { Event as CoreEvent } from '@timetoleave/core';
|
|||||||
*
|
*
|
||||||
* Only events that have a non-empty `location` field are included, since the
|
* Only events that have a non-empty `location` field are included, since the
|
||||||
* app requires a destination to compute routes.
|
* app requires a destination to compute routes.
|
||||||
|
*
|
||||||
|
* Supports calendar selection so the user can pick which calendars to sync.
|
||||||
|
* CalDAV sources (DAVx5 on Android, Apple Calendar on iOS) are detected and
|
||||||
|
* labelled automatically.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// ── Source type metadata ──
|
||||||
|
|
||||||
|
/** Maps expo-calendar account types to human-readable info. */
|
||||||
|
const SOURCE_INFO: Record<string, CalendarSourceInfo> = {
|
||||||
|
caldav: {
|
||||||
|
label: 'CalDAV / DAVx',
|
||||||
|
badge: 'CalDAV',
|
||||||
|
emoji: '🔗',
|
||||||
|
},
|
||||||
|
mobileme: {
|
||||||
|
label: 'Apple Calendar',
|
||||||
|
badge: 'Apple',
|
||||||
|
emoji: '🍎',
|
||||||
|
},
|
||||||
|
google: {
|
||||||
|
label: 'Google Calendar',
|
||||||
|
badge: 'Google',
|
||||||
|
emoji: '📅',
|
||||||
|
},
|
||||||
|
exchange: {
|
||||||
|
label: 'Microsoft Exchange',
|
||||||
|
badge: 'Exchange',
|
||||||
|
emoji: '🏢',
|
||||||
|
},
|
||||||
|
subscriptions: {
|
||||||
|
label: 'Subscribed',
|
||||||
|
badge: 'Sub',
|
||||||
|
emoji: '📡',
|
||||||
|
},
|
||||||
|
local: {
|
||||||
|
label: 'On My Device',
|
||||||
|
badge: 'Local',
|
||||||
|
emoji: '📱',
|
||||||
|
},
|
||||||
|
carddav: {
|
||||||
|
label: 'CardDAV',
|
||||||
|
badge: 'CardDAV',
|
||||||
|
emoji: '👤',
|
||||||
|
},
|
||||||
|
activesync: {
|
||||||
|
label: 'ActiveSync',
|
||||||
|
badge: 'Sync',
|
||||||
|
emoji: '🔄',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_SOURCE_INFO: CalendarSourceInfo = {
|
||||||
|
label: 'Other',
|
||||||
|
badge: 'Other',
|
||||||
|
emoji: '📆',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the account type string from expo-calendar into a known
|
||||||
|
* CalendarAccountType. Returns 'other' for unknown types.
|
||||||
|
*/
|
||||||
|
function resolveAccountType(type: string | undefined | null): CalendarAccountType {
|
||||||
|
if (!type) return 'other';
|
||||||
|
const lower = type.toLowerCase();
|
||||||
|
if (lower in SOURCE_INFO) return lower as CalendarAccountType;
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get source info for an expo-calendar source object.
|
||||||
|
* DAVx5 on Android appears as `caldav`, Apple Calendar on iOS as `mobileme`.
|
||||||
|
*/
|
||||||
|
function sourceInfoFor(source: { type?: string } | undefined | null): CalendarSourceInfo {
|
||||||
|
if (!source || !source.type) return DEFAULT_SOURCE_INFO;
|
||||||
|
return SOURCE_INFO[source.type.toLowerCase()] ?? DEFAULT_SOURCE_INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Permission ──
|
||||||
|
|
||||||
/** Requests calendar read permission and verifies the calendar service is available. */
|
/** Requests calendar read permission and verifies the calendar service is available. */
|
||||||
export async function ensureCalendarPermission(): Promise<boolean> {
|
export async function ensureCalendarPermission(): Promise<boolean> {
|
||||||
const { status } = await Calendar.requestCalendarPermissionsAsync();
|
const { status } = await Calendar.requestCalendarPermissionsAsync();
|
||||||
if (status !== 'granted')
|
if (status !== 'granted') return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
return Calendar.isAvailableAsync();
|
return Calendar.isAvailableAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Calendar listing ──
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch events from native calendars within a date range.
|
* Get all available calendars on the device, converted to a minimal format
|
||||||
* Returns events converted to our internal Event format.
|
* suitable for the selection UI. Grouped by account type.
|
||||||
|
*
|
||||||
|
* DAVx5 calendars (Android) will show as CalDAV type.
|
||||||
|
* Apple Calendar (iOS) will show as mobileme type.
|
||||||
*/
|
*/
|
||||||
export async function fetchNativeEvents(
|
export async function getSelectableCalendars(): Promise<SelectableCalendar[]> {
|
||||||
startDate: Date,
|
|
||||||
endDate: Date,
|
|
||||||
): Promise<CoreEvent[]> {
|
|
||||||
const available = await ensureCalendarPermission();
|
const available = await ensureCalendarPermission();
|
||||||
if (!available) return [];
|
if (!available) return [];
|
||||||
|
|
||||||
const calendars = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT);
|
const calendars = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT);
|
||||||
|
|
||||||
|
return calendars.map((cal) => {
|
||||||
|
const accountType = resolveAccountType(cal.source?.type);
|
||||||
|
const sourceInfo = sourceInfoFor(cal.source);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: cal.id,
|
||||||
|
name: cal.name ?? 'Unnamed Calendar',
|
||||||
|
accountType,
|
||||||
|
sourceInfo,
|
||||||
|
editable: cal.allowsModifications ?? false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group calendars by account type for display in the selection UI.
|
||||||
|
*/
|
||||||
|
export function groupCalendarsByType(calendars: SelectableCalendar[]): Map<CalendarAccountType, SelectableCalendar[]> {
|
||||||
|
const groups = new Map<CalendarAccountType, SelectableCalendar[]>();
|
||||||
|
|
||||||
|
for (const cal of calendars) {
|
||||||
|
const existing = groups.get(cal.accountType) ?? [];
|
||||||
|
existing.push(cal);
|
||||||
|
groups.set(cal.accountType, existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Event fetching ──
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch events from native calendars within a date range.
|
||||||
|
* Returns events converted to our internal Event format.
|
||||||
|
*
|
||||||
|
* @param startDate - Start of the date range.
|
||||||
|
* @param endDate - End of the date range.
|
||||||
|
* @param calendarIds - Optional list of calendar IDs to fetch. If omitted,
|
||||||
|
* fetches from all calendars.
|
||||||
|
*/
|
||||||
|
export async function fetchNativeEvents(
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date,
|
||||||
|
calendarIds?: string[],
|
||||||
|
): Promise<CoreEvent[]> {
|
||||||
|
const available = await ensureCalendarPermission();
|
||||||
|
if (!available) return [];
|
||||||
|
|
||||||
|
let calendars = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT);
|
||||||
|
|
||||||
|
if (calendarIds && calendarIds.length > 0) {
|
||||||
|
calendars = calendars.filter((c) => calendarIds.includes(c.id));
|
||||||
|
}
|
||||||
|
|
||||||
if (calendars.length === 0) return [];
|
if (calendars.length === 0) return [];
|
||||||
|
|
||||||
const calendarIds = calendars.map((c) => c.id);
|
const ids = calendarIds ?? calendars.map((c) => c.id);
|
||||||
const events = await Calendar.getEventsAsync(calendarIds, startDate, endDate);
|
const events = await Calendar.getEventsAsync(ids, startDate, endDate);
|
||||||
|
|
||||||
return events
|
return events
|
||||||
.filter((evt) => evt.location && evt.location.trim().length > 0)
|
.filter((evt) => evt.location && evt.location.trim().length > 0)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { SchedulableTriggerInputTypes } from 'expo-notifications';
|
|||||||
const EVENTS_KEY = '@timetoleave_events';
|
const EVENTS_KEY = '@timetoleave_events';
|
||||||
const ORIGIN_KEY = '@timetoleave_origin';
|
const ORIGIN_KEY = '@timetoleave_origin';
|
||||||
const NOTIFICATIONS_KEY = '@timetoleave_notifications';
|
const NOTIFICATIONS_KEY = '@timetoleave_notifications';
|
||||||
|
const SELECTED_CALENDARS_KEY = '@timetoleave_selected_calendars';
|
||||||
|
|
||||||
// ── Default notification settings ─────────────────────────────
|
// ── Default notification settings ─────────────────────────────
|
||||||
|
|
||||||
@@ -190,8 +191,40 @@ export async function saveNotificationSettings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ───────────────────────────────────────────────────────────────
|
// ───────────────────────────────────────────────────────────────
|
||||||
// Reschedule all notifications (for origin/setting changes)
|
// Calendar Selection
|
||||||
// ───────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of calendar IDs the user has chosen to sync.
|
||||||
|
* Returns an empty array if no selection has been made yet (meaning
|
||||||
|
* all calendars should be synced).
|
||||||
|
*/
|
||||||
|
export async function getSelectedCalendarIds(): Promise<string[]> {
|
||||||
|
const json = await AsyncStorage.getItem(SELECTED_CALENDARS_KEY);
|
||||||
|
if (!json) return [];
|
||||||
|
try {
|
||||||
|
return JSON.parse(json) as string[];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the list of calendar IDs the user wants to sync.
|
||||||
|
* Pass an empty array to reset to "sync all calendars".
|
||||||
|
*/
|
||||||
|
export async function saveSelectedCalendarIds(ids: string[]): Promise<void> {
|
||||||
|
await AsyncStorage.setItem(SELECTED_CALENDARS_KEY, JSON.stringify(ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the user has made an explicit calendar selection.
|
||||||
|
* Returns true if a non-empty list is stored.
|
||||||
|
*/
|
||||||
|
export async function hasCalendarSelection(): Promise<boolean> {
|
||||||
|
const ids = await getSelectedCalendarIds();
|
||||||
|
return ids.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
export async function rescheduleAllNotifications(): Promise<void> {
|
export async function rescheduleAllNotifications(): Promise<void> {
|
||||||
const [events, settings] = await Promise.all([loadEvents(), loadNotificationSettings()]);
|
const [events, settings] = await Promise.all([loadEvents(), loadNotificationSettings()]);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"/></svg>
|
||||||
|
After Width: | Height: | Size: 528 B |
@@ -0,0 +1,30 @@
|
|||||||
|
# OSX
|
||||||
|
#
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Xcode
|
||||||
|
#
|
||||||
|
build/
|
||||||
|
*.pbxuser
|
||||||
|
!default.pbxuser
|
||||||
|
*.mode1v3
|
||||||
|
!default.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
!default.mode2v3
|
||||||
|
*.perspectivev3
|
||||||
|
!default.perspectivev3
|
||||||
|
xcuserdata
|
||||||
|
*.xccheckout
|
||||||
|
*.moved-aside
|
||||||
|
DerivedData
|
||||||
|
*.hmap
|
||||||
|
*.ipa
|
||||||
|
*.xcuserstate
|
||||||
|
project.xcworkspace
|
||||||
|
.xcode.env.local
|
||||||
|
|
||||||
|
# Bundle artifacts
|
||||||
|
*.jsbundle
|
||||||
|
|
||||||
|
# CocoaPods
|
||||||
|
/Pods/
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# This `.xcode.env` file is versioned and is used to source the environment
|
||||||
|
# used when running script phases inside Xcode.
|
||||||
|
# To customize your local environment, you can create an `.xcode.env.local`
|
||||||
|
# file that is not versioned.
|
||||||
|
|
||||||
|
# NODE_BINARY variable contains the PATH to the node executable.
|
||||||
|
#
|
||||||
|
# Customize the NODE_BINARY variable here.
|
||||||
|
# For example, to use nvm with brew, add the following line
|
||||||
|
# . "$(brew --prefix nvm)/nvm.sh" --no-use
|
||||||
|
export NODE_BINARY=$(command -v node)
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
|
||||||
|
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
|
||||||
|
|
||||||
|
require 'json'
|
||||||
|
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
|
||||||
|
|
||||||
|
def ccache_enabled?(podfile_properties)
|
||||||
|
# Environment variable takes precedence
|
||||||
|
return ENV['USE_CCACHE'] == '1' if ENV['USE_CCACHE']
|
||||||
|
|
||||||
|
# Fall back to Podfile properties
|
||||||
|
podfile_properties['apple.ccacheEnabled'] == 'true'
|
||||||
|
end
|
||||||
|
|
||||||
|
ENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == 'false'
|
||||||
|
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
|
||||||
|
ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
|
||||||
|
ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
|
||||||
|
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
|
||||||
|
|
||||||
|
prepare_react_native_project!
|
||||||
|
|
||||||
|
target 'timetoleave' do
|
||||||
|
use_expo_modules!
|
||||||
|
|
||||||
|
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
|
||||||
|
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
|
||||||
|
else
|
||||||
|
config_command = [
|
||||||
|
'node',
|
||||||
|
'--no-warnings',
|
||||||
|
'--eval',
|
||||||
|
'require(\'expo/bin/autolinking\')',
|
||||||
|
'expo-modules-autolinking',
|
||||||
|
'react-native-config',
|
||||||
|
'--json',
|
||||||
|
'--platform',
|
||||||
|
'ios'
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
config = use_native_modules!(config_command)
|
||||||
|
|
||||||
|
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
|
||||||
|
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
|
||||||
|
|
||||||
|
use_react_native!(
|
||||||
|
:path => config[:reactNativePath],
|
||||||
|
:hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
|
||||||
|
# An absolute path to your application root.
|
||||||
|
:app_path => "#{Pod::Config.instance.installation_root}/..",
|
||||||
|
:privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
|
||||||
|
)
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
react_native_post_install(
|
||||||
|
installer,
|
||||||
|
config[:reactNativePath],
|
||||||
|
:mac_catalyst_enabled => false,
|
||||||
|
:ccache_enabled => ccache_enabled?(podfile_properties),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"expo.jsEngine": "hermes",
|
||||||
|
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true"
|
||||||
|
}
|
||||||
@@ -0,0 +1,436 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||||
|
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
|
||||||
|
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
|
||||||
|
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
13B07F961A680F5B00A75B9A /* timetoleave.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = timetoleave.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = timetoleave/Images.xcassets; sourceTree = "<group>"; };
|
||||||
|
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = timetoleave/Info.plist; sourceTree = "<group>"; };
|
||||||
|
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = timetoleave/SplashScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||||
|
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||||
|
F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = timetoleave/AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
F11748442D0722820044C1D9 /* timetoleave-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "timetoleave-Bridging-Header.h"; path = "timetoleave/timetoleave-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
13B07FAE1A68108700A75B9A /* timetoleave */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F11748412D0307B40044C1D9 /* AppDelegate.swift */,
|
||||||
|
F11748442D0722820044C1D9 /* timetoleave-Bridging-Header.h */,
|
||||||
|
BB2F792B24A3F905000567C9 /* Supporting */,
|
||||||
|
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||||
|
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||||
|
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
|
||||||
|
);
|
||||||
|
name = timetoleave;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
name = Libraries;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
83CBB9F61A601CBA00E9B192 = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
13B07FAE1A68108700A75B9A /* timetoleave */,
|
||||||
|
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||||
|
83CBBA001A601CBA00E9B192 /* Products */,
|
||||||
|
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||||
|
);
|
||||||
|
indentWidth = 2;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
tabWidth = 2;
|
||||||
|
usesTabs = 0;
|
||||||
|
};
|
||||||
|
83CBBA001A601CBA00E9B192 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
13B07F961A680F5B00A75B9A /* timetoleave.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
BB2F792B24A3F905000567C9 /* Supporting */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BB2F792C24A3F905000567C9 /* Expo.plist */,
|
||||||
|
);
|
||||||
|
name = Supporting;
|
||||||
|
path = timetoleave/Supporting;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
13B07F861A680F5B00A75B9A /* timetoleave */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "timetoleave" */;
|
||||||
|
buildPhases = (
|
||||||
|
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
|
||||||
|
13B07F871A680F5B00A75B9A /* Sources */,
|
||||||
|
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||||
|
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||||
|
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||||
|
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = timetoleave;
|
||||||
|
productName = timetoleave;
|
||||||
|
productReference = 13B07F961A680F5B00A75B9A /* timetoleave.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastUpgradeCheck = 1130;
|
||||||
|
TargetAttributes = {
|
||||||
|
13B07F861A680F5B00A75B9A = {
|
||||||
|
LastSwiftMigration = 1250;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "timetoleave" */;
|
||||||
|
compatibilityVersion = "Xcode 3.2";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 83CBB9F61A601CBA00E9B192;
|
||||||
|
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
13B07F861A680F5B00A75B9A /* timetoleave */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
13B07F8E1A680F5B00A75B9A /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
|
||||||
|
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||||
|
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"$(SRCROOT)/.xcode.env",
|
||||||
|
"$(SRCROOT)/.xcode.env.local",
|
||||||
|
);
|
||||||
|
name = "Bundle React Native code and images";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
|
||||||
|
};
|
||||||
|
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-timetoleave-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-timetoleave/Pods-timetoleave-resources.sh",
|
||||||
|
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
||||||
|
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
||||||
|
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
|
||||||
|
);
|
||||||
|
name = "[CP] Copy Pods Resources";
|
||||||
|
outputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
||||||
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
||||||
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-timetoleave/Pods-timetoleave-resources.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
13B07F871A680F5B00A75B9A /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"FB_SONARKIT_ENABLED=1",
|
||||||
|
);
|
||||||
|
INFOPLIST_FILE = timetoleave/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
OTHER_LDFLAGS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"-ObjC",
|
||||||
|
"-lc++",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "com.floegger.timetoleave";
|
||||||
|
PRODUCT_NAME = "timetoleave";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "timetoleave/timetoleave-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1";
|
||||||
|
CODE_SIGN_ENTITLEMENTS = timetoleave/timetoleave.entitlements;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
INFOPLIST_FILE = timetoleave/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
OTHER_LDFLAGS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"-ObjC",
|
||||||
|
"-lc++",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "com.floegger.timetoleave";
|
||||||
|
PRODUCT_NAME = "timetoleave";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "timetoleave/timetoleave-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1";
|
||||||
|
CODE_SIGN_ENTITLEMENTS = timetoleave/timetoleave.entitlements;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
83CBBA201A601CBA00E9B192 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
/usr/lib/swift,
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
LIBRARY_SEARCH_PATHS = "\"$(inherited)\"";
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
83CBBA211A601CBA00E9B192 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = YES;
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
/usr/lib/swift,
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
LIBRARY_SEARCH_PATHS = "\"$(inherited)\"";
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "timetoleave" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
13B07F941A680F5B00A75B9A /* Debug */,
|
||||||
|
13B07F951A680F5B00A75B9A /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "timetoleave" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
83CBBA201A601CBA00E9B192 /* Debug */,
|
||||||
|
83CBBA211A601CBA00E9B192 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1130"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||||
|
BuildableName = "timetoleave.app"
|
||||||
|
BlueprintName = "timetoleave"
|
||||||
|
ReferencedContainer = "container:timetoleave.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||||
|
BuildableName = "timetoleaveTests.xctest"
|
||||||
|
BlueprintName = "timetoleaveTests"
|
||||||
|
ReferencedContainer = "container:timetoleave.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||||
|
BuildableName = "timetoleave.app"
|
||||||
|
BlueprintName = "timetoleave"
|
||||||
|
ReferencedContainer = "container:timetoleave.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||||
|
BuildableName = "timetoleave.app"
|
||||||
|
BlueprintName = "timetoleave"
|
||||||
|
ReferencedContainer = "container:timetoleave.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import Expo
|
||||||
|
import React
|
||||||
|
import ReactAppDependencyProvider
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
public class AppDelegate: ExpoAppDelegate {
|
||||||
|
var window: UIWindow?
|
||||||
|
|
||||||
|
var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
|
||||||
|
var reactNativeFactory: RCTReactNativeFactory?
|
||||||
|
|
||||||
|
public override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||||
|
) -> Bool {
|
||||||
|
let delegate = ReactNativeDelegate()
|
||||||
|
let factory = ExpoReactNativeFactory(delegate: delegate)
|
||||||
|
delegate.dependencyProvider = RCTAppDependencyProvider()
|
||||||
|
|
||||||
|
reactNativeDelegate = delegate
|
||||||
|
reactNativeFactory = factory
|
||||||
|
bindReactNativeFactory(factory)
|
||||||
|
|
||||||
|
#if os(iOS) || os(tvOS)
|
||||||
|
window = UIWindow(frame: UIScreen.main.bounds)
|
||||||
|
factory.startReactNative(
|
||||||
|
withModuleName: "main",
|
||||||
|
in: window,
|
||||||
|
launchOptions: launchOptions)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linking API
|
||||||
|
public override func application(
|
||||||
|
_ app: UIApplication,
|
||||||
|
open url: URL,
|
||||||
|
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
|
||||||
|
) -> Bool {
|
||||||
|
return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Universal Links
|
||||||
|
public override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
continue userActivity: NSUserActivity,
|
||||||
|
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
|
||||||
|
) -> Bool {
|
||||||
|
let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||||
|
return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
|
||||||
|
// Extension point for config-plugins
|
||||||
|
|
||||||
|
override func sourceURL(for bridge: RCTBridge) -> URL? {
|
||||||
|
// needed to return the correct URL for expo-dev-client.
|
||||||
|
bridge.bundleURL ?? bundleURL()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func bundleURL() -> URL? {
|
||||||
|
#if DEBUG
|
||||||
|
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
|
||||||
|
#else
|
||||||
|
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "App-Icon-1024x1024@1x.png",
|
||||||
|
"idiom": "universal",
|
||||||
|
"platform": "ios",
|
||||||
|
"size": "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"version": 1,
|
||||||
|
"author": "expo"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "expo"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors": [
|
||||||
|
{
|
||||||
|
"color": {
|
||||||
|
"components": {
|
||||||
|
"alpha": "1.000",
|
||||||
|
"blue": "1.00000000000000",
|
||||||
|
"green": "1.00000000000000",
|
||||||
|
"red": "1.00000000000000"
|
||||||
|
},
|
||||||
|
"color-space": "srgb"
|
||||||
|
},
|
||||||
|
"idiom": "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"version": 1,
|
||||||
|
"author": "expo"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>time-to-leave</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>com.floegger.timetoleave</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>exp+time-to-leave</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>12.0</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>RCTNewArchEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>SplashScreen</string>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
</array>
|
||||||
|
<key>UIRequiresFullScreen</key>
|
||||||
|
<false/>
|
||||||
|
<key>UIStatusBarStyle</key>
|
||||||
|
<string>UIStatusBarStyleDefault</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UIUserInterfaceStyle</key>
|
||||||
|
<string>Light</string>
|
||||||
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24053.1"/>
|
||||||
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<scene sceneID="EXPO-SCENE-1">
|
||||||
|
<objects>
|
||||||
|
<viewController storyboardIdentifier="SplashScreenViewController" id="EXPO-VIEWCONTROLLER-1" sceneMemberID="viewController">
|
||||||
|
<view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EXPO-ContainerView" userLabel="ContainerView">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews/>
|
||||||
|
<viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="EXPO-SplashScreen" firstAttribute="centerY" secondItem="EXPO-ContainerView" secondAttribute="centerY" id="0VC-Wk-OaO"/>
|
||||||
|
<constraint firstItem="EXPO-SplashScreen" firstAttribute="centerX" secondItem="EXPO-ContainerView" secondAttribute="centerX" id="zR4-NK-mVN"/>
|
||||||
|
</constraints>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="0.0" y="0.0"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="SplashScreenLogo" width="100" height="90.333335876464844"/>
|
||||||
|
<systemColor name="systemBackgroundColor">
|
||||||
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>EXUpdatesCheckOnLaunch</key>
|
||||||
|
<string>ALWAYS</string>
|
||||||
|
<key>EXUpdatesEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>EXUpdatesLaunchWaitMs</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
//
|
||||||
|
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||||
|
//
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict/>
|
||||||
|
</plist>
|
||||||
Generated
+223
-2
@@ -10,12 +10,28 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
]
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
|
"expo": "~54.0.34",
|
||||||
|
"expo-calendar": "~15.0.8",
|
||||||
|
"expo-dev-client": "~6.0.21",
|
||||||
|
"expo-location": "~19.0.8",
|
||||||
|
"expo-notifications": "~0.32.17",
|
||||||
|
"expo-status-bar": "~3.0.9",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-native": "0.81.5",
|
||||||
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
|
"react-native-screens": "~4.16.0",
|
||||||
|
"react-native-svg": "^15.15.5"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"apps/mobile": {
|
"apps/mobile": {
|
||||||
"name": "@timetoleave/mobile",
|
"name": "@timetoleave/mobile",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
||||||
|
"@fortawesome/react-native-fontawesome": "^1.0.0",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-navigation/native": "^7.2.4",
|
"@react-navigation/native": "^7.2.4",
|
||||||
"@react-navigation/native-stack": "^7.14.14",
|
"@react-navigation/native-stack": "^7.14.14",
|
||||||
@@ -30,7 +46,8 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.5",
|
"react-native": "0.81.5",
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
"react-native-screens": "~4.16.0"
|
"react-native-screens": "~4.16.0",
|
||||||
|
"react-native-svg": "^15.15.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.4",
|
"@eslint/js": "^9.39.4",
|
||||||
@@ -2678,6 +2695,58 @@
|
|||||||
"excpretty": "build/cli.js"
|
"excpretty": "build/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "7.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==",
|
||||||
|
"license": "(CC-BY-4.0 AND MIT)",
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "7.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/react-native-fontawesome": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/react-native-fontawesome/-/react-native-fontawesome-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-whGM1GewA1kO0r+RxSY4cefcXNAiKGMh9PIkfid8lMd7ndO/yDAEHBiHTYVfeCICm7DwbmcAUWBJZ6HcFeN7fg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"example"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"humps": "^2.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "~7",
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*",
|
||||||
|
"react-native-svg": ">=11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||||
@@ -7028,6 +7097,12 @@
|
|||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/bplist-creator": {
|
"node_modules/bplist-creator": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
|
||||||
@@ -7629,6 +7704,22 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css-tree": {
|
"node_modules/css-tree": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
||||||
@@ -7643,6 +7734,18 @@
|
|||||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css.escape": {
|
"node_modules/css.escape": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||||
@@ -7985,6 +8088,44 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dom-serializer/node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
"node_modules/domexception": {
|
"node_modules/domexception": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
|
||||||
@@ -8009,6 +8150,35 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.7",
|
"version": "16.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||||
@@ -10133,6 +10303,12 @@
|
|||||||
"node": ">=10.17.0"
|
"node": ">=10.17.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/humps": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
@@ -13991,6 +14167,18 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nullthrows": {
|
"node_modules/nullthrows": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
||||||
@@ -15136,6 +15324,39 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-svg": {
|
||||||
|
"version": "15.15.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.5.tgz",
|
||||||
|
"integrity": "sha512-L4go5jA+GWutdJ/JucuN20cjAbMg1HmMtAP+wZ+3JLCf6Jd0bhXQHxciRP/AQm/FlrIEZwkMcHNZP+FXAiic0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"css-tree": "^1.1.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-svg/node_modules/css-tree": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mdn-data": "2.0.14",
|
||||||
|
"source-map": "^0.6.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-svg/node_modules/mdn-data": {
|
||||||
|
"version": "2.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||||
|
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
|
||||||
|
"license": "CC0-1.0"
|
||||||
|
},
|
||||||
"node_modules/react-native/node_modules/ansi-styles": {
|
"node_modules/react-native/node_modules/ansi-styles": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||||
|
|||||||
+10
-1
@@ -24,8 +24,17 @@
|
|||||||
"react-test-renderer": "19.1.0"
|
"react-test-renderer": "19.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"expo": "~54.0.34",
|
"expo": "~54.0.34",
|
||||||
|
"expo-calendar": "~15.0.8",
|
||||||
|
"expo-dev-client": "~6.0.21",
|
||||||
|
"expo-location": "~19.0.8",
|
||||||
|
"expo-notifications": "~0.32.17",
|
||||||
|
"expo-status-bar": "~3.0.9",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.5"
|
"react-native": "0.81.5",
|
||||||
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
|
"react-native-screens": "~4.16.0",
|
||||||
|
"react-native-svg": "^15.15.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,3 +144,35 @@ export interface NearbyStop {
|
|||||||
lat: number;
|
lat: number;
|
||||||
lng: number;
|
lng: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Calendar Source Types ──
|
||||||
|
|
||||||
|
/** Known calendar account types from expo-calendar. */
|
||||||
|
export type CalendarAccountType =
|
||||||
|
| 'caldav'
|
||||||
|
| 'local'
|
||||||
|
| 'exchange'
|
||||||
|
| 'google'
|
||||||
|
| 'mobileme'
|
||||||
|
| 'subscriptions'
|
||||||
|
| 'carddav'
|
||||||
|
| 'activesync'
|
||||||
|
| 'other'
|
||||||
|
| 'none';
|
||||||
|
|
||||||
|
/** Human-readable label and visual badge for each account type. */
|
||||||
|
export interface CalendarSourceInfo {
|
||||||
|
label: string;
|
||||||
|
badge: string;
|
||||||
|
/** Icon emoji shown next to the calendar name. */
|
||||||
|
emoji: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimal calendar descriptor used for selection UI. */
|
||||||
|
export interface SelectableCalendar {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
accountType: CalendarAccountType;
|
||||||
|
sourceInfo: CalendarSourceInfo;
|
||||||
|
editable: boolean;
|
||||||
|
}
|
||||||
|
|||||||
+17
-6
@@ -12,11 +12,22 @@
|
|||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"include": [],
|
"include": [],
|
||||||
"exclude": ["node_modules"],
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "apps/web" },
|
{
|
||||||
{ "path": "apps/mobile" },
|
"path": "apps/web"
|
||||||
{ "path": "packages/core" },
|
},
|
||||||
{ "path": "packages/api-client" }
|
{
|
||||||
]
|
"path": "apps/mobile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "packages/core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "packages/api-client"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extends": "expo/tsconfig.base"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user