diff --git a/docs/pages/tutorial/build-a-screen.mdx b/docs/pages/tutorial/build-a-screen.mdx index dd2ef96ec4017f..fae1b511c74000 100644 --- a/docs/pages/tutorial/build-a-screen.mdx +++ b/docs/pages/tutorial/build-a-screen.mdx @@ -63,7 +63,7 @@ Now that we've broken down the UI into smaller chunks, we're ready to start codi We'll use `expo-image` library to display the image in the app. It provides a cross-platform `` component to load and render an image. -Stop the development server by pressing Ctrl + c in the terminal. Then, install the `expo-image` library: +The `expo-image` library comes out of the box, but if you don't have it, stop the development server by pressing Ctrl + c in the terminal. Then, install the `expo-image` library: diff --git a/docs/pages/versions/unversioned/sdk/maps.mdx b/docs/pages/versions/unversioned/sdk/maps.mdx index da3569a8ee0c77..2bf8b8d234c315 100644 --- a/docs/pages/versions/unversioned/sdk/maps.mdx +++ b/docs/pages/versions/unversioned/sdk/maps.mdx @@ -161,7 +161,7 @@ export default function App() { ```js import { AppleMaps, GoogleMaps } from 'expo-maps'; -// ApplesMaps.View and GoogleMaps.View are the React components +// AppleMaps.View and GoogleMaps.View are the React components ``` diff --git a/docs/pages/versions/unversioned/sdk/widgets.mdx b/docs/pages/versions/unversioned/sdk/widgets.mdx index 250cfb07dff763..aa1d70ad3e2305 100644 --- a/docs/pages/versions/unversioned/sdk/widgets.mdx +++ b/docs/pages/versions/unversioned/sdk/widgets.mdx @@ -1,6 +1,6 @@ --- title: Widgets -description: A library that enables creation of iOS home screen widgets and Live Activities using Expo UI components. +description: A library to build iOS home screen widgets and Live Activities using Expo UI components. sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-widgets' packageName: 'expo-widgets' platforms: ['ios'] @@ -148,20 +148,21 @@ You can configure `expo-widgets` using its built-in [config plugin](/config-plug ### Widgets -#### Basic widget +#### Prerequisite: Registering widget layout -The simplest way to create a widget is using `updateWidgetSnapshot`. This creates a widget with a single timeline entry that displays immediately. +Widget components must be registered once using `registerWidgetLayout` and marked with the `'widget'` directive. ```tsx import { Text, VStack } from '@expo/ui/swift-ui'; import { font, foregroundStyle } from '@expo/ui/swift-ui/modifiers'; -import { updateWidgetSnapshot, WidgetBase } from 'expo-widgets'; +import { registerWidgetLayout, WidgetBase } from 'expo-widgets'; type MyWidgetProps = { count: number; }; const MyWidget = (props: WidgetBase) => { + 'widget'; return ( @@ -171,37 +172,39 @@ const MyWidget = (props: WidgetBase) => { ); }; -// Update the widget -updateWidgetSnapshot('MyWidget', MyWidget, { count: 5 }); +registerWidgetLayout('MyWidget', MyWidget); ``` The widget name (`'MyWidget'`) must match the `name` field in your widget configuration in the [app config](/workflow/configuration/). -#### Timeline widget +#### Basic widget -Use `updateWidgetTimeline` to schedule widget updates at specific time. System will automatically update the widget based on the timeline. +The simplest way to create a widget is to update it using `updateWidgetSnapshot`. This creates a widget with a single timeline entry that displays immediately. + +The example below continues from [Registering widget layout](#registering-widget-layout). ```tsx -import { Text, VStack } from '@expo/ui/swift-ui'; -import { updateWidgetTimeline, WidgetBase } from 'expo-widgets'; +import { updateWidgetSnapshot } from 'expo-widgets'; -const TimeWidget = (props: WidgetBase) => { - return ( - - {props.date.toLocaleTimeString()} - - ); -}; +// Update the widget +updateWidgetSnapshot('MyWidget', { count: 5 }); +``` + +#### Timeline widget -// Schedule updates for the next 3 hours -const dates = [ - new Date(), - new Date(Date.now() + 3600000), // 1 hour from now - new Date(Date.now() + 7200000), // 2 hours from now - new Date(Date.now() + 10800000), // 3 hours from now -]; +Use `updateWidgetTimeline` to schedule widget updates at specific time. System will automatically update the widget based on the timeline. + +The example below continues from [Registering widget layout](#registering-widget-layout). -updateWidgetTimeline('TimeWidget', dates, TimeWidget); +```tsx +import { updateWidgetTimeline } from 'expo-widgets'; + +updateWidgetTimeline('MyWidget', [ + { date: new Date(), props: { count: 1 } }, + { date: new Date(Date.now() + 3600000), props: { count: 2 } }, // 1 hour from now + { date: new Date(Date.now() + 7200000), props: { count: 3 } }, // 2 hours from now + { date: new Date(Date.now() + 10800000), props: { count: 4 } }, // 3 hours from now +]); ``` #### Responsive widget @@ -210,7 +213,7 @@ Widget component receives a `family` prop indicating which size is being rendere ```tsx import { HStack, Text, VStack } from '@expo/ui/swift-ui'; -import { updateWidgetSnapshot, WidgetBase } from 'expo-widgets'; +import { registerWidgetLayout, updateWidgetSnapshot, WidgetBase } from 'expo-widgets'; type WeatherWidgetProps = { temperature: number; @@ -218,6 +221,7 @@ type WeatherWidgetProps = { }; const WeatherWidget = (props: WidgetBase) => { + 'widget'; // Render different layouts based on size if (props.family === 'systemSmall') { return ( @@ -246,7 +250,9 @@ const WeatherWidget = (props: WidgetBase) => { ); }; -updateWidgetSnapshot('WeatherWidget', WeatherWidget, { +registerWidgetLayout('WeatherWidget', WeatherWidget); + +updateWidgetSnapshot('WeatherWidget', { temperature: 72, condition: 'Sunny', }); @@ -256,48 +262,75 @@ updateWidgetSnapshot('WeatherWidget', WeatherWidget, { Live Activities display real-time information on the Lock Screen and in the Dynamic Island on supported devices. +#### Prerequisite: Registering Live Activity layout + +Live Activity layouts must be registered once using `registerLiveActivityLayout` and marked with the `'widget'` directive. + +```tsx +import { Image, Text, VStack } from '@expo/ui/swift-ui'; +import { font, foregroundStyle, padding } from '@expo/ui/swift-ui/modifiers'; +import { registerLiveActivityLayout } from 'expo-widgets'; + +type DeliveryActivityProps = { + etaMinutes: number; + status: string; +}; + +const DeliveryActivity = (props: DeliveryActivityProps) => { + 'widget'; + return { + banner: ( + + + {props.status} + + Estimated arrival: {props.etaMinutes} minutes + + ), + compactLeading: , + compactTrailing: {props.etaMinutes} min, + minimal: , + expandedLeading: ( + + + Delivering + + ), + expandedTrailing: ( + + {props.etaMinutes} + minutes + + ), + expandedBottom: ( + + Driver: John Smith + Order #12345 + + ), + }; +}; + +registerLiveActivityLayout('DeliveryActivity', DeliveryActivity); +``` + #### Starting a Live Activity +The example below continues from [Registering Live Activity layout](#registering-live-activity-layout). + ```tsx import { Image, Text, VStack } from '@expo/ui/swift-ui'; -import { font, padding } from '@expo/ui/swift-ui/modifiers'; -import { LiveActivityComponent, startLiveActivity } from 'expo-widgets'; +import { font, foregroundStyle, padding } from '@expo/ui/swift-ui/modifiers'; +import { startLiveActivity } from 'expo-widgets'; import { Button, View } from 'react-native'; -const DeliveryActivity: LiveActivityComponent = () => ({ - banner: ( - - Your delivery is on the way - Estimated arrival: 15 minutes - - ), - compactLeading: , - compactTrailing: 15 min, - minimal: , - expandedLeading: ( - - - Delivering - - ), - expandedTrailing: ( - - 15 - minutes - - ), - expandedBottom: ( - - Driver: John Smith - Order #12345 - - ), -}); - function App() { const startDeliveryTracking = () => { // Start the Live Activity - const activityId = startLiveActivity('DeliveryActivity', DeliveryActivity); + const activityId = startLiveActivity('DeliveryActivity', { + etaMinutes: 15, + status: 'Your delivery is on the way', + }); console.log('Started Live Activity:', activityId); // Store activityId for later updates @@ -314,30 +347,16 @@ export default App; #### Updating a Live Activity -```tsx -import { updateLiveActivity, LiveActivityComponent } from 'expo-widgets'; +The example below continues from [Starting a Live Activity](#starting-a-live-activity). -const UpdatedDeliveryActivity: LiveActivityComponent = () => ({ - banner: ( - - - Delivery arriving soon! - - Estimated arrival: 2 minutes - - ), - compactLeading: , - compactTrailing: 2 min, - expandedTrailing: ( - - 2 - minutes - - ), -}); +```tsx +import { updateLiveActivity } from 'expo-widgets'; function updateDelivery(activityId: string) { - updateLiveActivity(activityId, 'DeliveryActivity', UpdatedDeliveryActivity); + updateLiveActivity(activityId, 'DeliveryActivity', { + etaMinutes: 2, + status: 'Delivery arriving soon!', + }); } ``` @@ -345,7 +364,7 @@ function updateDelivery(activityId: string) { {/* prettier-ignore */} ```tsx -import { updateWidgetSnapshot, updateWidgetTimeline, WidgetBase, WidgetFamily, updateLiveActivity, LiveActivityComponent } from 'expo-widgets'; +import * as ExpoWidgets from 'expo-widgets'; ``` diff --git a/docs/pages/versions/v55.0.0/sdk/widgets.mdx b/docs/pages/versions/v55.0.0/sdk/widgets.mdx index 250cfb07dff763..aa1d70ad3e2305 100644 --- a/docs/pages/versions/v55.0.0/sdk/widgets.mdx +++ b/docs/pages/versions/v55.0.0/sdk/widgets.mdx @@ -1,6 +1,6 @@ --- title: Widgets -description: A library that enables creation of iOS home screen widgets and Live Activities using Expo UI components. +description: A library to build iOS home screen widgets and Live Activities using Expo UI components. sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-widgets' packageName: 'expo-widgets' platforms: ['ios'] @@ -148,20 +148,21 @@ You can configure `expo-widgets` using its built-in [config plugin](/config-plug ### Widgets -#### Basic widget +#### Prerequisite: Registering widget layout -The simplest way to create a widget is using `updateWidgetSnapshot`. This creates a widget with a single timeline entry that displays immediately. +Widget components must be registered once using `registerWidgetLayout` and marked with the `'widget'` directive. ```tsx import { Text, VStack } from '@expo/ui/swift-ui'; import { font, foregroundStyle } from '@expo/ui/swift-ui/modifiers'; -import { updateWidgetSnapshot, WidgetBase } from 'expo-widgets'; +import { registerWidgetLayout, WidgetBase } from 'expo-widgets'; type MyWidgetProps = { count: number; }; const MyWidget = (props: WidgetBase) => { + 'widget'; return ( @@ -171,37 +172,39 @@ const MyWidget = (props: WidgetBase) => { ); }; -// Update the widget -updateWidgetSnapshot('MyWidget', MyWidget, { count: 5 }); +registerWidgetLayout('MyWidget', MyWidget); ``` The widget name (`'MyWidget'`) must match the `name` field in your widget configuration in the [app config](/workflow/configuration/). -#### Timeline widget +#### Basic widget -Use `updateWidgetTimeline` to schedule widget updates at specific time. System will automatically update the widget based on the timeline. +The simplest way to create a widget is to update it using `updateWidgetSnapshot`. This creates a widget with a single timeline entry that displays immediately. + +The example below continues from [Registering widget layout](#registering-widget-layout). ```tsx -import { Text, VStack } from '@expo/ui/swift-ui'; -import { updateWidgetTimeline, WidgetBase } from 'expo-widgets'; +import { updateWidgetSnapshot } from 'expo-widgets'; -const TimeWidget = (props: WidgetBase) => { - return ( - - {props.date.toLocaleTimeString()} - - ); -}; +// Update the widget +updateWidgetSnapshot('MyWidget', { count: 5 }); +``` + +#### Timeline widget -// Schedule updates for the next 3 hours -const dates = [ - new Date(), - new Date(Date.now() + 3600000), // 1 hour from now - new Date(Date.now() + 7200000), // 2 hours from now - new Date(Date.now() + 10800000), // 3 hours from now -]; +Use `updateWidgetTimeline` to schedule widget updates at specific time. System will automatically update the widget based on the timeline. + +The example below continues from [Registering widget layout](#registering-widget-layout). -updateWidgetTimeline('TimeWidget', dates, TimeWidget); +```tsx +import { updateWidgetTimeline } from 'expo-widgets'; + +updateWidgetTimeline('MyWidget', [ + { date: new Date(), props: { count: 1 } }, + { date: new Date(Date.now() + 3600000), props: { count: 2 } }, // 1 hour from now + { date: new Date(Date.now() + 7200000), props: { count: 3 } }, // 2 hours from now + { date: new Date(Date.now() + 10800000), props: { count: 4 } }, // 3 hours from now +]); ``` #### Responsive widget @@ -210,7 +213,7 @@ Widget component receives a `family` prop indicating which size is being rendere ```tsx import { HStack, Text, VStack } from '@expo/ui/swift-ui'; -import { updateWidgetSnapshot, WidgetBase } from 'expo-widgets'; +import { registerWidgetLayout, updateWidgetSnapshot, WidgetBase } from 'expo-widgets'; type WeatherWidgetProps = { temperature: number; @@ -218,6 +221,7 @@ type WeatherWidgetProps = { }; const WeatherWidget = (props: WidgetBase) => { + 'widget'; // Render different layouts based on size if (props.family === 'systemSmall') { return ( @@ -246,7 +250,9 @@ const WeatherWidget = (props: WidgetBase) => { ); }; -updateWidgetSnapshot('WeatherWidget', WeatherWidget, { +registerWidgetLayout('WeatherWidget', WeatherWidget); + +updateWidgetSnapshot('WeatherWidget', { temperature: 72, condition: 'Sunny', }); @@ -256,48 +262,75 @@ updateWidgetSnapshot('WeatherWidget', WeatherWidget, { Live Activities display real-time information on the Lock Screen and in the Dynamic Island on supported devices. +#### Prerequisite: Registering Live Activity layout + +Live Activity layouts must be registered once using `registerLiveActivityLayout` and marked with the `'widget'` directive. + +```tsx +import { Image, Text, VStack } from '@expo/ui/swift-ui'; +import { font, foregroundStyle, padding } from '@expo/ui/swift-ui/modifiers'; +import { registerLiveActivityLayout } from 'expo-widgets'; + +type DeliveryActivityProps = { + etaMinutes: number; + status: string; +}; + +const DeliveryActivity = (props: DeliveryActivityProps) => { + 'widget'; + return { + banner: ( + + + {props.status} + + Estimated arrival: {props.etaMinutes} minutes + + ), + compactLeading: , + compactTrailing: {props.etaMinutes} min, + minimal: , + expandedLeading: ( + + + Delivering + + ), + expandedTrailing: ( + + {props.etaMinutes} + minutes + + ), + expandedBottom: ( + + Driver: John Smith + Order #12345 + + ), + }; +}; + +registerLiveActivityLayout('DeliveryActivity', DeliveryActivity); +``` + #### Starting a Live Activity +The example below continues from [Registering Live Activity layout](#registering-live-activity-layout). + ```tsx import { Image, Text, VStack } from '@expo/ui/swift-ui'; -import { font, padding } from '@expo/ui/swift-ui/modifiers'; -import { LiveActivityComponent, startLiveActivity } from 'expo-widgets'; +import { font, foregroundStyle, padding } from '@expo/ui/swift-ui/modifiers'; +import { startLiveActivity } from 'expo-widgets'; import { Button, View } from 'react-native'; -const DeliveryActivity: LiveActivityComponent = () => ({ - banner: ( - - Your delivery is on the way - Estimated arrival: 15 minutes - - ), - compactLeading: , - compactTrailing: 15 min, - minimal: , - expandedLeading: ( - - - Delivering - - ), - expandedTrailing: ( - - 15 - minutes - - ), - expandedBottom: ( - - Driver: John Smith - Order #12345 - - ), -}); - function App() { const startDeliveryTracking = () => { // Start the Live Activity - const activityId = startLiveActivity('DeliveryActivity', DeliveryActivity); + const activityId = startLiveActivity('DeliveryActivity', { + etaMinutes: 15, + status: 'Your delivery is on the way', + }); console.log('Started Live Activity:', activityId); // Store activityId for later updates @@ -314,30 +347,16 @@ export default App; #### Updating a Live Activity -```tsx -import { updateLiveActivity, LiveActivityComponent } from 'expo-widgets'; +The example below continues from [Starting a Live Activity](#starting-a-live-activity). -const UpdatedDeliveryActivity: LiveActivityComponent = () => ({ - banner: ( - - - Delivery arriving soon! - - Estimated arrival: 2 minutes - - ), - compactLeading: , - compactTrailing: 2 min, - expandedTrailing: ( - - 2 - minutes - - ), -}); +```tsx +import { updateLiveActivity } from 'expo-widgets'; function updateDelivery(activityId: string) { - updateLiveActivity(activityId, 'DeliveryActivity', UpdatedDeliveryActivity); + updateLiveActivity(activityId, 'DeliveryActivity', { + etaMinutes: 2, + status: 'Delivery arriving soon!', + }); } ``` @@ -345,7 +364,7 @@ function updateDelivery(activityId: string) { {/* prettier-ignore */} ```tsx -import { updateWidgetSnapshot, updateWidgetTimeline, WidgetBase, WidgetFamily, updateLiveActivity, LiveActivityComponent } from 'expo-widgets'; +import * as ExpoWidgets from 'expo-widgets'; ``` diff --git a/docs/public/static/data/unversioned/expo-widgets.json b/docs/public/static/data/unversioned/expo-widgets.json index eeec8dedb56bb2..4ae891558aae26 100644 --- a/docs/public/static/data/unversioned/expo-widgets.json +++ b/docs/public/static/data/unversioned/expo-widgets.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-widgets","variant":"project","kind":1,"children":[{"name":"ExpoLiveActivityEntry","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Defines the layout sections for an iOS Live Activity."}]},"children":[{"name":"banner","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The main banner content displayed in Notifications Center."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"bannerSmall","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The small banner content displayed in CarPlay and WatchOS. Falls back to "},{"kind":"code","text":"`banner`"},{"kind":"text","text":" if not provided."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"compactLeading","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The leading content in the compact Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"compactTrailing","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The trailing content in the compact Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"expandedBottom","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The bottom content in the expanded Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"expandedCenter","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The center content in the expanded Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"expandedLeading","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The leading content in the expanded Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"expandedTrailing","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The trailing content in the expanded Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"minimal","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The minimal content shown when the Dynamic Island is in its smallest form."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}}]},{"name":"ExpoWidgetsEvents","variant":"declaration","kind":2097152,"children":[{"name":"onUserInteraction","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Function that is invoked when user interacts with a widget."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"event","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Interaction event details."}]},"type":{"type":"reference","name":"UserInteractionEvent","package":"expo-widgets"}}],"type":{"type":"intrinsic","name":"void"}}]}}}]},{"name":"LiveActivityComponent","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"A function that returns the layout for a Live Activity."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","name":"ExpoLiveActivityEntry","package":"expo-widgets"}}]}}},{"name":"UserInteractionEvent","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Event emitted when a user interacts with a widget."}]},"children":[{"name":"source","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Widget that triggered the interaction."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"target","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Button/toggle that was pressed."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"timestamp","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Timestamp of the event."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"type","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The event type identifier."}]},"type":{"type":"literal","value":"ExpoWidgetsUserInteraction"}}]},{"name":"WidgetBase","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Props passed to a widget component."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"type":{"type":"intrinsic","name":"object"},"default":{"type":"intrinsic","name":"object"}}],"type":{"type":"intersection","types":[{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"date","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The date of this timeline entry."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Date"},"name":"Date","package":"typescript"}},{"name":"family","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The widget family."}]},"type":{"type":"reference","name":"WidgetFamily","package":"expo-widgets"}}]}},{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}]}},{"name":"WidgetFamily","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"The widget family (size).\n- "},{"kind":"code","text":"`systemSmall`"},{"kind":"text","text":" - Small square widget (2x2 grid).\n- "},{"kind":"code","text":"`systemMedium`"},{"kind":"text","text":" - Medium widget (4x2 grid).\n- "},{"kind":"code","text":"`systemLarge`"},{"kind":"text","text":" - Large widget (4x4 grid).\n- "},{"kind":"code","text":"`systemExtraLarge`"},{"kind":"text","text":" - Extra large widget (iPad only, 6x4 grid).\n- "},{"kind":"code","text":"`accessoryCircular`"},{"kind":"text","text":" - Circular accessory widget for the Lock Screen.\n- "},{"kind":"code","text":"`accessoryRectangular`"},{"kind":"text","text":" - Rectangular accessory widget for the Lock Screen.\n- "},{"kind":"code","text":"`accessoryInline`"},{"kind":"text","text":" - Inline accessory widget for the Lock Screen."}]},"type":{"type":"union","types":[{"type":"literal","value":"systemSmall"},{"type":"literal","value":"systemMedium"},{"type":"literal","value":"systemLarge"},{"type":"literal","value":"systemExtraLarge"},{"type":"literal","value":"accessoryCircular"},{"type":"literal","value":"accessoryRectangular"},{"type":"literal","value":"accessoryInline"}]}},{"name":"addUserInteractionListener","variant":"declaration","kind":64,"signatures":[{"name":"addUserInteractionListener","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Adds a listener for widget interaction events (for example, button taps)."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"An event subscription that can be used to remove the listener."}]}]},"parameters":[{"name":"listener","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Callback function to handle user interaction events."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"event","variant":"param","kind":32768,"type":{"type":"reference","name":"UserInteractionEvent","package":"expo-widgets"}}],"type":{"type":"intrinsic","name":"void"}}]}}}],"type":{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/EventEmitter.ts","qualifiedName":"EventSubscription"},"name":"EventSubscription","package":"expo-modules-core"}}]},{"name":"startLiveActivity","variant":"declaration","kind":64,"signatures":[{"name":"startLiveActivity","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Starts a new Live Activity on iOS.\nLive Activities display real-time information on the Lock Screen and in the Dynamic Island."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"The unique identifier of the started Live Activity."}]}]},"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the Live Activity to start."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"liveActivity","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A function that returns the Live Activity layout configuration."}]},"type":{"type":"reference","name":"LiveActivityComponent","package":"expo-widgets"}},{"name":"url","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"An optional deep link URL to open when the user taps the Live Activity."}]},"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"intrinsic","name":"string"}}]},{"name":"updateLiveActivity","variant":"declaration","kind":64,"signatures":[{"name":"updateLiveActivity","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Updates an existing Live Activity with new content."}]},"parameters":[{"name":"id","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The unique identifier of the Live Activity to update (returned from "},{"kind":"code","text":"`startLiveActivity`"},{"kind":"text","text":")."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the Live Activity."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"liveActivity","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A function that returns the updated Live Activity layout configuration."}]},"type":{"type":"reference","name":"LiveActivityComponent","package":"expo-widgets"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"updateWidgetSnapshot","variant":"declaration","kind":64,"signatures":[{"name":"updateWidgetSnapshot","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Updates a widget with a single snapshot entry for the current time.\nThis is a convenience wrapper around "},{"kind":"code","text":"`updateWidgetTimeline`"},{"kind":"text","text":" for widgets that don't need multiple timeline entries."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"comment":{"summary":[{"kind":"text","text":"The type of custom props passed to the widget."}]},"type":{"type":"intrinsic","name":"object"}}],"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the widget to update."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"widget","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A function component that renders the widget content for a given set of props."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"p","variant":"param","kind":32768,"type":{"type":"reference","typeArguments":[{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}],"name":"WidgetBase","package":"expo-widgets"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"React.JSX.Element"}}]}}},{"name":"props","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional custom props to pass to the widget component."}]},"type":{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}},{"name":"updateFunction","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional name of a function to call for dynamic updates."}]},"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"updateWidgetTimeline","variant":"declaration","kind":64,"signatures":[{"name":"updateWidgetTimeline","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Updates a widget's timeline with multiple entries that will be displayed at scheduled times.\nThe widget system will automatically switch between entries based on their timestamps."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"comment":{"summary":[{"kind":"text","text":"The type of custom props passed to the widget."}]},"type":{"type":"intrinsic","name":"object"}}],"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the widget to update."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"dates","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"An array of dates representing when each timeline entry should be displayed."}]},"type":{"type":"array","elementType":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Date"},"name":"Date","package":"typescript"}}},{"name":"widget","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A function component that renders the widget content for a given set of props."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"p","variant":"param","kind":32768,"type":{"type":"reference","typeArguments":[{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}],"name":"WidgetBase","package":"expo-widgets"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"React.JSX.Element"}}]}}},{"name":"props","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional custom props to pass to the widget component."}]},"type":{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}},{"name":"updateFunction","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional name of a function to call for dynamic updates."}]},"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"intrinsic","name":"void"}}]}],"packageName":"expo-widgets"} \ No newline at end of file +{"schemaVersion":"2.0","name":"expo-widgets","variant":"project","kind":1,"children":[{"name":"ExpoLiveActivityEntry","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Defines the layout sections for an iOS Live Activity."}]},"children":[{"name":"banner","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The main banner content displayed in Notifications Center."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"bannerSmall","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The small banner content displayed in CarPlay and WatchOS. Falls back to "},{"kind":"code","text":"`banner`"},{"kind":"text","text":" if not provided."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"compactLeading","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The leading content in the compact Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"compactTrailing","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The trailing content in the compact Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"expandedBottom","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The bottom content in the expanded Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"expandedCenter","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The center content in the expanded Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"expandedLeading","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The leading content in the expanded Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"expandedTrailing","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The trailing content in the expanded Dynamic Island presentation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"minimal","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The minimal content shown when the Dynamic Island is in its smallest form."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}}]},{"name":"ExpoWidgetsEvents","variant":"declaration","kind":2097152,"children":[{"name":"onExpoWidgetsPushToStartTokenReceived","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Function that is invoked when a push-to-start token is received."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"event","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Token event details."}]},"type":{"type":"reference","name":"PushToStartTokenEvent","package":"expo-widgets"}}],"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onExpoWidgetsTokenReceived","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Function that is invoked when a push token is received for a live activity."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"event","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Token event details."}]},"type":{"type":"reference","name":"PushTokenEvent","package":"expo-widgets"}}],"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onExpoWidgetsUserInteraction","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Function that is invoked when user interacts with a widget."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"event","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Interaction event details."}]},"type":{"type":"reference","name":"UserInteractionEvent","package":"expo-widgets"}}],"type":{"type":"intrinsic","name":"void"}}]}}}]},{"name":"LiveActivityComponent","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"A function that returns the layout for a Live Activity."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","name":"ExpoLiveActivityEntry","package":"expo-widgets"}}]}}},{"name":"LiveActivityDismissalPolicy","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Dismissal policy for ending a live activity."}]},"type":{"type":"union","types":[{"type":"literal","value":"default"},{"type":"literal","value":"immediate"}]}},{"name":"LiveActivityInfo","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Information about a running live activity."}]},"children":[{"name":"id","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The unique identifier of the live activity."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"name","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The name of the live activity."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"pushToken","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The push token for the live activity, if available."}]},"type":{"type":"intrinsic","name":"string"}}]},{"name":"PushTokenEvent","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Event emitted when a push token is received for a live activity."}]},"children":[{"name":"activityId","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The ID of the live activity."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"pushToken","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The push token for the live activity."}]},"type":{"type":"intrinsic","name":"string"}}]},{"name":"PushToStartTokenEvent","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Event emitted when a push-to-start token is received."}]},"children":[{"name":"activityPushToStartToken","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The push-to-start token for starting live activities remotely."}]},"type":{"type":"intrinsic","name":"string"}}]},{"name":"UserInteractionEvent","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Event emitted when a user interacts with a widget."}]},"children":[{"name":"source","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Widget that triggered the interaction."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"target","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Button/toggle that was pressed."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"timestamp","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Timestamp of the event."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"type","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The event type identifier."}]},"type":{"type":"literal","value":"ExpoWidgetsUserInteraction"}}]},{"name":"WidgetBase","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Props passed to a widget component."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"type":{"type":"intrinsic","name":"object"},"default":{"type":"intrinsic","name":"object"}}],"type":{"type":"intersection","types":[{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"date","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The date of this timeline entry."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Date"},"name":"Date","package":"typescript"}},{"name":"family","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The widget family."}]},"type":{"type":"reference","name":"WidgetFamily","package":"expo-widgets"}}]}},{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}]}},{"name":"WidgetFamily","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"The widget family (size).\n- "},{"kind":"code","text":"`systemSmall`"},{"kind":"text","text":" - Small square widget (2x2 grid).\n- "},{"kind":"code","text":"`systemMedium`"},{"kind":"text","text":" - Medium widget (4x2 grid).\n- "},{"kind":"code","text":"`systemLarge`"},{"kind":"text","text":" - Large widget (4x4 grid).\n- "},{"kind":"code","text":"`systemExtraLarge`"},{"kind":"text","text":" - Extra large widget (iPad only, 6x4 grid).\n- "},{"kind":"code","text":"`accessoryCircular`"},{"kind":"text","text":" - Circular accessory widget for the Lock Screen.\n- "},{"kind":"code","text":"`accessoryRectangular`"},{"kind":"text","text":" - Rectangular accessory widget for the Lock Screen.\n- "},{"kind":"code","text":"`accessoryInline`"},{"kind":"text","text":" - Inline accessory widget for the Lock Screen."}]},"type":{"type":"union","types":[{"type":"literal","value":"systemSmall"},{"type":"literal","value":"systemMedium"},{"type":"literal","value":"systemLarge"},{"type":"literal","value":"systemExtraLarge"},{"type":"literal","value":"accessoryCircular"},{"type":"literal","value":"accessoryRectangular"},{"type":"literal","value":"accessoryInline"}]}},{"name":"addPushTokenListener","variant":"declaration","kind":64,"signatures":[{"name":"addPushTokenListener","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Adds a listener for push token updates."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"An event subscription that can be used to remove the listener."}]}]},"parameters":[{"name":"listener","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Callback function to handle push token updates."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"event","variant":"param","kind":32768,"type":{"type":"reference","name":"PushTokenEvent","package":"expo-widgets"}}],"type":{"type":"intrinsic","name":"void"}}]}}}],"type":{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/EventEmitter.ts","qualifiedName":"EventSubscription"},"name":"EventSubscription","package":"expo-modules-core"}}]},{"name":"addPushToStartTokenListener","variant":"declaration","kind":64,"signatures":[{"name":"addPushToStartTokenListener","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Adds a listener for push-to-start token events.\nThis token can be used to start live activities remotely via APNs."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"An event subscription that can be used to remove the listener."}]}]},"parameters":[{"name":"listener","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Callback function to handle push-to-start token events."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"event","variant":"param","kind":32768,"type":{"type":"reference","name":"PushToStartTokenEvent","package":"expo-widgets"}}],"type":{"type":"intrinsic","name":"void"}}]}}}],"type":{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/EventEmitter.ts","qualifiedName":"EventSubscription"},"name":"EventSubscription","package":"expo-modules-core"}}]},{"name":"addUserInteractionListener","variant":"declaration","kind":64,"signatures":[{"name":"addUserInteractionListener","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Adds a listener for widget interaction events (for example, button taps)."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"An event subscription that can be used to remove the listener."}]}]},"parameters":[{"name":"listener","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Callback function to handle user interaction events."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"event","variant":"param","kind":32768,"type":{"type":"reference","name":"UserInteractionEvent","package":"expo-widgets"}}],"type":{"type":"intrinsic","name":"void"}}]}}}],"type":{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/EventEmitter.ts","qualifiedName":"EventSubscription"},"name":"EventSubscription","package":"expo-modules-core"}}]},{"name":"endLiveActivity","variant":"declaration","kind":64,"signatures":[{"name":"endLiveActivity","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Ends a live activity."}]},"parameters":[{"name":"activityId","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The ID of the live activity to end."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"dismissalPolicy","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"How the live activity should be dismissed from the screen."}]},"type":{"type":"reference","name":"LiveActivityDismissalPolicy","package":"expo-widgets"},"defaultValue":"'default'"}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"getLiveActivities","variant":"declaration","kind":64,"signatures":[{"name":"getLiveActivities","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Gets all currently running live activities."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"An array of live activity information objects."}]}]},"type":{"type":"array","elementType":{"type":"reference","name":"LiveActivityInfo","package":"expo-widgets"}}}]},{"name":"getLiveActivityPushToken","variant":"declaration","kind":64,"signatures":[{"name":"getLiveActivityPushToken","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Gets the push token for a specific live activity."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A promise that resolves to the push token, or null if not available."}]}]},"parameters":[{"name":"activityId","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The ID of the live activity."}]},"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"literal","value":null}]}],"name":"Promise","package":"typescript"}}]},{"name":"registerLiveActivityLayout","variant":"declaration","kind":64,"signatures":[{"name":"registerLiveActivityLayout","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Registers a Live Activity layout for a given activity name."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"type":{"type":"intrinsic","name":"object"}}],"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the Live Activity."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"widget","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A function that returns the Live Activity layout marked with "},{"kind":"code","text":"`'widget'`"},{"kind":"text","text":" directive."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}}],"type":{"type":"reference","name":"ExpoLiveActivityEntry","package":"expo-widgets"}}]}}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"registerWidgetLayout","variant":"declaration","kind":64,"signatures":[{"name":"registerWidgetLayout","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Registers a widget layout for a given widget name."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"type":{"type":"intrinsic","name":"object"}}],"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the widget."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"widget","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A React component that renders the widget layout marked with "},{"kind":"code","text":"`'widget'`"},{"kind":"text","text":" directive."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","typeArguments":[{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}],"name":"WidgetBase","package":"expo-widgets"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"React.JSX.Element"}}]}}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"startLiveActivity","variant":"declaration","kind":64,"signatures":[{"name":"startLiveActivity","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Starts a new Live Activity on iOS.\nLive Activities display real-time information on the Lock Screen and in the Dynamic Island."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"The unique identifier of the started Live Activity."}]}]},"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the Live Activity to start."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"props","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional props to pass to the Live Activity layout."}]},"type":{"type":"intrinsic","name":"object"}},{"name":"url","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"An optional deep link URL to open when the user taps the Live Activity."}]},"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"intrinsic","name":"string"}}]},{"name":"updateLiveActivity","variant":"declaration","kind":64,"signatures":[{"name":"updateLiveActivity","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Updates an existing Live Activity with new content."}]},"parameters":[{"name":"id","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The unique identifier of the Live Activity to update (returned from "},{"kind":"code","text":"`startLiveActivity`"},{"kind":"text","text":")."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the Live Activity."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"props","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional props to pass to the Live Activity layout."}]},"type":{"type":"intrinsic","name":"object"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"updateWidgetSnapshot","variant":"declaration","kind":64,"signatures":[{"name":"updateWidgetSnapshot","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Updates a widget with a single snapshot entry for the current time.\nThis is a convenience wrapper around "},{"kind":"code","text":"`updateWidgetTimeline`"},{"kind":"text","text":" for widgets that don't need multiple timeline entries."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"comment":{"summary":[{"kind":"text","text":"The type of custom props passed to the widget."}]},"type":{"type":"intrinsic","name":"object"}}],"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the widget to update."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"props","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional custom props to pass to the widget component."}]},"type":{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"updateWidgetTimeline","variant":"declaration","kind":64,"signatures":[{"name":"updateWidgetTimeline","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Updates a widget's timeline with multiple entries that will be displayed at scheduled times.\nThe widget system will automatically switch between entries based on their timestamps."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"comment":{"summary":[{"kind":"text","text":"The type of custom props passed to the widget."}]},"type":{"type":"intrinsic","name":"object"}}],"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The name/identifier of the widget to update."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"timeline","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Timeline entries with the dates and optional props for each entry."}]},"type":{"type":"array","elementType":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"date","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Date"},"name":"Date","package":"typescript"}},{"name":"props","variant":"declaration","kind":1024,"flags":{"isOptional":true},"type":{"type":"reference","name":"T","package":"expo-widgets","refersToTypeParameter":true}}]}}}}],"type":{"type":"intrinsic","name":"void"}}]}],"packageName":"expo-widgets"} \ No newline at end of file diff --git a/packages/expo-brownfield/CHANGELOG.md b/packages/expo-brownfield/CHANGELOG.md index 73ccf9bdb27e92..960eb00b461e01 100644 --- a/packages/expo-brownfield/CHANGELOG.md +++ b/packages/expo-brownfield/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- [Android] improve security of env injection in publishing in [#43059](https://github.com/expo/expo/pull/43059) by [@pmleczek](https://github.com/pmleczek) + ### 💡 Others ## 55.0.8 — 2026-02-16 diff --git a/packages/expo-brownfield/e2e/plugin/__tests__/plugin-android.test.ts b/packages/expo-brownfield/e2e/plugin/__tests__/plugin-android.test.ts index f3c362fe23de67..2fd9050652780d 100644 --- a/packages/expo-brownfield/e2e/plugin/__tests__/plugin-android.test.ts +++ b/packages/expo-brownfield/e2e/plugin/__tests__/plugin-android.test.ts @@ -7,7 +7,7 @@ import { getAndroidPaths, getPublishingLines, } from '../../utils/android'; -import { createTempProject, cleanUpProject, createEnvFile } from '../../utils/project'; +import { createTempProject, cleanUpProject } from '../../utils/project'; import { expectFile, expectFiles } from '../../utils/test'; import { PluginProps } from '../../utils/types'; @@ -104,7 +104,7 @@ describe('plugin for android', () => { content: ['group = "com.example.test.app"', 'version = "1.0.0"'], }); - const publishingLines = ['localDefault {', 'type = "localMaven"']; + const publishingLines = ['localDefault {', 'type.set("localMaven")']; validateBuildGradle(TEMP_DIR, publishingLines); }); @@ -315,12 +315,11 @@ describe('plugin for android', () => { }, }, ] as PluginProps['android']['publishing']; - await createEnvFile(TEMP_DIR, ENV); await setupPlugin(TEMP_DIR, { publishing: PUBLISHING, }); - validateBuildGradle(TEMP_DIR, getPublishingLines(TEMP_DIR, PUBLISHING, ENV)); + validateBuildGradle(TEMP_DIR, getPublishingLines(TEMP_DIR, PUBLISHING)); }); /** diff --git a/packages/expo-brownfield/e2e/utils/android.ts b/packages/expo-brownfield/e2e/utils/android.ts index c1e13cdfa4f4ac..0467cad7598dd5 100644 --- a/packages/expo-brownfield/e2e/utils/android.ts +++ b/packages/expo-brownfield/e2e/utils/android.ts @@ -134,8 +134,7 @@ export const getAndroidPaths = (packageId: string): AndroidPaths => { */ export const getPublishingLines = ( projectRoot: string, - publishing: PluginProps['android']['publishing'], - env?: Record + publishing: PluginProps['android']['publishing'] ): string[] => { const lines = []; const count = { @@ -151,38 +150,40 @@ export const getPublishingLines = ( switch (publication.type) { case 'localMaven': - lines.push('localDefault {', 'type = "localMaven"'); + lines.push('localDefault {', 'type.set("localMaven")'); break; case 'localDirectory': name = publication.name ?? `localDirectory${count['localDirectory']} {`; url = path.isAbsolute(publication.path) ? publication.path : path.join(projectRoot, publication.path); - lines.push(name, 'type = "localDirectory"', `url = "file://${url}"`); + lines.push(name, 'type.set("localDirectory")', `url.set("file://${url}")`); break; case 'remotePublic': name = publication.name ?? `remotePublic${count['remotePublic']} {`; url = publication.url; - lines.push(name, 'type = "remotePublic"', `url = "${url}"`); + lines.push(name, 'type.set("remotePublic")', `url.set("${url}")`); if (publication.allowInsecure) { - lines.push('allowInsecure = true'); + lines.push('allowInsecure.set(true)'); } break; case 'remotePrivate': name = publication.name ?? `remotePrivate${count['remotePrivate']} {`; - url = typeof publication.url === 'object' ? env[publication.url.variable] : publication.url; + url = + typeof publication.url === 'object' + ? `url.set(providers.environmentVariable("${publication.url.variable}").orElse(""))` + : `url.set("${publication.url}")`; username = typeof publication.username === 'object' - ? env[publication.username.variable] - : publication.username; + ? `username.set(providers.environmentVariable("${publication.username.variable}").orElse(""))` + : `username.set("${publication.username}")`; password = typeof publication.password === 'object' - ? env[publication.password.variable] - : publication.password; - lines.push(name, 'type = "remotePrivate"', `url = "${url}"`); - lines.push(`username = "${username}"`, `password = "${password}"`); + ? `password.set(providers.environmentVariable("${publication.password.variable}").orElse(""))` + : `password.set("${publication.password}")`; + lines.push(name, 'type.set("remotePrivate")', url, username, password); if (publication.allowInsecure) { - lines.push('allowInsecure = true'); + lines.push('allowInsecure.set(true)'); } break; } diff --git a/packages/expo-brownfield/e2e/utils/project.ts b/packages/expo-brownfield/e2e/utils/project.ts index d1c16733a70935..e1ab0d42df560b 100644 --- a/packages/expo-brownfield/e2e/utils/project.ts +++ b/packages/expo-brownfield/e2e/utils/project.ts @@ -203,19 +203,3 @@ export const createTemplateOverrides = async (projectRoot: string, entries: Temp await fs.promises.writeFile(templatePath, entry.content); } }; - -/** - * Create an .env file with specified values - */ -export const createEnvFile = async (projectRoot: string, variables: Record) => { - const envFilePath = path.join(projectRoot, '.env'); - if (fs.existsSync(envFilePath)) { - await fs.promises.rm(envFilePath, { force: true }); - } - await fs.promises.writeFile( - envFilePath, - Object.entries(variables) - .map(([key, value]) => `${key}=${value}`) - .join('\n') - ); -}; diff --git a/packages/expo-brownfield/package.json b/packages/expo-brownfield/package.json index eb2dbfae890ce8..2476f664d4cbd0 100644 --- a/packages/expo-brownfield/package.json +++ b/packages/expo-brownfield/package.json @@ -49,6 +49,7 @@ "preset": "expo-module-scripts" }, "dependencies": { + "@expo/env": "~2.1.0", "chalk": "^4.1.2", "commander": "^14.0.3", "diff": "^5.2.0", diff --git a/packages/expo-brownfield/plugin/build/android/types.d.ts b/packages/expo-brownfield/plugin/build/android/types.d.ts index a4a62185e269c5..0b2408faa564e2 100644 --- a/packages/expo-brownfield/plugin/build/android/types.d.ts +++ b/packages/expo-brownfield/plugin/build/android/types.d.ts @@ -13,10 +13,10 @@ export interface LocalDirectoryPublication { export interface RemotePublicPublication { type: 'remotePublic'; name?: string; - url: string; + url: string | EnvValue; allowInsecure?: boolean; } -export interface RemotePrivateBasicPublication { +export interface RemotePrivatePublication { type: 'remotePrivate'; name?: string; url: string | EnvValue; @@ -24,12 +24,7 @@ export interface RemotePrivateBasicPublication { password: string | EnvValue; allowInsecure?: boolean; } -export interface RemotePrivatePublicationInternal extends RemotePrivateBasicPublication { - url: string; - username: string; - password: string; -} -export type Publication = LocalMavenPublication | LocalDirectoryPublication | RemotePublicPublication | RemotePrivateBasicPublication; +export type Publication = LocalMavenPublication | LocalDirectoryPublication | RemotePublicPublication | RemotePrivatePublication; export interface PluginConfig { group: string; libraryName: string; diff --git a/packages/expo-brownfield/plugin/build/android/utils/repositories.js b/packages/expo-brownfield/plugin/build/android/utils/repositories.js index 06ea3aff4c4aa5..77bb7ef60215a4 100644 --- a/packages/expo-brownfield/plugin/build/android/utils/repositories.js +++ b/packages/expo-brownfield/plugin/build/android/utils/repositories.js @@ -6,13 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.addRepository = void 0; const node_path_1 = __importDefault(require("node:path")); const repositoryTemplates = { - localMaven: () => [' localDefault {', ' type = "localMaven"', ' }'], + localMaven: () => [' localDefault {', ' type.set("localMaven")', ' }'], localDirectory: (count, publication, projectRoot) => { const nameOrPlaceholder = publication.name ?? `localDirectory${count + 1}`; return [ ` ${nameOrPlaceholder} {`, - ' type = "localDirectory"', - ` url = "file://${standardizePath(publication.path, projectRoot)}"`, + ' type.set("localDirectory")', + ` url.set("file://${standardizePath(publication.path, projectRoot)}")`, ' }', ]; }, @@ -20,9 +20,9 @@ const repositoryTemplates = { const nameOrPlaceholder = publication.name ?? `remotePublic${count + 1}`; return [ ` ${nameOrPlaceholder} {`, - ' type = "remotePublic"', - ` url = "${publication.url}"`, - ` allowInsecure = ${publication.allowInsecure}`, + ' type.set("remotePublic")', + setProperty('url', publication.url), + ` allowInsecure.set(${publication.allowInsecure ?? false})`, ' }', ]; }, @@ -30,11 +30,11 @@ const repositoryTemplates = { const nameOrPlaceholder = publication.name ?? `remotePrivate${count + 1}`; return [ ` ${nameOrPlaceholder} {`, - ' type = "remotePrivate"', - ` url = "${publication.url}"`, - ` username = "${publication.username}"`, - ` password = "${publication.password}"`, - ` allowInsecure = ${publication.allowInsecure}`, + ' type.set("remotePrivate")', + setProperty('url', publication.url), + setProperty('username', publication.username), + setProperty('password', publication.password), + ` allowInsecure.set(${publication.allowInsecure ?? false})`, ' }', ]; }, @@ -46,10 +46,7 @@ const addRepository = (lines, projectRoot, publication) => { case 'localDirectory': case 'remotePublic': case 'remotePrivate': - if (publication.type === 'remotePrivate') { - publication = resolveEnv(publication); - } - return repositoryTemplates[publication.type](countOccurences(lines, `type = "${publication.type}"`), + return repositoryTemplates[publication.type](countOccurences(lines, `type.set("${publication.type}")`), // @ts-expect-error - TypeScript can't narrow union in fall-through case publication, projectRoot); default: @@ -65,24 +62,9 @@ const countOccurences = (lines, pattern) => { const standardizePath = (url, projectRoot) => { return node_path_1.default.isAbsolute(url) ? url : node_path_1.default.join(projectRoot, url); }; -const resolveEnv = (publication) => { - const publicationInternal = { - ...publication, - }; - if (typeof publication.url === 'object') { - publicationInternal.url = findEnvOrThrow(publication.url.variable); +const setProperty = (property, value) => { + if (typeof value === 'string') { + return ` ${property}.set("${value}")`; } - if (typeof publication.username === 'object') { - publicationInternal.username = findEnvOrThrow(publication.username.variable); - } - if (typeof publication.password === 'object') { - publicationInternal.password = findEnvOrThrow(publication.password.variable); - } - return publicationInternal; -}; -const findEnvOrThrow = (envVariable) => { - if (process.env[envVariable]) { - return process.env[envVariable]; - } - throw new Error(`Environment variable: "${envVariable}" used to define publishing configuration not found`); + return ` ${property}.set(providers.environmentVariable("${value.variable}").orElse(""))`; }; diff --git a/packages/expo-brownfield/plugin/src/android/types.ts b/packages/expo-brownfield/plugin/src/android/types.ts index b58842867b7721..6f0d547366d191 100644 --- a/packages/expo-brownfield/plugin/src/android/types.ts +++ b/packages/expo-brownfield/plugin/src/android/types.ts @@ -22,11 +22,11 @@ export interface LocalDirectoryPublication { export interface RemotePublicPublication { type: 'remotePublic'; name?: string; - url: string; + url: string | EnvValue; allowInsecure?: boolean; } -export interface RemotePrivateBasicPublication { +export interface RemotePrivatePublication { type: 'remotePrivate'; name?: string; url: string | EnvValue; @@ -35,17 +35,11 @@ export interface RemotePrivateBasicPublication { allowInsecure?: boolean; } -export interface RemotePrivatePublicationInternal extends RemotePrivateBasicPublication { - url: string; - username: string; - password: string; -} - export type Publication = | LocalMavenPublication | LocalDirectoryPublication | RemotePublicPublication - | RemotePrivateBasicPublication; + | RemotePrivatePublication; export interface PluginConfig { group: string; diff --git a/packages/expo-brownfield/plugin/src/android/utils/repositories.ts b/packages/expo-brownfield/plugin/src/android/utils/repositories.ts index 45de7ec27defb4..4985a9a12a600a 100644 --- a/packages/expo-brownfield/plugin/src/android/utils/repositories.ts +++ b/packages/expo-brownfield/plugin/src/android/utils/repositories.ts @@ -1,21 +1,21 @@ import path from 'node:path'; import { + EnvValue, LocalDirectoryPublication, Publication, - RemotePrivateBasicPublication, - RemotePrivatePublicationInternal, + RemotePrivatePublication, RemotePublicPublication, } from '../types'; const repositoryTemplates = { - localMaven: () => [' localDefault {', ' type = "localMaven"', ' }'], + localMaven: () => [' localDefault {', ' type.set("localMaven")', ' }'], localDirectory: (count: number, publication: LocalDirectoryPublication, projectRoot: string) => { const nameOrPlaceholder = publication.name ?? `localDirectory${count + 1}`; return [ ` ${nameOrPlaceholder} {`, - ' type = "localDirectory"', - ` url = "file://${standardizePath(publication.path, projectRoot)}"`, + ' type.set("localDirectory")', + ` url.set("file://${standardizePath(publication.path, projectRoot)}")`, ' }', ]; }, @@ -23,25 +23,21 @@ const repositoryTemplates = { const nameOrPlaceholder = publication.name ?? `remotePublic${count + 1}`; return [ ` ${nameOrPlaceholder} {`, - ' type = "remotePublic"', - ` url = "${publication.url}"`, - ` allowInsecure = ${publication.allowInsecure}`, + ' type.set("remotePublic")', + setProperty('url', publication.url), + ` allowInsecure.set(${publication.allowInsecure ?? false})`, ' }', ]; }, - remotePrivate: ( - count: number, - publication: RemotePrivatePublicationInternal, - _projectRoot: string - ) => { + remotePrivate: (count: number, publication: RemotePrivatePublication, _projectRoot: string) => { const nameOrPlaceholder = publication.name ?? `remotePrivate${count + 1}`; return [ ` ${nameOrPlaceholder} {`, - ' type = "remotePrivate"', - ` url = "${publication.url}"`, - ` username = "${publication.username}"`, - ` password = "${publication.password}"`, - ` allowInsecure = ${publication.allowInsecure}`, + ' type.set("remotePrivate")', + setProperty('url', publication.url), + setProperty('username', publication.username), + setProperty('password', publication.password), + ` allowInsecure.set(${publication.allowInsecure ?? false})`, ' }', ]; }, @@ -54,11 +50,8 @@ export const addRepository = (lines: string[], projectRoot: string, publication: case 'localDirectory': case 'remotePublic': case 'remotePrivate': - if (publication.type === 'remotePrivate') { - publication = resolveEnv(publication); - } return repositoryTemplates[publication.type]( - countOccurences(lines, `type = "${publication.type}"`), + countOccurences(lines, `type.set("${publication.type}")`), // @ts-expect-error - TypeScript can't narrow union in fall-through case publication, projectRoot @@ -78,34 +71,10 @@ const standardizePath = (url: string, projectRoot: string) => { return path.isAbsolute(url) ? url : path.join(projectRoot, url); }; -const resolveEnv = ( - publication: RemotePrivateBasicPublication -): RemotePrivatePublicationInternal => { - const publicationInternal = { - ...publication, - }; - - if (typeof publication.url === 'object') { - publicationInternal.url = findEnvOrThrow(publication.url.variable); - } - - if (typeof publication.username === 'object') { - publicationInternal.username = findEnvOrThrow(publication.username.variable); - } - - if (typeof publication.password === 'object') { - publicationInternal.password = findEnvOrThrow(publication.password.variable); - } - - return publicationInternal as RemotePrivatePublicationInternal; -}; - -const findEnvOrThrow = (envVariable: string) => { - if (process.env[envVariable]) { - return process.env[envVariable]; +const setProperty = (property: string, value: string | EnvValue) => { + if (typeof value === 'string') { + return ` ${property}.set("${value}")`; } - throw new Error( - `Environment variable: "${envVariable}" used to define publishing configuration not found` - ); + return ` ${property}.set(providers.environmentVariable("${value.variable}").orElse(""))`; }; diff --git a/packages/expo-keep-awake/android/src/main/java/expo/modules/keepawake/ExpoKeepAwakeManager.kt b/packages/expo-keep-awake/android/src/main/java/expo/modules/keepawake/ExpoKeepAwakeManager.kt index e7a75aadc87ac6..5ec417e97e00a5 100644 --- a/packages/expo-keep-awake/android/src/main/java/expo/modules/keepawake/ExpoKeepAwakeManager.kt +++ b/packages/expo-keep-awake/android/src/main/java/expo/modules/keepawake/ExpoKeepAwakeManager.kt @@ -2,38 +2,37 @@ package expo.modules.keepawake import android.app.Activity import android.view.WindowManager -import expo.modules.core.errors.CurrentActivityNotFoundException -import expo.modules.core.interfaces.services.KeepAwakeManager import expo.modules.kotlin.AppContext +import expo.modules.kotlin.exception.Exceptions -class ExpoKeepAwakeManager(private val appContext: AppContext?) : KeepAwakeManager { - private val tags: MutableSet = HashSet() +class ExpoKeepAwakeManager( + private val appContext: AppContext? +) { + private val tags = mutableSetOf() - @get:Throws(CurrentActivityNotFoundException::class) private val currentActivity: Activity - get() = appContext?.currentActivity ?: throw CurrentActivityNotFoundException() + get() = (appContext ?: throw Exceptions.AppContextLost()).throwingActivity - @Throws(CurrentActivityNotFoundException::class) - override fun activate(tag: String, done: Runnable) { + val isActivated: Boolean + get() = tags.isNotEmpty() + + fun activate(tag: String) { val activity = currentActivity if (!isActivated) { - activity.runOnUiThread { activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } + activity.runOnUiThread { + activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } } tags.add(tag) - done.run() } - @Throws(CurrentActivityNotFoundException::class) - override fun deactivate(tag: String, done: Runnable) { + fun deactivate(tag: String) { val activity = currentActivity if (tags.size == 1 && tags.contains(tag)) { - activity.runOnUiThread { activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } + activity.runOnUiThread { + activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } } tags.remove(tag) - done.run() - } - - override fun isActivated(): Boolean { - return tags.isNotEmpty() } } diff --git a/packages/expo-keep-awake/android/src/main/java/expo/modules/keepawake/KeepAwakeModule.kt b/packages/expo-keep-awake/android/src/main/java/expo/modules/keepawake/KeepAwakeModule.kt index 9ad014158f0dd0..05326202504c58 100644 --- a/packages/expo-keep-awake/android/src/main/java/expo/modules/keepawake/KeepAwakeModule.kt +++ b/packages/expo-keep-awake/android/src/main/java/expo/modules/keepawake/KeepAwakeModule.kt @@ -1,8 +1,6 @@ // Copyright 2015-present 650 Industries. All rights reserved. package expo.modules.keepawake -import expo.modules.core.errors.CurrentActivityNotFoundException -import expo.modules.kotlin.Promise import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition @@ -12,24 +10,16 @@ class KeepAwakeModule : Module() { override fun definition() = ModuleDefinition { Name("ExpoKeepAwake") - AsyncFunction("activate") { tag: String, promise: Promise -> - try { - keepAwakeManager.activate(tag) { promise.resolve() } - } catch (ex: CurrentActivityNotFoundException) { - promise.reject(ActivateKeepAwakeException()) - } + AsyncFunction("activate") { tag: String -> + keepAwakeManager.activate(tag) } - AsyncFunction("deactivate") { tag: String, promise: Promise -> - try { - keepAwakeManager.deactivate(tag) { promise.resolve() } - } catch (e: Exception) { - promise.resolve() - } + AsyncFunction("deactivate") { tag: String -> + keepAwakeManager.deactivate(tag) } AsyncFunction("isActivated") { - return@AsyncFunction keepAwakeManager.isActivated + keepAwakeManager.isActivated } } } diff --git a/packages/expo-maps/CHANGELOG.md b/packages/expo-maps/CHANGELOG.md index dfacd8072abe5e..97cd315e96d518 100644 --- a/packages/expo-maps/CHANGELOG.md +++ b/packages/expo-maps/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- [ios] refactor distance and hit detection ([#43087](https://github.com/expo/expo/pull/43087) by [@vonovak](https://github.com/vonovak)) + ## 55.0.6 — 2026-02-16 ### 🐛 Bug fixes diff --git a/packages/expo-maps/ios/AppleMapsViewiOS18.swift b/packages/expo-maps/ios/AppleMapsViewiOS18.swift index e4988adfcdfbd0..ab64d4e505c45e 100644 --- a/packages/expo-maps/ios/AppleMapsViewiOS18.swift +++ b/packages/expo-maps/ios/AppleMapsViewiOS18.swift @@ -22,7 +22,6 @@ extension MKMapPoint { x: a.x + clamped * dx, y: a.y + clamped * dy ) - return distance(to: proj) } } @@ -281,14 +280,15 @@ struct AppleMapsViewiOS18: View, AppleMapsViewProtocol { let threshold = props.properties.polylineTapThreshold return props.polylines.first { line in - let pts = line.hitTestCoordinates.map(MKMapPoint.init) + let coords = line.hitTestCoordinates + guard var prev = coords.first.map(MKMapPoint.init) else { return false } - var minDist = CLLocationDistance.greatestFiniteMagnitude - for (a, b) in zip(pts, pts.dropFirst()) { - minDist = min(minDist, tapPoint.distance(toSegmentFrom: a, to: b)) - if minDist < threshold { + for coord in coords.dropFirst() { + let curr = MKMapPoint(coord) + if tapPoint.distance(toSegmentFrom: prev, to: curr) < threshold { return true } + prev = curr } return false } @@ -320,16 +320,8 @@ struct AppleMapsViewiOS18: View, AppleMapsViewProtocol { func isTapInsideCircle( tapCoordinate: CLLocationCoordinate2D, circleCenter: CLLocationCoordinate2D, radius: Double ) -> Bool { - // Convert coordinates to CLLocation for distance calculation - let tapLocation = CLLocation( - latitude: tapCoordinate.latitude, longitude: tapCoordinate.longitude) - let circleCenterLocation = CLLocation( - latitude: circleCenter.latitude, longitude: circleCenter.longitude) - - // Calculate distance between tap and circle center (in meters) - let distance = tapLocation.distance(from: circleCenterLocation) - - // Return true if distance is less than or equal to the radius - return distance <= radius + let tapPoint = MKMapPoint(tapCoordinate) + let centerPoint = MKMapPoint(circleCenter) + return tapPoint.distance(to: centerPoint) <= radius } } diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/core/interfaces/services/KeepAwakeManager.java b/packages/expo-modules-core/android/src/main/java/expo/modules/core/interfaces/services/KeepAwakeManager.java deleted file mode 100644 index 1a7d1862168328..00000000000000 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/core/interfaces/services/KeepAwakeManager.java +++ /dev/null @@ -1,11 +0,0 @@ -package expo.modules.core.interfaces.services; - -import expo.modules.core.errors.CurrentActivityNotFoundException; - -public interface KeepAwakeManager { - void activate(String tag, Runnable done) throws CurrentActivityNotFoundException; - - void deactivate(String tag, Runnable done) throws CurrentActivityNotFoundException; - - boolean isActivated(); -} diff --git a/packages/expo-widgets/README.md b/packages/expo-widgets/README.md index 92b7c62286e8f4..12607934735b9a 100644 --- a/packages/expo-widgets/README.md +++ b/packages/expo-widgets/README.md @@ -1,6 +1,6 @@ # Expo Widgets -Provide battery information for the physical device. +Build iOS home screen widgets and Live Activities using Expo UI components. # API documentation diff --git a/tools/package.json b/tools/package.json index 9a0beaf3f452ba..3266c5e1ebe9e8 100644 --- a/tools/package.json +++ b/tools/package.json @@ -59,7 +59,7 @@ "recursive-omit-by": "^2.0.0", "semver": "^7.6.3", "strip-ansi": "^6.0.0", - "tar": "^7.5.7", + "tar": "^7.5.8", "terminal-link": "^2.1.1", "typedoc": "^0.28.15", "uuid": "^9.0.0" diff --git a/tools/yarn.lock b/tools/yarn.lock index 9e09593c650eea..07da8d309ae0a8 100644 --- a/tools/yarn.lock +++ b/tools/yarn.lock @@ -4676,10 +4676,10 @@ tar@^6.1.11: mkdirp "^1.0.3" yallist "^4.0.0" -tar@^7.5.7: - version "7.5.7" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.7.tgz#adf99774008ba1c89819f15dbd6019c630539405" - integrity sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ== +tar@^7.5.8: + version "7.5.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.8.tgz#6bbdfdb487914803d401bab6a2abb100aaa253d5" + integrity sha512-SYkBtK99u0yXa+IWL0JRzzcl7RxNpvX/U08Z+8DKnysfno7M+uExnTZH8K+VGgShf2qFPKtbNr9QBl8n7WBP6Q== dependencies: "@isaacs/fs-minipass" "^4.0.0" chownr "^3.0.0"