Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
605397a
fix(precomile) reduce output on CI (#45492)
chrfalch May 7, 2026
c311fb9
fix(precompile) fixed react-native-skia prebuild failing (#45483)
chrfalch May 7, 2026
bea363b
chore(cli): Bump expo/xcpretty (#45473)
EvanBacon May 7, 2026
10ac0b5
Add zoontek to codemention (#45486)
zoontek May 7, 2026
88b8e01
test(e2e): Fix manifest version fixture (#45470)
kitten May 7, 2026
f1e7237
fix(expo-router): Move `@jest/globals` to `devDependencies` (#45469)
kitten May 7, 2026
683f2e1
feat(create-expo): Drop automatic `node-linker=hoisted` for pnpm (#45…
kitten May 7, 2026
1980cb8
chore(metro-config): Drop obsolete `EXPO_USE_EXOTIC` flag (#45494)
kitten May 7, 2026
b8b37cf
chore(cli): Drop obsolete webview check (#45489)
kitten May 7, 2026
055ec74
test(cli/e2e): Update icons fixtures for SDK 56 (#45496)
kitten May 7, 2026
a32243d
[iOS][calendar][next] Add support for writeOnly permissions (#44967)
Wenszel May 7, 2026
efa2f5a
[docs] Fix Algolia crawler record limit on EAS CLI reference (#45481)
amandeepmittal May 7, 2026
8955c80
[core][Android] Use CT reflection to convert Records to JS in legacy …
lukmccall May 7, 2026
318a5d4
[linear-gradient] Add macOS support (#45448)
gabrieldonadel May 7, 2026
4a029a0
fix(cli): Tweak dependency check message and placement (#45487)
kitten May 7, 2026
362df76
[tools] prevent et bump-rn to change vendored modules (#45450)
gabrieldonadel May 7, 2026
c70f6fd
[ui][docs] move Expo UI to a separate subsection (#45477)
intergalacticspacehighway May 7, 2026
c21f425
[docs] fine tune dom component usage (#45480)
Kudo May 7, 2026
42776ae
[docs] backport community Picker docs to SDK 56 (#45498)
vonovak May 7, 2026
a750a74
[ui][docs] add images to SwiftUI component docs (#45459)
intergalacticspacehighway May 7, 2026
aed8ff8
[bare-expo][Android] Fix network security in debugOptimized (#45500)
lukmccall May 7, 2026
a28b9cb
[ui] add mask view for Android (#44958)
vonovak May 7, 2026
e1002d8
[ui] add MaskedView drop-in replacement (#45488)
vonovak May 7, 2026
885d71a
[docs] add expo-ui MaskedView docs (#45490)
vonovak May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/codemention.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/**']
Expand All @@ -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/**']
Expand Down Expand Up @@ -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/**']
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config
cleartextTrafficPermitted="true"
tools:ignore="InsecureBaseConfiguration" />
</network-security-config>
17 changes: 12 additions & 5 deletions apps/bare-expo/plugins/withAndroidNetworkSecurityConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,25 @@ 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`
),
`\
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config
cleartextTrafficPermitted="true"
tools:ignore="InsecureBaseConfiguration" />
</network-security-config>
`
);
);
}
return config;
},
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -218,7 +218,7 @@ export const ScreensList: ScreenConfig[] = [
},
{
getComponent() {
return optionalRequire(() => require('../screens/CalendarsNextScreen'));
return optionalRequire(() => require('../screens/Calendar@Next/CalendarNextScreens'));
},
name: 'Calendars@next',
},
Expand Down Expand Up @@ -500,7 +500,7 @@ export const Screens: ScreenConfig[] = [
...ContactsScreens,
...ContactsNextScreens,
...CalendarsScreens,
...CalendarsNextScreens,
...CalendarNextScreens,
...CryptoScreens,
...WorkletsScreens,
];
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <ComponentListScreen apis={apis} sort={false} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<View style={styles.step}>
<HeadingText>{`${number}. ${title}`}</HeadingText>
<Text style={styles.description}>{description}</Text>
{children}
</View>
);
}

export default function WriteOnlyPermissionsScreen() {
const [calendar, setCalendar] = useState<ExpoCalendar | null>(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 (
<ScrollView style={styles.container}>
<HeadingText>Write-Only Permissions</HeadingText>
<Text style={styles.intro}>
Follow these steps on iOS 17+ to verify that write-only calendar permissions can create
events without first granting full calendar access.
</Text>

<Step
number={1}
title="Start clean"
description="Remove Calendar permissions for this app in iOS Settings, then check both write-only and full access statuses before requesting anything.">
<Button
onPress={checkWriteOnlyPermission}
title="Check Write-Only Status"
style={styles.button}
/>
<Button
onPress={checkFullAccessPermission}
title="Check Full Access Status"
style={styles.button}
/>
</Step>

<Step
number={2}
title="Request write-only access"
description="Accept the write-only prompt. The request result appears in an alert.">
<Button
onPress={requestWriteOnlyAccess}
title="Request Write-Only Permission"
style={styles.button}
/>
</Step>

<Step
number={3}
title="Pick the destination calendar"
description="Use the system picker to choose where the test event should be created.">
<Button onPress={pickWriteOnlyCalendar} title="Present Picker" style={styles.button} />
</Step>

<Step
number={4}
title="Create an event"
description="Create a test event in the picked calendar. This is the write-only path.">
<Button
onPress={addEventToCalendar}
title="Add Event to Calendar"
style={styles.button}
disabled={!calendar}
/>
</Step>

<Step
number={5}
title="Request full access"
description="Test whether full access permissions can be requested after the app already has write-only access.">
<Button
onPress={requestFullAccessPermission}
title="Request Full Access Permission"
style={styles.button}
/>
</Step>
</ScrollView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 10,
paddingVertical: 16,
},
intro: {
marginBottom: 16,
color: '#444',
},
step: {
marginBottom: 18,
},
description: {
marginBottom: 12,
color: '#666',
},
button: {
marginBottom: 8,
},
});
Loading
Loading