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.swift +16 -0
- package/README.md +60 -7
- package/build/index.d.ts +41 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +57 -33
- package/build/index.js.map +1 -1
- package/ios/ExpoLiveActivityModule.swift +96 -8
- package/ios/LiveActivityAttributes.swift +10 -0
- package/ios-files/LiveActivityHelpers.swift +58 -0
- package/ios-files/LiveActivityMediumView.swift +127 -0
- package/ios-files/LiveActivitySmallView.swift +178 -0
- package/ios-files/LiveActivityView.swift +137 -108
- package/ios-files/LiveActivityWidget.swift +204 -10
- package/ios-files/ViewHelpers.swift +41 -0
- package/package.json +8 -4
- package/plugin/build/index.js +2 -0
- package/plugin/build/lib/getWidgetFiles.js +1 -1
- package/plugin/build/types.d.ts +1 -0
- package/plugin/build/withUnsupportedOS.d.ts +4 -0
- package/plugin/build/withUnsupportedOS.js +9 -0
- package/plugin/src/index.ts +3 -0
- package/plugin/src/lib/getWidgetFiles.ts +1 -1
- package/plugin/src/types.ts +1 -0
- package/plugin/src/withUnsupportedOS.ts +10 -0
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/index.ts +88 -33
- package/.prettierignore +0 -5
package/Package.swift
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// swift-tools-version: 5.7
|
|
2
|
+
|
|
3
|
+
import PackageDescription
|
|
4
|
+
|
|
5
|
+
let package = Package(
|
|
6
|
+
name: "ExpoLiveActivity",
|
|
7
|
+
platforms: [.iOS(.v16)],
|
|
8
|
+
products: [.library(name: "ExpoLiveActivity", targets: ["ExpoLiveActivity"])],
|
|
9
|
+
targets: [
|
|
10
|
+
.target(
|
|
11
|
+
name: "ExpoLiveActivity",
|
|
12
|
+
path: "ios-files",
|
|
13
|
+
exclude: ["LiveActivityWidgetBundle.swift"]
|
|
14
|
+
),
|
|
15
|
+
]
|
|
16
|
+
)
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ The module comes with a built-in config plugin that creates a target in iOS with
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
|
-
If you want to update Live
|
|
47
|
+
If you want to update Live Activity with push notifications you can add option `"enablePushNotifications": true`:
|
|
48
48
|
```json
|
|
49
49
|
{
|
|
50
50
|
"expo": {
|
|
@@ -132,9 +132,12 @@ The `state` object should include:
|
|
|
132
132
|
{
|
|
133
133
|
title: string;
|
|
134
134
|
subtitle?: string;
|
|
135
|
-
progressBar: { // Only one property
|
|
136
|
-
date?: number; // Set as epoch time in milliseconds. This is used as an end date in a timer.
|
|
137
|
-
progress?: number; //Set amount of progress in the progress bar
|
|
135
|
+
progressBar: { // Only one property (date, progress, or elapsedTimer) is available at a time
|
|
136
|
+
date?: number; // Set as epoch time in milliseconds. This is used as an end date in a countdown timer.
|
|
137
|
+
progress?: number; // Set amount of progress in the progress bar (0-1)
|
|
138
|
+
elapsedTimer?: { // Count up timer (elapsed time from start)
|
|
139
|
+
startDate: number; // Epoch time in milliseconds when the timer started
|
|
140
|
+
};
|
|
138
141
|
};
|
|
139
142
|
imageName?: string; // Matches the name of the image in 'assets/liveActivity'
|
|
140
143
|
dynamicIslandImageName?: string; // Matches the name of the image in 'assets/liveActivity'
|
|
@@ -157,7 +160,7 @@ The `config` object should include:
|
|
|
157
160
|
padding?: Padding // number | {top?: number bottom?: number ...}
|
|
158
161
|
imagePosition?: ImagePosition; // 'left' | 'right';
|
|
159
162
|
imageAlign?: ImageAlign; // 'top' | 'center' | 'bottom'
|
|
160
|
-
imageSize?: ImageSize // { width
|
|
163
|
+
imageSize?: ImageSize // { width?: number|`${number}%`, height?: number|`${number}%` } | undefined (defaults to 64pt)
|
|
161
164
|
contentFit?: ImageContentFit; // 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
|
|
162
165
|
};
|
|
163
166
|
```
|
|
@@ -202,6 +205,26 @@ const activityId = LiveActivity.startActivity(state, config)
|
|
|
202
205
|
|
|
203
206
|
This will initiate a Live Activity with the specified title, subtitle, image from your configured assets folder and a time to which there will be a countdown in a progress view.
|
|
204
207
|
|
|
208
|
+
Using an elapsed timer:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const elapsedTimerState: LiveActivity.LiveActivityState = {
|
|
212
|
+
title: 'Walk in Progress',
|
|
213
|
+
subtitle: 'With Max the Dog',
|
|
214
|
+
progressBar: {
|
|
215
|
+
elapsedTimer: {
|
|
216
|
+
startDate: Date.now() - 5 * 60 * 1000, // Started 5 minutes ago
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
imageName: 'dog_walking',
|
|
220
|
+
dynamicIslandImageName: 'dog_icon',
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const activityId = LiveActivity.startActivity(elapsedTimerState, config)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The elapsed timer will automatically update every second based on the `startDate` you provide.
|
|
227
|
+
|
|
205
228
|
Subscribing to push token changes:
|
|
206
229
|
|
|
207
230
|
```typescript
|
|
@@ -246,7 +269,8 @@ Example payload for starting Live Activity:
|
|
|
246
269
|
"timerEndDateInMilliseconds": 1754410997000,
|
|
247
270
|
"progress": 0.5,
|
|
248
271
|
"imageName": "live_activity_image",
|
|
249
|
-
"dynamicIslandImageName": "dynamic_island_image"
|
|
272
|
+
"dynamicIslandImageName": "dynamic_island_image",
|
|
273
|
+
"elapsedTimerStartDateInMilliseconds": null
|
|
250
274
|
},
|
|
251
275
|
"timestamp": 1754491435000, // timestamp of when the push notification was sent
|
|
252
276
|
"attributes-type": "LiveActivityAttributes",
|
|
@@ -290,7 +314,36 @@ Example payload for updating Live Activity:
|
|
|
290
314
|
}
|
|
291
315
|
```
|
|
292
316
|
|
|
293
|
-
Where `timerEndDateInMilliseconds` value is a timestamp in milliseconds corresponding to the target point of the
|
|
317
|
+
Where `timerEndDateInMilliseconds` value is a timestamp in milliseconds corresponding to the target point of the countdown displayed in Live Activity view.
|
|
318
|
+
|
|
319
|
+
Example payload for starting Live Activity with elapsed timer:
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"aps": {
|
|
324
|
+
"event": "start",
|
|
325
|
+
"content-state": {
|
|
326
|
+
"title": "Walk in Progress",
|
|
327
|
+
"subtitle": "With Max",
|
|
328
|
+
"timerEndDateInMilliseconds": null,
|
|
329
|
+
"progress": null,
|
|
330
|
+
"imageName": "dog_walking",
|
|
331
|
+
"dynamicIslandImageName": "dog_icon",
|
|
332
|
+
"elapsedTimerStartDateInMilliseconds": 1754410997000
|
|
333
|
+
},
|
|
334
|
+
"timestamp": 1754491435000,
|
|
335
|
+
"attributes-type": "LiveActivityAttributes",
|
|
336
|
+
"attributes": {
|
|
337
|
+
"name": "WalkActivity",
|
|
338
|
+
"backgroundColor": "001A72",
|
|
339
|
+
"titleColor": "EBEBF0",
|
|
340
|
+
"progressViewLabelColor": "FFFFFF"
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Where `elapsedTimerStartDateInMilliseconds` is the timestamp (in milliseconds) when the elapsed timer started counting up.
|
|
294
347
|
|
|
295
348
|
## Image support
|
|
296
349
|
|
package/build/index.d.ts
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import { EventSubscription } from 'expo-modules-core';
|
|
2
2
|
type Voidable<T> = T | void;
|
|
3
3
|
export type DynamicIslandTimerType = 'circular' | 'digital';
|
|
4
|
+
export type ElapsedTimer = {
|
|
5
|
+
startDate: number;
|
|
6
|
+
};
|
|
4
7
|
type ProgressBarType = {
|
|
5
8
|
date?: number;
|
|
6
9
|
progress?: undefined;
|
|
10
|
+
elapsedTimer?: undefined;
|
|
11
|
+
currentStep?: undefined;
|
|
12
|
+
totalSteps?: undefined;
|
|
7
13
|
} | {
|
|
8
14
|
date?: undefined;
|
|
9
15
|
progress?: number;
|
|
16
|
+
elapsedTimer?: undefined;
|
|
17
|
+
currentStep?: undefined;
|
|
18
|
+
totalSteps?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
date?: undefined;
|
|
21
|
+
progress?: undefined;
|
|
22
|
+
elapsedTimer?: ElapsedTimer;
|
|
23
|
+
currentStep?: undefined;
|
|
24
|
+
totalSteps?: undefined;
|
|
25
|
+
} | {
|
|
26
|
+
date?: undefined;
|
|
27
|
+
progress?: undefined;
|
|
28
|
+
elapsedTimer?: undefined;
|
|
29
|
+
currentStep?: number;
|
|
30
|
+
totalSteps?: number;
|
|
10
31
|
};
|
|
11
32
|
export type LiveActivityState = {
|
|
12
33
|
title: string;
|
|
@@ -14,6 +35,7 @@ export type LiveActivityState = {
|
|
|
14
35
|
progressBar?: ProgressBarType;
|
|
15
36
|
imageName?: string;
|
|
16
37
|
dynamicIslandImageName?: string;
|
|
38
|
+
smallImageName?: string;
|
|
17
39
|
};
|
|
18
40
|
export type NativeLiveActivityState = {
|
|
19
41
|
title: string;
|
|
@@ -35,8 +57,8 @@ export type ImagePosition = 'left' | 'right' | 'leftStretch' | 'rightStretch';
|
|
|
35
57
|
export type ImageAlign = 'top' | 'center' | 'bottom';
|
|
36
58
|
export type ImageDimension = number | `${number}%`;
|
|
37
59
|
export type ImageSize = {
|
|
38
|
-
width
|
|
39
|
-
height
|
|
60
|
+
width?: ImageDimension;
|
|
61
|
+
height?: ImageDimension;
|
|
40
62
|
};
|
|
41
63
|
export type ImageContentFit = 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
|
|
42
64
|
export type LiveActivityConfig = {
|
|
@@ -51,7 +73,10 @@ export type LiveActivityConfig = {
|
|
|
51
73
|
imagePosition?: ImagePosition;
|
|
52
74
|
imageAlign?: ImageAlign;
|
|
53
75
|
imageSize?: ImageSize;
|
|
76
|
+
smallImageSize?: ImageSize;
|
|
54
77
|
contentFit?: ImageContentFit;
|
|
78
|
+
progressSegmentActiveColor?: string;
|
|
79
|
+
progressSegmentInactiveColor?: string;
|
|
55
80
|
};
|
|
56
81
|
export type ActivityTokenReceivedEvent = {
|
|
57
82
|
activityID: string;
|
|
@@ -59,7 +84,7 @@ export type ActivityTokenReceivedEvent = {
|
|
|
59
84
|
activityPushToken: string;
|
|
60
85
|
};
|
|
61
86
|
export type ActivityPushToStartTokenReceivedEvent = {
|
|
62
|
-
activityPushToStartToken: string;
|
|
87
|
+
activityPushToStartToken: string | null;
|
|
63
88
|
};
|
|
64
89
|
type ActivityState = 'active' | 'dismissed' | 'pending' | 'stale' | 'ended';
|
|
65
90
|
export type ActivityUpdateEvent = {
|
|
@@ -88,8 +113,19 @@ export declare function stopActivity(id: string, state: LiveActivityState): any;
|
|
|
88
113
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
89
114
|
*/
|
|
90
115
|
export declare function updateActivity(id: string, state: LiveActivityState): any;
|
|
91
|
-
|
|
116
|
+
/**
|
|
117
|
+
* @param {function} updateTokenListener The listener function that will be called when an update token is received.
|
|
118
|
+
*/
|
|
119
|
+
export declare function addActivityTokenListener(updateTokenListener: (event: ActivityTokenReceivedEvent) => void): Voidable<EventSubscription>;
|
|
120
|
+
/**
|
|
121
|
+
* Adds a listener that is called when a push-to-start token is received. Supported only on iOS > 17.2.
|
|
122
|
+
* On earlier iOS versions, the listener will return null as a token.
|
|
123
|
+
* @param {function} listener The listener function that will be called when the observer starts and then when a push-to-start token is received.
|
|
124
|
+
*/
|
|
92
125
|
export declare function addActivityPushToStartTokenListener(listener: (event: ActivityPushToStartTokenReceivedEvent) => void): Voidable<EventSubscription>;
|
|
93
|
-
|
|
126
|
+
/**
|
|
127
|
+
* @param {function} statusListener The listener function that will be called when an activity status changes.
|
|
128
|
+
*/
|
|
129
|
+
export declare function addActivityUpdatesListener(statusListener: (event: ActivityUpdateEvent) => void): Voidable<EventSubscription>;
|
|
94
130
|
export {};
|
|
95
131
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAKrD,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAE3B,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,SAAS,CAAA;AAE3D,KAAK,eAAe,GAChB;IACE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAKrD,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAE3B,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,SAAS,CAAA;AAE3D,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,eAAe,GAChB;IACE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,UAAU,CAAC,EAAE,SAAS,CAAA;CACvB,GACD;IACE,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,UAAU,CAAC,EAAE,SAAS,CAAA;CACvB,GACD;IACE,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,UAAU,CAAC,EAAE,SAAS,CAAA;CACvB,GACD;IACE,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAEL,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,OAAO,GACf;IACE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GACD,MAAM,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,GAAG,cAAc,CAAA;AAE7E,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEpD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG,CAAA;AAClD,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,CAAA;AAElF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,sBAAsB,CAAA;IAClC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,4BAA4B,CAAC,EAAE,MAAM,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,iBAAiB,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,qCAAqC,GAAG;IAClD,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAA;CACxC,CAAA;AAED,KAAK,aAAa,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;AAE3E,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,aAAa,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,eAAe,EAAE,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAA;IAC7D,0BAA0B,EAAE,CAAC,MAAM,EAAE,qCAAqC,KAAK,IAAI,CAAA;IACnF,aAAa,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAA;CACrD,CAAA;AA4ED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAErG;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,OAEhE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,OAElE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,mBAAmB,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAC/D,QAAQ,CAAC,iBAAiB,CAAC,CAG7B;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,CAAC,KAAK,EAAE,qCAAqC,KAAK,IAAI,GAC/D,QAAQ,CAAC,iBAAiB,CAAC,CAG7B;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,cAAc,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GACnD,QAAQ,CAAC,iBAAiB,CAAC,CAG7B"}
|
package/build/index.js
CHANGED
|
@@ -9,8 +9,12 @@ function assertIOS(name) {
|
|
|
9
9
|
function normalizeConfig(config) {
|
|
10
10
|
if (config === undefined)
|
|
11
11
|
return config;
|
|
12
|
-
const { padding, imageSize, ...base } = config;
|
|
13
|
-
const normalized = {
|
|
12
|
+
const { padding, imageSize, smallImageSize, progressSegmentActiveColor, progressSegmentInactiveColor, ...base } = config;
|
|
13
|
+
const normalized = {
|
|
14
|
+
...base,
|
|
15
|
+
progressSegmentActiveColor,
|
|
16
|
+
progressSegmentInactiveColor,
|
|
17
|
+
};
|
|
14
18
|
// Normalize padding: keep number in padding, object in paddingDetails
|
|
15
19
|
if (typeof padding === 'number') {
|
|
16
20
|
normalized.padding = padding;
|
|
@@ -18,34 +22,43 @@ function normalizeConfig(config) {
|
|
|
18
22
|
else if (typeof padding === 'object') {
|
|
19
23
|
normalized.paddingDetails = padding;
|
|
20
24
|
}
|
|
21
|
-
//
|
|
22
|
-
|
|
25
|
+
// Helper to parse a dimension value (number or percent string like '50%')
|
|
26
|
+
const parseDimension = (value, fieldName) => {
|
|
27
|
+
if (value === undefined)
|
|
28
|
+
return {};
|
|
29
|
+
if (typeof value === 'number')
|
|
30
|
+
return { absolute: value };
|
|
23
31
|
const regExp = /^(100(?:\.0+)?|\d{1,2}(?:\.\d+)?)%$/; // Matches 0.0% to 100.0%
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
normalized.imageHeight =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
const match = value.trim().match(regExp);
|
|
33
|
+
if (match)
|
|
34
|
+
return { percent: Number(match[1]) };
|
|
35
|
+
throw new Error(`${fieldName} percent string must be in format "0%" to "100%"`);
|
|
36
|
+
};
|
|
37
|
+
// Normalize imageSize
|
|
38
|
+
if (imageSize) {
|
|
39
|
+
const w = parseDimension(imageSize.width, 'imageSize.width');
|
|
40
|
+
const h = parseDimension(imageSize.height, 'imageSize.height');
|
|
41
|
+
if (w.absolute !== undefined)
|
|
42
|
+
normalized.imageWidth = w.absolute;
|
|
43
|
+
if (w.percent !== undefined)
|
|
44
|
+
normalized.imageWidthPercent = w.percent;
|
|
45
|
+
if (h.absolute !== undefined)
|
|
46
|
+
normalized.imageHeight = h.absolute;
|
|
47
|
+
if (h.percent !== undefined)
|
|
48
|
+
normalized.imageHeightPercent = h.percent;
|
|
49
|
+
}
|
|
50
|
+
// Normalize smallImageSize
|
|
51
|
+
if (smallImageSize) {
|
|
52
|
+
const w = parseDimension(smallImageSize.width, 'smallImageSize.width');
|
|
53
|
+
const h = parseDimension(smallImageSize.height, 'smallImageSize.height');
|
|
54
|
+
if (w.absolute !== undefined)
|
|
55
|
+
normalized.smallImageWidth = w.absolute;
|
|
56
|
+
if (w.percent !== undefined)
|
|
57
|
+
normalized.smallImageWidthPercent = w.percent;
|
|
58
|
+
if (h.absolute !== undefined)
|
|
59
|
+
normalized.smallImageHeight = h.absolute;
|
|
60
|
+
if (h.percent !== undefined)
|
|
61
|
+
normalized.smallImageHeightPercent = h.percent;
|
|
49
62
|
}
|
|
50
63
|
return normalized;
|
|
51
64
|
}
|
|
@@ -74,16 +87,27 @@ export function updateActivity(id, state) {
|
|
|
74
87
|
if (assertIOS('updateActivity'))
|
|
75
88
|
return ExpoLiveActivityModule.updateActivity(id, state);
|
|
76
89
|
}
|
|
77
|
-
|
|
90
|
+
/**
|
|
91
|
+
* @param {function} updateTokenListener The listener function that will be called when an update token is received.
|
|
92
|
+
*/
|
|
93
|
+
export function addActivityTokenListener(updateTokenListener) {
|
|
78
94
|
if (assertIOS('addActivityTokenListener'))
|
|
79
|
-
return ExpoLiveActivityModule.addListener('onTokenReceived',
|
|
95
|
+
return ExpoLiveActivityModule.addListener('onTokenReceived', updateTokenListener);
|
|
80
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Adds a listener that is called when a push-to-start token is received. Supported only on iOS > 17.2.
|
|
99
|
+
* On earlier iOS versions, the listener will return null as a token.
|
|
100
|
+
* @param {function} listener The listener function that will be called when the observer starts and then when a push-to-start token is received.
|
|
101
|
+
*/
|
|
81
102
|
export function addActivityPushToStartTokenListener(listener) {
|
|
82
103
|
if (assertIOS('addActivityPushToStartTokenListener'))
|
|
83
104
|
return ExpoLiveActivityModule.addListener('onPushToStartTokenReceived', listener);
|
|
84
105
|
}
|
|
85
|
-
|
|
106
|
+
/**
|
|
107
|
+
* @param {function} statusListener The listener function that will be called when an activity status changes.
|
|
108
|
+
*/
|
|
109
|
+
export function addActivityUpdatesListener(statusListener) {
|
|
86
110
|
if (assertIOS('addActivityUpdatesListener'))
|
|
87
|
-
return ExpoLiveActivityModule.addListener('onStateChange',
|
|
111
|
+
return ExpoLiveActivityModule.addListener('onStateChange', statusListener);
|
|
88
112
|
}
|
|
89
113
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventSubscription } from 'expo-modules-core'\nimport { Platform } from 'react-native'\n\nimport ExpoLiveActivityModule from './ExpoLiveActivityModule'\n\ntype Voidable<T> = T | void\n\nexport type DynamicIslandTimerType = 'circular' | 'digital'\n\ntype ProgressBarType =\n | {\n date?: number\n progress?: undefined\n }\n | {\n date?: undefined\n progress?: number\n }\n\nexport type LiveActivityState = {\n title: string\n subtitle?: string\n progressBar?: ProgressBarType\n imageName?: string\n dynamicIslandImageName?: string\n}\n\nexport type NativeLiveActivityState = {\n title: string\n subtitle?: string\n date?: number\n progress?: number\n imageName?: string\n dynamicIslandImageName?: string\n}\n\nexport type Padding =\n | {\n top?: number\n bottom?: number\n left?: number\n right?: number\n vertical?: number\n horizontal?: number\n }\n | number\n\nexport type ImagePosition = 'left' | 'right' | 'leftStretch' | 'rightStretch'\n\nexport type ImageAlign = 'top' | 'center' | 'bottom'\n\nexport type ImageDimension = number | `${number}%`\nexport type ImageSize = {\n width: ImageDimension\n height: ImageDimension\n}\n\nexport type ImageContentFit = 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'\n\nexport type LiveActivityConfig = {\n backgroundColor?: string\n titleColor?: string\n subtitleColor?: string\n progressViewTint?: string\n progressViewLabelColor?: string\n deepLinkUrl?: string\n timerType?: DynamicIslandTimerType\n padding?: Padding\n imagePosition?: ImagePosition\n imageAlign?: ImageAlign\n imageSize?: ImageSize\n contentFit?: ImageContentFit\n}\n\nexport type ActivityTokenReceivedEvent = {\n activityID: string\n activityName: string\n activityPushToken: string\n}\n\nexport type ActivityPushToStartTokenReceivedEvent = {\n activityPushToStartToken: string\n}\n\ntype ActivityState = 'active' | 'dismissed' | 'pending' | 'stale' | 'ended'\n\nexport type ActivityUpdateEvent = {\n activityID: string\n activityName: string\n activityState: ActivityState\n}\n\nexport type LiveActivityModuleEvents = {\n onTokenReceived: (params: ActivityTokenReceivedEvent) => void\n onPushToStartTokenReceived: (params: ActivityPushToStartTokenReceivedEvent) => void\n onStateChange: (params: ActivityUpdateEvent) => void\n}\n\nfunction assertIOS(name: string) {\n const isIOS = Platform.OS === 'ios'\n\n if (!isIOS) console.error(`${name} is only available on iOS`)\n\n return isIOS\n}\n\nfunction normalizeConfig(config?: LiveActivityConfig) {\n if (config === undefined) return config\n\n const { padding, imageSize, ...base } = config\n type NormalizedConfig = LiveActivityConfig & {\n paddingDetails?: Padding\n imageWidth?: number\n imageHeight?: number\n imageWidthPercent?: number\n imageHeightPercent?: number\n }\n const normalized: NormalizedConfig = { ...base }\n\n // Normalize padding: keep number in padding, object in paddingDetails\n if (typeof padding === 'number') {\n normalized.padding = padding\n } else if (typeof padding === 'object') {\n normalized.paddingDetails = padding\n }\n\n // Normalize imageSize: object with width/height each a number (points) or percent string like '50%'\n if (imageSize) {\n const regExp = /^(100(?:\\.0+)?|\\d{1,2}(?:\\.\\d+)?)%$/ // Matches 0.0% to 100.0%\n\n const { width, height } = imageSize\n\n if (typeof width === 'number') {\n normalized.imageWidth = width\n } else if (typeof width === 'string') {\n const match = width.trim().match(regExp)\n if (match) {\n normalized.imageWidthPercent = Number(match[1])\n } else {\n throw new Error('imageSize.width percent string must be in format \"0%\" to \"100%\"')\n }\n }\n\n if (typeof height === 'number') {\n normalized.imageHeight = height\n } else if (typeof height === 'string') {\n const match = height.trim().match(regExp)\n if (match) {\n normalized.imageHeightPercent = Number(match[1])\n } else {\n throw new Error('imageSize.height percent string must be in format \"0%\" to \"100%\"')\n }\n }\n }\n\n return normalized\n}\n\n/**\n * @param {LiveActivityState} state The state for the live activity.\n * @param {LiveActivityConfig} config Live activity config object.\n * @returns {string} The identifier of the started activity or undefined if creating live activity failed.\n */\nexport function startActivity(state: LiveActivityState, config?: LiveActivityConfig): Voidable<string> {\n if (assertIOS('startActivity')) return ExpoLiveActivityModule.startActivity(state, normalizeConfig(config))\n}\n\n/**\n * @param {string} id The identifier of the activity to stop.\n * @param {LiveActivityState} state The updated state for the live activity.\n */\nexport function stopActivity(id: string, state: LiveActivityState) {\n if (assertIOS('stopActivity')) return ExpoLiveActivityModule.stopActivity(id, state)\n}\n\n/**\n * @param {string} id The identifier of the activity to update.\n * @param {LiveActivityState} state The updated state for the live activity.\n */\nexport function updateActivity(id: string, state: LiveActivityState) {\n if (assertIOS('updateActivity')) return ExpoLiveActivityModule.updateActivity(id, state)\n}\n\nexport function addActivityTokenListener(\n listener: (event: ActivityTokenReceivedEvent) => void\n): Voidable<EventSubscription> {\n if (assertIOS('addActivityTokenListener')) return ExpoLiveActivityModule.addListener('onTokenReceived', listener)\n}\n\nexport function addActivityPushToStartTokenListener(\n listener: (event: ActivityPushToStartTokenReceivedEvent) => void\n): Voidable<EventSubscription> {\n if (assertIOS('addActivityPushToStartTokenListener'))\n return ExpoLiveActivityModule.addListener('onPushToStartTokenReceived', listener)\n}\n\nexport function addActivityUpdatesListener(\n listener: (event: ActivityUpdateEvent) => void\n): Voidable<EventSubscription> {\n if (assertIOS('addActivityUpdatesListener')) return ExpoLiveActivityModule.addListener('onStateChange', listener)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventSubscription } from 'expo-modules-core'\nimport { Platform } from 'react-native'\n\nimport ExpoLiveActivityModule from './ExpoLiveActivityModule'\n\ntype Voidable<T> = T | void\n\nexport type DynamicIslandTimerType = 'circular' | 'digital'\n\nexport type ElapsedTimer = {\n startDate: number // milliseconds timestamp (past time when timer started)\n}\n\ntype ProgressBarType =\n | {\n date?: number\n progress?: undefined\n elapsedTimer?: undefined\n currentStep?: undefined\n totalSteps?: undefined\n }\n | {\n date?: undefined\n progress?: number\n elapsedTimer?: undefined\n currentStep?: undefined\n totalSteps?: undefined\n }\n | {\n date?: undefined\n progress?: undefined\n elapsedTimer?: ElapsedTimer\n currentStep?: undefined\n totalSteps?: undefined\n }\n | {\n date?: undefined\n progress?: undefined\n elapsedTimer?: undefined\n currentStep?: number\n totalSteps?: number\n }\n\nexport type LiveActivityState = {\n title: string\n subtitle?: string\n progressBar?: ProgressBarType\n imageName?: string\n dynamicIslandImageName?: string\n smallImageName?: string\n}\n\nexport type NativeLiveActivityState = {\n title: string\n subtitle?: string\n date?: number\n progress?: number\n imageName?: string\n dynamicIslandImageName?: string\n}\n\nexport type Padding =\n | {\n top?: number\n bottom?: number\n left?: number\n right?: number\n vertical?: number\n horizontal?: number\n }\n | number\n\nexport type ImagePosition = 'left' | 'right' | 'leftStretch' | 'rightStretch'\n\nexport type ImageAlign = 'top' | 'center' | 'bottom'\n\nexport type ImageDimension = number | `${number}%`\nexport type ImageSize = {\n width?: ImageDimension\n height?: ImageDimension\n}\n\nexport type ImageContentFit = 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'\n\nexport type LiveActivityConfig = {\n backgroundColor?: string\n titleColor?: string\n subtitleColor?: string\n progressViewTint?: string\n progressViewLabelColor?: string\n deepLinkUrl?: string\n timerType?: DynamicIslandTimerType\n padding?: Padding\n imagePosition?: ImagePosition\n imageAlign?: ImageAlign\n imageSize?: ImageSize\n smallImageSize?: ImageSize\n contentFit?: ImageContentFit\n progressSegmentActiveColor?: string\n progressSegmentInactiveColor?: string\n}\n\nexport type ActivityTokenReceivedEvent = {\n activityID: string\n activityName: string\n activityPushToken: string\n}\n\nexport type ActivityPushToStartTokenReceivedEvent = {\n activityPushToStartToken: string | null\n}\n\ntype ActivityState = 'active' | 'dismissed' | 'pending' | 'stale' | 'ended'\n\nexport type ActivityUpdateEvent = {\n activityID: string\n activityName: string\n activityState: ActivityState\n}\n\nexport type LiveActivityModuleEvents = {\n onTokenReceived: (params: ActivityTokenReceivedEvent) => void\n onPushToStartTokenReceived: (params: ActivityPushToStartTokenReceivedEvent) => void\n onStateChange: (params: ActivityUpdateEvent) => void\n}\n\nfunction assertIOS(name: string) {\n const isIOS = Platform.OS === 'ios'\n\n if (!isIOS) console.error(`${name} is only available on iOS`)\n\n return isIOS\n}\n\nfunction normalizeConfig(config?: LiveActivityConfig) {\n if (config === undefined) return config\n\n const { padding, imageSize, smallImageSize, progressSegmentActiveColor, progressSegmentInactiveColor, ...base } =\n config\n type NormalizedConfig = LiveActivityConfig & {\n paddingDetails?: Padding\n imageWidth?: number\n imageHeight?: number\n imageWidthPercent?: number\n imageHeightPercent?: number\n smallImageWidth?: number\n smallImageHeight?: number\n smallImageWidthPercent?: number\n smallImageHeightPercent?: number\n }\n const normalized: NormalizedConfig = {\n ...base,\n progressSegmentActiveColor,\n progressSegmentInactiveColor,\n }\n\n // Normalize padding: keep number in padding, object in paddingDetails\n if (typeof padding === 'number') {\n normalized.padding = padding\n } else if (typeof padding === 'object') {\n normalized.paddingDetails = padding\n }\n\n // Helper to parse a dimension value (number or percent string like '50%')\n const parseDimension = (\n value: ImageDimension | undefined,\n fieldName: string\n ): { absolute?: number; percent?: number } => {\n if (value === undefined) return {}\n\n if (typeof value === 'number') return { absolute: value }\n const regExp = /^(100(?:\\.0+)?|\\d{1,2}(?:\\.\\d+)?)%$/ // Matches 0.0% to 100.0%\n const match = value.trim().match(regExp)\n if (match) return { percent: Number(match[1]) }\n throw new Error(`${fieldName} percent string must be in format \"0%\" to \"100%\"`)\n }\n\n // Normalize imageSize\n if (imageSize) {\n const w = parseDimension(imageSize.width, 'imageSize.width')\n const h = parseDimension(imageSize.height, 'imageSize.height')\n if (w.absolute !== undefined) normalized.imageWidth = w.absolute\n if (w.percent !== undefined) normalized.imageWidthPercent = w.percent\n if (h.absolute !== undefined) normalized.imageHeight = h.absolute\n if (h.percent !== undefined) normalized.imageHeightPercent = h.percent\n }\n\n // Normalize smallImageSize\n if (smallImageSize) {\n const w = parseDimension(smallImageSize.width, 'smallImageSize.width')\n const h = parseDimension(smallImageSize.height, 'smallImageSize.height')\n if (w.absolute !== undefined) normalized.smallImageWidth = w.absolute\n if (w.percent !== undefined) normalized.smallImageWidthPercent = w.percent\n if (h.absolute !== undefined) normalized.smallImageHeight = h.absolute\n if (h.percent !== undefined) normalized.smallImageHeightPercent = h.percent\n }\n\n return normalized\n}\n\n/**\n * @param {LiveActivityState} state The state for the live activity.\n * @param {LiveActivityConfig} config Live activity config object.\n * @returns {string} The identifier of the started activity or undefined if creating live activity failed.\n */\nexport function startActivity(state: LiveActivityState, config?: LiveActivityConfig): Voidable<string> {\n if (assertIOS('startActivity')) return ExpoLiveActivityModule.startActivity(state, normalizeConfig(config))\n}\n\n/**\n * @param {string} id The identifier of the activity to stop.\n * @param {LiveActivityState} state The updated state for the live activity.\n */\nexport function stopActivity(id: string, state: LiveActivityState) {\n if (assertIOS('stopActivity')) return ExpoLiveActivityModule.stopActivity(id, state)\n}\n\n/**\n * @param {string} id The identifier of the activity to update.\n * @param {LiveActivityState} state The updated state for the live activity.\n */\nexport function updateActivity(id: string, state: LiveActivityState) {\n if (assertIOS('updateActivity')) return ExpoLiveActivityModule.updateActivity(id, state)\n}\n\n/**\n * @param {function} updateTokenListener The listener function that will be called when an update token is received.\n */\nexport function addActivityTokenListener(\n updateTokenListener: (event: ActivityTokenReceivedEvent) => void\n): Voidable<EventSubscription> {\n if (assertIOS('addActivityTokenListener'))\n return ExpoLiveActivityModule.addListener('onTokenReceived', updateTokenListener)\n}\n\n/**\n * Adds a listener that is called when a push-to-start token is received. Supported only on iOS > 17.2.\n * On earlier iOS versions, the listener will return null as a token.\n * @param {function} listener The listener function that will be called when the observer starts and then when a push-to-start token is received.\n */\nexport function addActivityPushToStartTokenListener(\n listener: (event: ActivityPushToStartTokenReceivedEvent) => void\n): Voidable<EventSubscription> {\n if (assertIOS('addActivityPushToStartTokenListener'))\n return ExpoLiveActivityModule.addListener('onPushToStartTokenReceived', listener)\n}\n\n/**\n * @param {function} statusListener The listener function that will be called when an activity status changes.\n */\nexport function addActivityUpdatesListener(\n statusListener: (event: ActivityUpdateEvent) => void\n): Voidable<EventSubscription> {\n if (assertIOS('addActivityUpdatesListener'))\n return ExpoLiveActivityModule.addListener('onStateChange', statusListener)\n}\n"]}
|
|
@@ -18,6 +18,20 @@ public class ExpoLiveActivityModule: Module {
|
|
|
18
18
|
|
|
19
19
|
@Field
|
|
20
20
|
var progress: Double?
|
|
21
|
+
|
|
22
|
+
@Field
|
|
23
|
+
var elapsedTimer: ElapsedTimer?
|
|
24
|
+
|
|
25
|
+
@Field
|
|
26
|
+
var currentStep: Int?
|
|
27
|
+
|
|
28
|
+
@Field
|
|
29
|
+
var totalSteps: Int?
|
|
30
|
+
|
|
31
|
+
struct ElapsedTimer: Record {
|
|
32
|
+
@Field
|
|
33
|
+
var startDate: Double?
|
|
34
|
+
}
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
@Field
|
|
@@ -25,6 +39,9 @@ public class ExpoLiveActivityModule: Module {
|
|
|
25
39
|
|
|
26
40
|
@Field
|
|
27
41
|
var dynamicIslandImageName: String?
|
|
42
|
+
|
|
43
|
+
@Field
|
|
44
|
+
var smallImageName: String?
|
|
28
45
|
}
|
|
29
46
|
|
|
30
47
|
struct LiveActivityConfig: Record {
|
|
@@ -70,12 +87,30 @@ public class ExpoLiveActivityModule: Module {
|
|
|
70
87
|
@Field
|
|
71
88
|
var imageHeightPercent: Double?
|
|
72
89
|
|
|
90
|
+
@Field
|
|
91
|
+
var smallImageWidth: Int?
|
|
92
|
+
|
|
93
|
+
@Field
|
|
94
|
+
var smallImageHeight: Int?
|
|
95
|
+
|
|
96
|
+
@Field
|
|
97
|
+
var smallImageWidthPercent: Double?
|
|
98
|
+
|
|
99
|
+
@Field
|
|
100
|
+
var smallImageHeightPercent: Double?
|
|
101
|
+
|
|
73
102
|
@Field
|
|
74
103
|
var imageAlign: String?
|
|
75
104
|
|
|
76
105
|
@Field
|
|
77
106
|
var contentFit: String?
|
|
78
107
|
|
|
108
|
+
@Field
|
|
109
|
+
var progressSegmentActiveColor: String?
|
|
110
|
+
|
|
111
|
+
@Field
|
|
112
|
+
var progressSegmentInactiveColor: String?
|
|
113
|
+
|
|
79
114
|
struct PaddingDetails: Record {
|
|
80
115
|
@Field var top: Int?
|
|
81
116
|
@Field var bottom: Int?
|
|
@@ -136,11 +171,19 @@ public class ExpoLiveActivityModule: Module {
|
|
|
136
171
|
if let name = state.dynamicIslandImageName {
|
|
137
172
|
newState.dynamicIslandImageName = try await resolveImage(from: name)
|
|
138
173
|
}
|
|
174
|
+
|
|
175
|
+
if let name = state.smallImageName {
|
|
176
|
+
newState.smallImageName = try await resolveImage(from: name)
|
|
177
|
+
}
|
|
139
178
|
}
|
|
140
179
|
|
|
141
180
|
private func observePushToStartToken() {
|
|
142
181
|
guard #available(iOS 17.2, *), ActivityAuthorizationInfo().areActivitiesEnabled else { return }
|
|
143
182
|
|
|
183
|
+
if let initialToken = (Activity<LiveActivityAttributes>.pushToStartToken?.reduce("") { $0 + String(format: "%02x", $1) }) {
|
|
184
|
+
sendPushToStartToken(activityPushToStartToken: initialToken)
|
|
185
|
+
}
|
|
186
|
+
|
|
144
187
|
print("Observing push to start token updates...")
|
|
145
188
|
Task {
|
|
146
189
|
for await data in Activity<LiveActivityAttributes>.pushToStartTokenUpdates {
|
|
@@ -193,21 +236,34 @@ public class ExpoLiveActivityModule: Module {
|
|
|
193
236
|
?? false
|
|
194
237
|
}
|
|
195
238
|
|
|
239
|
+
private var silentOnUnsupportedOS: Bool {
|
|
240
|
+
Bundle.main.object(forInfoDictionaryKey: "ExpoLiveActivity_SilentOnUnsupportedOS") as? Bool
|
|
241
|
+
?? false
|
|
242
|
+
}
|
|
243
|
+
|
|
196
244
|
public func definition() -> ModuleDefinition {
|
|
197
245
|
Name("ExpoLiveActivity")
|
|
198
246
|
|
|
199
|
-
|
|
247
|
+
OnStartObserving("onTokenReceived") {
|
|
248
|
+
observeLiveActivityUpdates()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
OnStartObserving("onPushToStartTokenReceived") {
|
|
200
252
|
if pushNotificationsEnabled {
|
|
201
253
|
observePushToStartToken()
|
|
202
254
|
}
|
|
203
|
-
observeLiveActivityUpdates()
|
|
204
255
|
}
|
|
205
256
|
|
|
206
257
|
Events("onTokenReceived", "onPushToStartTokenReceived", "onStateChange")
|
|
207
258
|
|
|
208
259
|
Function("startActivity") {
|
|
209
260
|
(state: LiveActivityState, maybeConfig: LiveActivityConfig?) -> String in
|
|
210
|
-
guard #available(iOS 16.2, *) else {
|
|
261
|
+
guard #available(iOS 16.2, *) else {
|
|
262
|
+
if silentOnUnsupportedOS {
|
|
263
|
+
return ""
|
|
264
|
+
}
|
|
265
|
+
throw UnsupportedOSException("16.2")
|
|
266
|
+
}
|
|
211
267
|
|
|
212
268
|
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
|
|
213
269
|
throw LiveActivitiesNotEnabledException()
|
|
@@ -241,15 +297,27 @@ public class ExpoLiveActivityModule: Module {
|
|
|
241
297
|
imageHeight: config.imageHeight,
|
|
242
298
|
imageWidthPercent: config.imageWidthPercent,
|
|
243
299
|
imageHeightPercent: config.imageHeightPercent,
|
|
300
|
+
smallImageWidth: config.smallImageWidth,
|
|
301
|
+
smallImageHeight: config.smallImageHeight,
|
|
302
|
+
smallImageWidthPercent: config.smallImageWidthPercent,
|
|
303
|
+
smallImageHeightPercent: config.smallImageHeightPercent,
|
|
244
304
|
imageAlign: config.imageAlign,
|
|
245
|
-
contentFit: config.contentFit
|
|
305
|
+
contentFit: config.contentFit,
|
|
306
|
+
progressSegmentActiveColor: config.progressSegmentActiveColor,
|
|
307
|
+
progressSegmentInactiveColor: config.progressSegmentInactiveColor
|
|
246
308
|
)
|
|
247
309
|
|
|
248
310
|
let initialState = LiveActivityAttributes.ContentState(
|
|
249
311
|
title: state.title,
|
|
250
312
|
subtitle: state.subtitle,
|
|
251
313
|
timerEndDateInMilliseconds: state.progressBar?.date,
|
|
252
|
-
progress: state.progressBar?.progress
|
|
314
|
+
progress: state.progressBar?.progress,
|
|
315
|
+
imageName: state.imageName,
|
|
316
|
+
dynamicIslandImageName: state.dynamicIslandImageName,
|
|
317
|
+
smallImageName: state.smallImageName,
|
|
318
|
+
elapsedTimerStartDateInMilliseconds: state.progressBar?.elapsedTimer?.startDate,
|
|
319
|
+
currentStep: state.progressBar?.currentStep,
|
|
320
|
+
totalSteps: state.progressBar?.totalSteps
|
|
253
321
|
)
|
|
254
322
|
|
|
255
323
|
let activity = try Activity.request(
|
|
@@ -271,7 +339,12 @@ public class ExpoLiveActivityModule: Module {
|
|
|
271
339
|
}
|
|
272
340
|
|
|
273
341
|
Function("stopActivity") { (activityId: String, state: LiveActivityState) in
|
|
274
|
-
guard #available(iOS 16.2, *) else {
|
|
342
|
+
guard #available(iOS 16.2, *) else {
|
|
343
|
+
if silentOnUnsupportedOS {
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
throw UnsupportedOSException("16.2")
|
|
347
|
+
}
|
|
275
348
|
|
|
276
349
|
guard
|
|
277
350
|
let activity = Activity<LiveActivityAttributes>.activities.first(where: {
|
|
@@ -285,7 +358,13 @@ public class ExpoLiveActivityModule: Module {
|
|
|
285
358
|
title: state.title,
|
|
286
359
|
subtitle: state.subtitle,
|
|
287
360
|
timerEndDateInMilliseconds: state.progressBar?.date,
|
|
288
|
-
progress: state.progressBar?.progress
|
|
361
|
+
progress: state.progressBar?.progress,
|
|
362
|
+
imageName: state.imageName,
|
|
363
|
+
dynamicIslandImageName: state.dynamicIslandImageName,
|
|
364
|
+
smallImageName: state.smallImageName,
|
|
365
|
+
elapsedTimerStartDateInMilliseconds: state.progressBar?.elapsedTimer?.startDate,
|
|
366
|
+
currentStep: state.progressBar?.currentStep,
|
|
367
|
+
totalSteps: state.progressBar?.totalSteps
|
|
289
368
|
)
|
|
290
369
|
try await updateImages(state: state, newState: &newState)
|
|
291
370
|
await activity.end(
|
|
@@ -297,6 +376,9 @@ public class ExpoLiveActivityModule: Module {
|
|
|
297
376
|
|
|
298
377
|
Function("updateActivity") { (activityId: String, state: LiveActivityState) in
|
|
299
378
|
guard #available(iOS 16.2, *) else {
|
|
379
|
+
if silentOnUnsupportedOS {
|
|
380
|
+
return
|
|
381
|
+
}
|
|
300
382
|
throw UnsupportedOSException("16.2")
|
|
301
383
|
}
|
|
302
384
|
|
|
@@ -312,7 +394,13 @@ public class ExpoLiveActivityModule: Module {
|
|
|
312
394
|
title: state.title,
|
|
313
395
|
subtitle: state.subtitle,
|
|
314
396
|
timerEndDateInMilliseconds: state.progressBar?.date,
|
|
315
|
-
progress: state.progressBar?.progress
|
|
397
|
+
progress: state.progressBar?.progress,
|
|
398
|
+
imageName: state.imageName,
|
|
399
|
+
dynamicIslandImageName: state.dynamicIslandImageName,
|
|
400
|
+
smallImageName: state.smallImageName,
|
|
401
|
+
elapsedTimerStartDateInMilliseconds: state.progressBar?.elapsedTimer?.startDate,
|
|
402
|
+
currentStep: state.progressBar?.currentStep,
|
|
403
|
+
totalSteps: state.progressBar?.totalSteps
|
|
316
404
|
)
|
|
317
405
|
try await updateImages(state: state, newState: &newState)
|
|
318
406
|
await activity.update(ActivityContent(state: newState, staleDate: nil))
|
|
@@ -9,6 +9,10 @@ struct LiveActivityAttributes: ActivityAttributes {
|
|
|
9
9
|
var progress: Double?
|
|
10
10
|
var imageName: String?
|
|
11
11
|
var dynamicIslandImageName: String?
|
|
12
|
+
var smallImageName: String?
|
|
13
|
+
var elapsedTimerStartDateInMilliseconds: Double?
|
|
14
|
+
var currentStep: Int?
|
|
15
|
+
var totalSteps: Int?
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
var name: String
|
|
@@ -26,8 +30,14 @@ struct LiveActivityAttributes: ActivityAttributes {
|
|
|
26
30
|
var imageHeight: Int?
|
|
27
31
|
var imageWidthPercent: Double?
|
|
28
32
|
var imageHeightPercent: Double?
|
|
33
|
+
var smallImageWidth: Int?
|
|
34
|
+
var smallImageHeight: Int?
|
|
35
|
+
var smallImageWidthPercent: Double?
|
|
36
|
+
var smallImageHeightPercent: Double?
|
|
29
37
|
var imageAlign: String?
|
|
30
38
|
var contentFit: String?
|
|
39
|
+
var progressSegmentActiveColor: String?
|
|
40
|
+
var progressSegmentInactiveColor: String?
|
|
31
41
|
|
|
32
42
|
enum DynamicIslandTimerType: String, Codable {
|
|
33
43
|
case circular
|