diff --git a/.github/codemention.yml b/.github/codemention.yml index 5b9557cc94cae2..752daa9e7ca458 100644 --- a/.github/codemention.yml +++ b/.github/codemention.yml @@ -139,7 +139,7 @@ rules: - patterns: ['packages/expo-local-authentication/**'] mentions: ['behenate'] - patterns: ['packages/expo-localization/**'] - mentions: ['EvanBacon'] + mentions: ['EvanBacon', 'zoontek'] - patterns: ['packages/expo-location/**'] mentions: ['behenate', 'lukmccall'] - patterns: ['packages/expo-mail-composer/**'] @@ -165,7 +165,7 @@ rules: - patterns: ['packages/expo-modules-test-core/**'] mentions: ['lukmccall', 'tsapeta'] - patterns: ['packages/expo-navigation-bar/**'] - mentions: ['EvanBacon'] + mentions: ['EvanBacon', 'zoontek'] - patterns: ['packages/expo-network/**'] mentions: ['ide', 'lukmccall'] - patterns: ['packages/expo-network-addons/**'] @@ -195,13 +195,13 @@ rules: - patterns: ['packages/expo-speech/**'] mentions: ['lukmccall', 'alanjhughes'] - patterns: ['packages/expo-splash-screen/**'] - mentions: ['alanjhughes'] + mentions: ['alanjhughes', 'zoontek'] - patterns: ['packages/expo-sqlite/**'] mentions: ['Kudo', 'alanjhughes'] - patterns: ['packages/expo-standard-web-crypto/**'] mentions: ['EvanBacon'] - patterns: ['packages/expo-status-bar/**'] - mentions: ['brentvatne'] + mentions: ['brentvatne', 'zoontek'] - patterns: ['packages/expo-store-review/**'] mentions: ['EvanBacon'] - patterns: ['packages/expo-structured-headers/**'] diff --git a/apps/bare-expo/android/app/src/debugOptimized/res/xml/network_security_config.xml b/apps/bare-expo/android/app/src/debugOptimized/res/xml/network_security_config.xml new file mode 100644 index 00000000000000..dd4013673f66c8 --- /dev/null +++ b/apps/bare-expo/android/app/src/debugOptimized/res/xml/network_security_config.xml @@ -0,0 +1,6 @@ + + + + diff --git a/apps/bare-expo/plugins/withAndroidNetworkSecurityConfig.js b/apps/bare-expo/plugins/withAndroidNetworkSecurityConfig.js index 85a152596ccfec..f67755a8e334d1 100644 --- a/apps/bare-expo/plugins/withAndroidNetworkSecurityConfig.js +++ b/apps/bare-expo/plugins/withAndroidNetworkSecurityConfig.js @@ -39,10 +39,16 @@ const withAndroidNetworkSecurityConfigXml = (config) => { ` ); - await fs.mkdir(path.join(projectPath, 'app/src/debug/res/xml'), { recursive: true }); - await fs.writeFile( - path.join(projectPath, 'app/src/debug/res/xml/network_security_config.xml'), - `\ + for (const debuggableBuildTypes in ['debug', 'debugOptimized']) { + await fs.mkdir(path.join(projectPath, `app/src/${debuggableBuildTypes}/res/xml`), { + recursive: true, + }); + await fs.writeFile( + path.join( + projectPath, + `app/src/${debuggableBuildTypes}/res/xml/network_security_config.xml` + ), + `\ { tools:ignore="InsecureBaseConfiguration" /> ` - ); + ); + } return config; }, ]); diff --git a/apps/native-component-list/src/navigation/ExpoApisStackNavigator.tsx b/apps/native-component-list/src/navigation/ExpoApisStackNavigator.tsx index 6003d6f18054e9..a0d733e1b69fab 100644 --- a/apps/native-component-list/src/navigation/ExpoApisStackNavigator.tsx +++ b/apps/native-component-list/src/navigation/ExpoApisStackNavigator.tsx @@ -9,7 +9,7 @@ import TabIcon from '../components/TabIcon'; import getStackNavWithConfig from '../navigation/StackConfig'; import { AudioScreens } from '../screens/Audio/AudioScreen'; import { BlobScreens } from '../screens/Blob/BlobScreen'; -import { CalendarsNextScreens } from '../screens/CalendarsNextScreen'; +import { CalendarNextScreens } from '../screens/Calendar@Next/CalendarNextScreens'; import { CalendarsScreens } from '../screens/CalendarsScreen'; import { apiScreensToListElements } from '../screens/ComponentListScreen'; import { ContactsScreens } from '../screens/Contacts/ContactsScreen'; @@ -218,7 +218,7 @@ export const ScreensList: ScreenConfig[] = [ }, { getComponent() { - return optionalRequire(() => require('../screens/CalendarsNextScreen')); + return optionalRequire(() => require('../screens/Calendar@Next/CalendarNextScreens')); }, name: 'Calendars@next', }, @@ -500,7 +500,7 @@ export const Screens: ScreenConfig[] = [ ...ContactsScreens, ...ContactsNextScreens, ...CalendarsScreens, - ...CalendarsNextScreens, + ...CalendarNextScreens, ...CryptoScreens, ...WorkletsScreens, ]; diff --git a/apps/native-component-list/src/screens/Calendar@Next/CalendarNextScreens.tsx b/apps/native-component-list/src/screens/Calendar@Next/CalendarNextScreens.tsx new file mode 100644 index 00000000000000..16960f2b8fa428 --- /dev/null +++ b/apps/native-component-list/src/screens/Calendar@Next/CalendarNextScreens.tsx @@ -0,0 +1,49 @@ +import { Platform } from 'react-native'; + +import { optionalRequire } from '../../navigation/routeBuilder'; +import ComponentListScreen, { apiScreensToListElements } from '../ComponentListScreen'; + +const calendarTopLevelScreens = [ + { + name: 'CalendarsNextList', + route: 'calendar@next/calendars', + getComponent() { + return optionalRequire(() => require('./CalendarsNextScreen')); + }, + }, +]; + +if (Platform.OS === 'ios') { + calendarTopLevelScreens.push({ + name: 'WriteOnly Permissions', + route: 'calendar@next/write-only-permissions', + getComponent() { + return optionalRequire(() => require('./WriteOnlyPermissionsScreen')); + }, + }); +} + +export const CalendarNextScreens = [ + ...calendarTopLevelScreens, + { + name: 'EventsNext', + route: 'events-next', + options: {}, + getComponent() { + return optionalRequire(() => require('./EventsNextScreen')); + }, + }, + { + name: 'RemindersNext', + route: 'reminders-next', + options: {}, + getComponent() { + return optionalRequire(() => require('./RemindersNextScreen')); + }, + }, +]; + +export default function CalendarNextScreen() { + const apis = apiScreensToListElements(calendarTopLevelScreens); + return ; +} diff --git a/apps/native-component-list/src/screens/CalendarsNextScreen.tsx b/apps/native-component-list/src/screens/Calendar@Next/CalendarsNextScreen.tsx similarity index 89% rename from apps/native-component-list/src/screens/CalendarsNextScreen.tsx rename to apps/native-component-list/src/screens/Calendar@Next/CalendarsNextScreen.tsx index 5a7f56e6b34596..d39cd04b35d8a9 100644 --- a/apps/native-component-list/src/screens/CalendarsNextScreen.tsx +++ b/apps/native-component-list/src/screens/Calendar@Next/CalendarsNextScreen.tsx @@ -13,31 +13,11 @@ import { import { useState } from 'react'; import { Alert, Platform, ScrollView, StyleSheet, View } from 'react-native'; -import Button from '../components/Button'; -import HeadingText from '../components/HeadingText'; -import ListButton from '../components/ListButton'; -import MonoText from '../components/MonoText'; -import Colors from '../constants/Colors'; -import { optionalRequire } from '../navigation/routeBuilder'; - -export const CalendarsNextScreens = [ - { - name: 'EventsNext', - route: 'events-next', - options: {}, - getComponent() { - return optionalRequire(() => require('./EventsNextScreen')); - }, - }, - { - name: 'RemindersNext', - route: 'reminders-next', - options: {}, - getComponent() { - return optionalRequire(() => require('./RemindersNextScreen')); - }, - }, -]; +import Button from '../../components/Button'; +import HeadingText from '../../components/HeadingText'; +import ListButton from '../../components/ListButton'; +import MonoText from '../../components/MonoText'; +import Colors from '../../constants/Colors'; type StackNavigation = StackNavigationProp<{ RemindersNext: { calendar: ExpoCalendar }; diff --git a/apps/native-component-list/src/screens/EventsNextScreen.tsx b/apps/native-component-list/src/screens/Calendar@Next/EventsNextScreen.tsx similarity index 97% rename from apps/native-component-list/src/screens/EventsNextScreen.tsx rename to apps/native-component-list/src/screens/Calendar@Next/EventsNextScreen.tsx index a84d539b4ba583..dea8955d16323f 100644 --- a/apps/native-component-list/src/screens/EventsNextScreen.tsx +++ b/apps/native-component-list/src/screens/Calendar@Next/EventsNextScreen.tsx @@ -4,10 +4,10 @@ import { AddEventWithFormOptions, ExpoCalendar, ExpoCalendarEvent } from 'expo-c import React, { useState, useEffect } from 'react'; import { Alert, Platform, ScrollView, StyleSheet, Text, View } from 'react-native'; -import Button from '../components/Button'; -import HeadingText from '../components/HeadingText'; -import ListButton from '../components/ListButton'; -import MonoText from '../components/MonoText'; +import Button from '../../components/Button'; +import HeadingText from '../../components/HeadingText'; +import ListButton from '../../components/ListButton'; +import MonoText from '../../components/MonoText'; type EventRowProps = { event: ExpoCalendarEvent; diff --git a/apps/native-component-list/src/screens/RemindersNextScreen.tsx b/apps/native-component-list/src/screens/Calendar@Next/RemindersNextScreen.tsx similarity index 100% rename from apps/native-component-list/src/screens/RemindersNextScreen.tsx rename to apps/native-component-list/src/screens/Calendar@Next/RemindersNextScreen.tsx diff --git a/apps/native-component-list/src/screens/Calendar@Next/WriteOnlyPermissionsScreen.tsx b/apps/native-component-list/src/screens/Calendar@Next/WriteOnlyPermissionsScreen.tsx new file mode 100644 index 00000000000000..d42ccfdf28903f --- /dev/null +++ b/apps/native-component-list/src/screens/Calendar@Next/WriteOnlyPermissionsScreen.tsx @@ -0,0 +1,195 @@ +import { + type ExpoCalendar, + getCalendarPermissions, + presentPicker, + requestCalendarPermissions, + useCalendarPermissions, +} from 'expo-calendar/next'; +import { type ReactNode, useState } from 'react'; +import { Alert, ScrollView, StyleSheet, Text, View } from 'react-native'; + +import Button from '../../components/Button'; +import HeadingText from '../../components/HeadingText'; + +type StepProps = { + number: number; + title: string; + description: string; + children?: ReactNode; +}; + +function Step({ number, title, description, children }: StepProps) { + return ( + + {`${number}. ${title}`} + {description} + {children} + + ); +} + +export default function WriteOnlyPermissionsScreen() { + const [calendar, setCalendar] = useState(null); + const [, requestWriteOnlyPermission] = useCalendarPermissions({ + writeOnly: true, + }); + + const pickWriteOnlyCalendar = async () => { + try { + const calendar = await presentPicker(); + if (calendar) { + setCalendar(calendar); + Alert.alert('Calendar picked', `Title: ${calendar.title}\nID: ${calendar.id}`); + } else { + Alert.alert('Calendar picker cancelled'); + } + } catch (e: any) { + Alert.alert('Failed to pick calendar', e.message); + } + }; + + const requestWriteOnlyAccess = async () => { + try { + const output = await requestWriteOnlyPermission(); + Alert.alert('Write-Only Permission Status', JSON.stringify(output, null, 2)); + } catch (e: any) { + Alert.alert('Failed to request write-only permission', e.message); + } + }; + + const checkWriteOnlyPermission = async () => { + try { + const output = await getCalendarPermissions(true); + Alert.alert('Write-Only Permission Status', JSON.stringify(output, null, 2)); + } catch (e: any) { + Alert.alert('Failed to check write-only permission', e.message); + } + }; + + const checkFullAccessPermission = async () => { + try { + const output = await getCalendarPermissions(); + Alert.alert('Full Access Permission Status', JSON.stringify(output, null, 2)); + } catch (e: any) { + Alert.alert('Failed to check full access permission', e.message); + } + }; + + const requestFullAccessPermission = async () => { + try { + const output = await requestCalendarPermissions(); + Alert.alert('Full Access Permission Status', JSON.stringify(output, null, 2)); + } catch (e: any) { + Alert.alert('Failed to request full access', e.message); + } + }; + + const addEventToCalendar = async () => { + if (!calendar) { + Alert.alert('No calendar', 'Pick a calendar first.'); + return; + } + try { + const timeInOneHour = new Date(); + timeInOneHour.setHours(timeInOneHour.getHours() + 1); + const event = await calendar.createEvent({ + title: 'Write-Only Test Event', + startDate: new Date(), + endDate: timeInOneHour, + timeZone: 'America/Los_Angeles', + }); + Alert.alert('Event created', `ID: ${event.id}`); + } catch (e: any) { + Alert.alert('Failed to create event', e.message); + } + }; + + return ( + + Write-Only Permissions + + Follow these steps on iOS 17+ to verify that write-only calendar permissions can create + events without first granting full calendar access. + + + +