expo-live-activity 0.4.2 → 0.5.0-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-live-activity",
3
- "version": "0.4.2",
3
+ "version": "0.5.0-alpha1",
4
4
  "description": "A module for adding Live Activity to a React Native app for iOS.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -8,7 +8,7 @@
8
8
  "build": "expo-module build",
9
9
  "clean": "expo-module clean",
10
10
  "clean:plugin": "rm -rf plugin/build plugin/tsconfig.tsbuildinfo",
11
- "format:check": "prettier --check .",
11
+ "lint:fix": "expo-module eslint --fix && swiftformat --exclude \"**/node_modules/\" .",
12
12
  "lint": "expo-module eslint",
13
13
  "lint:libOnly": "expo-module eslint --ignore-pattern 'example/*'",
14
14
  "test": "expo-module test",
@@ -16,7 +16,10 @@
16
16
  "prepublishOnly": "expo-module prepublishOnly",
17
17
  "expo-module": "expo-module",
18
18
  "open:ios": "xed example/ios",
19
- "typecheck": "tsc"
19
+ "typecheck": "tsc",
20
+ "generateTests": "node ./example/tests/scripts/generateFlows.js",
21
+ "runAllTests": "./example/tests/scripts/runAllTests.sh",
22
+ "generateTestReport": "node ./example/tests/scripts/generateReport.js"
20
23
  },
21
24
  "keywords": [
22
25
  "react-native",
@@ -47,7 +50,8 @@
47
50
  "expo": "~54.0.0",
48
51
  "expo-module-scripts": "^5.0.3",
49
52
  "prettier": "^3.6.2",
50
- "react-native": "0.81.4"
53
+ "react-native": "0.81.4",
54
+ "pdf-lib": "^1.17.1"
51
55
  },
52
56
  "peerDependencies": {
53
57
  "expo": "*",
@@ -7,6 +7,7 @@ const config_plugins_1 = require("expo/config-plugins");
7
7
  const withConfig_1 = require("./withConfig");
8
8
  const withPlist_1 = __importDefault(require("./withPlist"));
9
9
  const withPushNotifications_1 = require("./withPushNotifications");
10
+ const withUnsupportedOS_1 = require("./withUnsupportedOS");
10
11
  const withWidgetExtensionEntitlements_1 = require("./withWidgetExtensionEntitlements");
11
12
  const withXcode_1 = require("./withXcode");
12
13
  const withWidgetsAndLiveActivities = (config, props) => {
@@ -37,6 +38,7 @@ const withWidgetsAndLiveActivities = (config, props) => {
37
38
  if (props?.enablePushNotifications) {
38
39
  config = (0, withPushNotifications_1.withPushNotifications)(config);
39
40
  }
41
+ config = (0, withUnsupportedOS_1.withUnsupportedOS)(config, { silentOnUnsupportedOS: props?.silentOnUnsupportedOS ?? false });
40
42
  return config;
41
43
  };
42
44
  exports.default = withWidgetsAndLiveActivities;
@@ -102,7 +102,7 @@ function getWidgetFiles(targetPath) {
102
102
  const imagesXcassetsTarget = path.join(targetPath, 'Assets.xcassets');
103
103
  const files = fs.readdirSync(imageAssetsPath);
104
104
  files.forEach((file) => {
105
- if (path.extname(file).match(/\.(png|jpg|jpeg)$/)) {
105
+ if (path.extname(file).match(/\.(png|jpg|jpeg|svg)$/)) {
106
106
  const source = path.join(imageAssetsPath, file);
107
107
  const imageSetDir = path.join(imagesXcassetsTarget, `${path.basename(file, path.extname(file))}.imageset`);
108
108
  // Create the .imageset directory if it doesn't exist
@@ -1,6 +1,7 @@
1
1
  import { ConfigPlugin } from '@expo/config-plugins';
2
2
  interface ConfigPluginProps {
3
3
  enablePushNotifications?: boolean;
4
+ silentOnUnsupportedOS?: boolean;
4
5
  }
5
6
  export type LiveActivityConfigPlugin = ConfigPlugin<ConfigPluginProps | undefined>;
6
7
  export {};
@@ -0,0 +1,4 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ export declare const withUnsupportedOS: ConfigPlugin<{
3
+ silentOnUnsupportedOS: boolean;
4
+ }>;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withUnsupportedOS = void 0;
4
+ const config_plugins_1 = require("@expo/config-plugins");
5
+ const withUnsupportedOS = (config, { silentOnUnsupportedOS }) => (0, config_plugins_1.withInfoPlist)(config, (mod) => {
6
+ mod.modResults['ExpoLiveActivity_SilentOnUnsupportedOS'] = silentOnUnsupportedOS;
7
+ return mod;
8
+ });
9
+ exports.withUnsupportedOS = withUnsupportedOS;
@@ -4,6 +4,7 @@ import type { LiveActivityConfigPlugin } from './types'
4
4
  import { withConfig } from './withConfig'
5
5
  import withPlist from './withPlist'
6
6
  import { withPushNotifications } from './withPushNotifications'
7
+ import { withUnsupportedOS } from './withUnsupportedOS'
7
8
  import { withWidgetExtensionEntitlements } from './withWidgetExtensionEntitlements'
8
9
  import { withXcode } from './withXcode'
9
10
 
@@ -39,6 +40,8 @@ const withWidgetsAndLiveActivities: LiveActivityConfigPlugin = (config, props) =
39
40
  config = withPushNotifications(config)
40
41
  }
41
42
 
43
+ config = withUnsupportedOS(config, { silentOnUnsupportedOS: props?.silentOnUnsupportedOS ?? false })
44
+
42
45
  return config
43
46
  }
44
47
 
@@ -78,7 +78,7 @@ export function getWidgetFiles(targetPath: string) {
78
78
  const files = fs.readdirSync(imageAssetsPath)
79
79
 
80
80
  files.forEach((file) => {
81
- if (path.extname(file).match(/\.(png|jpg|jpeg)$/)) {
81
+ if (path.extname(file).match(/\.(png|jpg|jpeg|svg)$/)) {
82
82
  const source = path.join(imageAssetsPath, file)
83
83
  const imageSetDir = path.join(imagesXcassetsTarget, `${path.basename(file, path.extname(file))}.imageset`)
84
84
 
@@ -2,6 +2,7 @@ import { ConfigPlugin } from '@expo/config-plugins'
2
2
 
3
3
  interface ConfigPluginProps {
4
4
  enablePushNotifications?: boolean
5
+ silentOnUnsupportedOS?: boolean
5
6
  }
6
7
 
7
8
  export type LiveActivityConfigPlugin = ConfigPlugin<ConfigPluginProps | undefined>
@@ -0,0 +1,10 @@
1
+ import { ConfigPlugin, withInfoPlist } from '@expo/config-plugins'
2
+
3
+ export const withUnsupportedOS: ConfigPlugin<{ silentOnUnsupportedOS: boolean }> = (
4
+ config,
5
+ { silentOnUnsupportedOS }
6
+ ) =>
7
+ withInfoPlist(config, (mod) => {
8
+ mod.modResults['ExpoLiveActivity_SilentOnUnsupportedOS'] = silentOnUnsupportedOS
9
+ return mod
10
+ })
@@ -1 +1 @@
1
- {"root":["./src/index.ts","./src/types.ts","./src/withConfig.ts","./src/withPlist.ts","./src/withPushNotifications.ts","./src/withWidgetExtensionEntitlements.ts","./src/withXcode.ts","./src/lib/getWidgetExtensionEntitlements.ts","./src/lib/getWidgetFiles.ts","./src/xcode/addBuildPhases.ts","./src/xcode/addPbxGroup.ts","./src/xcode/addProductFile.ts","./src/xcode/addTargetDependency.ts","./src/xcode/addToPbxNativeTargetSection.ts","./src/xcode/addToPbxProjectSection.ts","./src/xcode/addXCConfigurationList.ts"],"version":"5.8.3"}
1
+ {"root":["./src/index.ts","./src/types.ts","./src/withConfig.ts","./src/withPlist.ts","./src/withPushNotifications.ts","./src/withUnsupportedOS.ts","./src/withWidgetExtensionEntitlements.ts","./src/withXcode.ts","./src/lib/getWidgetExtensionEntitlements.ts","./src/lib/getWidgetFiles.ts","./src/xcode/addBuildPhases.ts","./src/xcode/addPbxGroup.ts","./src/xcode/addProductFile.ts","./src/xcode/addTargetDependency.ts","./src/xcode/addToPbxNativeTargetSection.ts","./src/xcode/addToPbxProjectSection.ts","./src/xcode/addXCConfigurationList.ts"],"version":"5.8.3"}
package/src/index.ts CHANGED
@@ -7,14 +7,38 @@ type Voidable<T> = T | void
7
7
 
8
8
  export type DynamicIslandTimerType = 'circular' | 'digital'
9
9
 
10
+ export type ElapsedTimer = {
11
+ startDate: number // milliseconds timestamp (past time when timer started)
12
+ }
13
+
10
14
  type ProgressBarType =
11
15
  | {
12
16
  date?: number
13
17
  progress?: undefined
18
+ elapsedTimer?: undefined
19
+ currentStep?: undefined
20
+ totalSteps?: undefined
14
21
  }
15
22
  | {
16
23
  date?: undefined
17
24
  progress?: number
25
+ elapsedTimer?: undefined
26
+ currentStep?: undefined
27
+ totalSteps?: undefined
28
+ }
29
+ | {
30
+ date?: undefined
31
+ progress?: undefined
32
+ elapsedTimer?: ElapsedTimer
33
+ currentStep?: undefined
34
+ totalSteps?: undefined
35
+ }
36
+ | {
37
+ date?: undefined
38
+ progress?: undefined
39
+ elapsedTimer?: undefined
40
+ currentStep?: number
41
+ totalSteps?: number
18
42
  }
19
43
 
20
44
  export type LiveActivityState = {
@@ -23,6 +47,7 @@ export type LiveActivityState = {
23
47
  progressBar?: ProgressBarType
24
48
  imageName?: string
25
49
  dynamicIslandImageName?: string
50
+ smallImageName?: string
26
51
  }
27
52
 
28
53
  export type NativeLiveActivityState = {
@@ -51,8 +76,8 @@ export type ImageAlign = 'top' | 'center' | 'bottom'
51
76
 
52
77
  export type ImageDimension = number | `${number}%`
53
78
  export type ImageSize = {
54
- width: ImageDimension
55
- height: ImageDimension
79
+ width?: ImageDimension
80
+ height?: ImageDimension
56
81
  }
57
82
 
58
83
  export type ImageContentFit = 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
@@ -69,7 +94,10 @@ export type LiveActivityConfig = {
69
94
  imagePosition?: ImagePosition
70
95
  imageAlign?: ImageAlign
71
96
  imageSize?: ImageSize
97
+ smallImageSize?: ImageSize
72
98
  contentFit?: ImageContentFit
99
+ progressSegmentActiveColor?: string
100
+ progressSegmentInactiveColor?: string
73
101
  }
74
102
 
75
103
  export type ActivityTokenReceivedEvent = {
@@ -79,7 +107,7 @@ export type ActivityTokenReceivedEvent = {
79
107
  }
80
108
 
81
109
  export type ActivityPushToStartTokenReceivedEvent = {
82
- activityPushToStartToken: string
110
+ activityPushToStartToken: string | null
83
111
  }
84
112
 
85
113
  type ActivityState = 'active' | 'dismissed' | 'pending' | 'stale' | 'ended'
@@ -107,15 +135,24 @@ function assertIOS(name: string) {
107
135
  function normalizeConfig(config?: LiveActivityConfig) {
108
136
  if (config === undefined) return config
109
137
 
110
- const { padding, imageSize, ...base } = config
138
+ const { padding, imageSize, smallImageSize, progressSegmentActiveColor, progressSegmentInactiveColor, ...base } =
139
+ config
111
140
  type NormalizedConfig = LiveActivityConfig & {
112
141
  paddingDetails?: Padding
113
142
  imageWidth?: number
114
143
  imageHeight?: number
115
144
  imageWidthPercent?: number
116
145
  imageHeightPercent?: number
146
+ smallImageWidth?: number
147
+ smallImageHeight?: number
148
+ smallImageWidthPercent?: number
149
+ smallImageHeightPercent?: number
150
+ }
151
+ const normalized: NormalizedConfig = {
152
+ ...base,
153
+ progressSegmentActiveColor,
154
+ progressSegmentInactiveColor,
117
155
  }
118
- const normalized: NormalizedConfig = { ...base }
119
156
 
120
157
  // Normalize padding: keep number in padding, object in paddingDetails
121
158
  if (typeof padding === 'number') {
@@ -124,33 +161,38 @@ function normalizeConfig(config?: LiveActivityConfig) {
124
161
  normalized.paddingDetails = padding
125
162
  }
126
163
 
127
- // Normalize imageSize: object with width/height each a number (points) or percent string like '50%'
128
- if (imageSize) {
164
+ // Helper to parse a dimension value (number or percent string like '50%')
165
+ const parseDimension = (
166
+ value: ImageDimension | undefined,
167
+ fieldName: string
168
+ ): { absolute?: number; percent?: number } => {
169
+ if (value === undefined) return {}
170
+
171
+ if (typeof value === 'number') return { absolute: value }
129
172
  const regExp = /^(100(?:\.0+)?|\d{1,2}(?:\.\d+)?)%$/ // Matches 0.0% to 100.0%
173
+ const match = value.trim().match(regExp)
174
+ if (match) return { percent: Number(match[1]) }
175
+ throw new Error(`${fieldName} percent string must be in format "0%" to "100%"`)
176
+ }
130
177
 
131
- const { width, height } = imageSize
132
-
133
- if (typeof width === 'number') {
134
- normalized.imageWidth = width
135
- } else if (typeof width === 'string') {
136
- const match = width.trim().match(regExp)
137
- if (match) {
138
- normalized.imageWidthPercent = Number(match[1])
139
- } else {
140
- throw new Error('imageSize.width percent string must be in format "0%" to "100%"')
141
- }
142
- }
178
+ // Normalize imageSize
179
+ if (imageSize) {
180
+ const w = parseDimension(imageSize.width, 'imageSize.width')
181
+ const h = parseDimension(imageSize.height, 'imageSize.height')
182
+ if (w.absolute !== undefined) normalized.imageWidth = w.absolute
183
+ if (w.percent !== undefined) normalized.imageWidthPercent = w.percent
184
+ if (h.absolute !== undefined) normalized.imageHeight = h.absolute
185
+ if (h.percent !== undefined) normalized.imageHeightPercent = h.percent
186
+ }
143
187
 
144
- if (typeof height === 'number') {
145
- normalized.imageHeight = height
146
- } else if (typeof height === 'string') {
147
- const match = height.trim().match(regExp)
148
- if (match) {
149
- normalized.imageHeightPercent = Number(match[1])
150
- } else {
151
- throw new Error('imageSize.height percent string must be in format "0%" to "100%"')
152
- }
153
- }
188
+ // Normalize smallImageSize
189
+ if (smallImageSize) {
190
+ const w = parseDimension(smallImageSize.width, 'smallImageSize.width')
191
+ const h = parseDimension(smallImageSize.height, 'smallImageSize.height')
192
+ if (w.absolute !== undefined) normalized.smallImageWidth = w.absolute
193
+ if (w.percent !== undefined) normalized.smallImageWidthPercent = w.percent
194
+ if (h.absolute !== undefined) normalized.smallImageHeight = h.absolute
195
+ if (h.percent !== undefined) normalized.smallImageHeightPercent = h.percent
154
196
  }
155
197
 
156
198
  return normalized
@@ -181,12 +223,21 @@ export function updateActivity(id: string, state: LiveActivityState) {
181
223
  if (assertIOS('updateActivity')) return ExpoLiveActivityModule.updateActivity(id, state)
182
224
  }
183
225
 
226
+ /**
227
+ * @param {function} updateTokenListener The listener function that will be called when an update token is received.
228
+ */
184
229
  export function addActivityTokenListener(
185
- listener: (event: ActivityTokenReceivedEvent) => void
230
+ updateTokenListener: (event: ActivityTokenReceivedEvent) => void
186
231
  ): Voidable<EventSubscription> {
187
- if (assertIOS('addActivityTokenListener')) return ExpoLiveActivityModule.addListener('onTokenReceived', listener)
232
+ if (assertIOS('addActivityTokenListener'))
233
+ return ExpoLiveActivityModule.addListener('onTokenReceived', updateTokenListener)
188
234
  }
189
235
 
236
+ /**
237
+ * Adds a listener that is called when a push-to-start token is received. Supported only on iOS > 17.2.
238
+ * On earlier iOS versions, the listener will return null as a token.
239
+ * @param {function} listener The listener function that will be called when the observer starts and then when a push-to-start token is received.
240
+ */
190
241
  export function addActivityPushToStartTokenListener(
191
242
  listener: (event: ActivityPushToStartTokenReceivedEvent) => void
192
243
  ): Voidable<EventSubscription> {
@@ -194,8 +245,12 @@ export function addActivityPushToStartTokenListener(
194
245
  return ExpoLiveActivityModule.addListener('onPushToStartTokenReceived', listener)
195
246
  }
196
247
 
248
+ /**
249
+ * @param {function} statusListener The listener function that will be called when an activity status changes.
250
+ */
197
251
  export function addActivityUpdatesListener(
198
- listener: (event: ActivityUpdateEvent) => void
252
+ statusListener: (event: ActivityUpdateEvent) => void
199
253
  ): Voidable<EventSubscription> {
200
- if (assertIOS('addActivityUpdatesListener')) return ExpoLiveActivityModule.addListener('onStateChange', listener)
254
+ if (assertIOS('addActivityUpdatesListener'))
255
+ return ExpoLiveActivityModule.addListener('onStateChange', statusListener)
201
256
  }
package/.prettierignore DELETED
@@ -1,5 +0,0 @@
1
- /build
2
- /example/.expo
3
- /example/ios
4
- /plugin/build
5
- /.eslintrc.js