expo-live-activity 0.2.0-alpha3 → 0.2.0-alpha5
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/README.md +66 -37
- package/build/index.d.ts +6 -8
- package/build/index.d.ts.map +1 -1
- package/build/index.js +28 -13
- package/build/index.js.map +1 -1
- package/ios/ExpoLiveActivityModule.swift +27 -24
- package/ios/LiveActivityAttributes.swift +3 -2
- package/ios-files/LiveActivityView.swift +1 -1
- package/ios-files/LiveActivityWidget.swift +18 -13
- package/ios-files/View+applyIfPresent.swift +19 -0
- package/ios-files/View+applyWidgetURL.swift +15 -0
- package/package.json +1 -1
- package/plugin/build/lib/getWidgetFiles.js +4 -1
- package/plugin/src/lib/getWidgetFiles.ts +3 -1
- package/src/index.ts +28 -15
package/README.md
CHANGED
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
- Listen and handle changes in push notification tokens associated with a live activity.
|
|
13
13
|
|
|
14
14
|
## Platform compatibility
|
|
15
|
-
**Note:** This module is intended for use on **iOS devices only**. When methods are invoked on platforms other than iOS, they will
|
|
15
|
+
**Note:** This module is intended for use on **iOS devices only**. The minimal iOS version that supports Live Activities is 16.2. When methods are invoked on platforms other than iOS or on older iOS versions, they will log an error, ensuring that they are used in the correct context.
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
|
+
> [!NOTE]
|
|
19
|
+
> The library isn't supported in Expo Go, to set it up correctly you need to use [Expo DevClient](https://docs.expo.dev/versions/latest/sdk/dev-client/) .
|
|
18
20
|
To begin using `expo-live-activity`, follow the installation and configuration steps outlined below:
|
|
19
21
|
|
|
20
22
|
### Step 1: Installation
|
|
@@ -33,49 +35,41 @@ The module comes with a built-in config plugin that creates a target in iOS with
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
```
|
|
38
|
+
If you want to update Live Acitivity with push notifications you can add option `"enablePushNotifications": true`:
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"expo": {
|
|
42
|
+
"plugins": [
|
|
43
|
+
[
|
|
44
|
+
"expo-live-activity",
|
|
45
|
+
{
|
|
46
|
+
"enablePushNotifications": true
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
36
53
|
2. **Assets configuration:**
|
|
37
54
|
Place images intended for live activities in the `assets/liveActivity` folder. The plugin manages these assets automatically.
|
|
38
55
|
|
|
56
|
+
Then prebuild your app with:
|
|
57
|
+
```sh
|
|
58
|
+
npx expo prebuild --clean
|
|
59
|
+
```
|
|
60
|
+
|
|
39
61
|
### Step 3: Usage in Your React Native App
|
|
40
62
|
Import the functionalities provided by the `expo-live-activity` module in your JavaScript or TypeScript files:
|
|
41
63
|
```javascript
|
|
42
64
|
import * as LiveActivity from "expo-live-activity";
|
|
43
65
|
```
|
|
44
66
|
|
|
45
|
-
## Push notifications
|
|
46
|
-
By default, updating live activity is possible only via API. There is also a way to update live activity using push notifications. To enable that feature, add `"enablePushNotifications": true`. Then, the notification payload should be looking like this:
|
|
47
|
-
|
|
48
|
-
```json
|
|
49
|
-
{
|
|
50
|
-
"aps":{
|
|
51
|
-
"event":"update",
|
|
52
|
-
"content-state":{
|
|
53
|
-
"title":"Hello",
|
|
54
|
-
"subtitle":"World",
|
|
55
|
-
"date":1754064245000
|
|
56
|
-
},
|
|
57
|
-
"timestamp":1754063621319
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Where `date` value is a timestamp in milliseconds corresponding to the target point of the counter displayed in live activity view.
|
|
63
|
-
|
|
64
|
-
## Image support
|
|
65
|
-
Live activity view also supports image display. There are two dedicated fields for that:
|
|
66
|
-
- `imageName`
|
|
67
|
-
- `dynamicIslandImageName`
|
|
68
|
-
Currently, it's possible to set them only via API, but we plan on to add that feature to push notifications as well. The value of each field can be:
|
|
69
|
-
- a string which maps to an asset name
|
|
70
|
-
- a URL to remote image
|
|
71
|
-
The latter requires adding "App Groups" capability to both "main app" and "live activity" targets.
|
|
72
|
-
|
|
73
67
|
## API
|
|
74
68
|
`expo-live-activity` module exports three primary functions to manage live activities:
|
|
75
69
|
|
|
76
70
|
### Managing Live Activities
|
|
77
|
-
- **`startActivity(state: LiveActivityState,
|
|
78
|
-
Start a new live activity. Takes a `state` configuration object for initial activity state and an optional `
|
|
71
|
+
- **`startActivity(state: LiveActivityState, config?: LiveActivityConfig): string | undefined`**:
|
|
72
|
+
Start a new live activity. Takes a `state` configuration object for initial activity state and an optional `config` object to customize appearance or behavior. It returns the `ID` of the created live activity, which should be stored for future reference. If the live activity can't be created (eg. on android or iOS lower than 16.2), it will return `undefined`.
|
|
79
73
|
|
|
80
74
|
- **`updateActivity(id: string, state: LiveActivityState)`**:
|
|
81
75
|
Update an existing live activity. The `state` object should contain updated information. The `activityId` indicates which activity should be updated.
|
|
@@ -84,23 +78,27 @@ The latter requires adding "App Groups" capability to both "main app" and "live
|
|
|
84
78
|
Terminate an ongoing live activity. The `state` object should contain the final state of the activity. The `activityId` indicates which activity should be stopped.
|
|
85
79
|
|
|
86
80
|
### Handling Push Notification Tokens
|
|
87
|
-
- **`addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription
|
|
81
|
+
- **`addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription | undefined`**:
|
|
88
82
|
Subscribe to changes in the push notification token associated with live activities.
|
|
89
83
|
|
|
84
|
+
|
|
85
|
+
### Deep linking
|
|
86
|
+
When starting a new live activity, it's possible to pass `deepLinkUrl` field in `config` object. This can be any string that you can handle in your main app target.
|
|
87
|
+
|
|
90
88
|
### State Object Structure
|
|
91
89
|
The `state` object should include:
|
|
92
90
|
```javascript
|
|
93
91
|
{
|
|
94
92
|
title: string;
|
|
95
93
|
subtitle?: string;
|
|
96
|
-
date?: number; // Set as epoch time in milliseconds
|
|
94
|
+
date?: number; // Set as epoch time in milliseconds. This is used as an end date in a timer.
|
|
97
95
|
imageName?: string; // Matches the name of the image in 'assets/live-activity'
|
|
98
96
|
dynamicIslandImageName?: string; // Matches the name of the image in 'assets/live-activity'
|
|
99
97
|
};
|
|
100
98
|
```
|
|
101
99
|
|
|
102
|
-
###
|
|
103
|
-
The `
|
|
100
|
+
### Config Object Structure
|
|
101
|
+
The `config` object should include:
|
|
104
102
|
```typescript
|
|
105
103
|
{
|
|
106
104
|
backgroundColor?: string;
|
|
@@ -108,6 +106,7 @@ The `styles` object should include:
|
|
|
108
106
|
subtitleColor?: string;
|
|
109
107
|
progressViewTint?: string;
|
|
110
108
|
progressViewLabelColor?: string;
|
|
109
|
+
deepLinkUrl?: string;
|
|
111
110
|
timerType?: DynamicIslandTimerType; // "circular" | "digital" - defines timer appereance on the dynamic island
|
|
112
111
|
};
|
|
113
112
|
```
|
|
@@ -123,16 +122,17 @@ const state = {
|
|
|
123
122
|
dynamicIslandImageName: "dynamic_island_image"
|
|
124
123
|
};
|
|
125
124
|
|
|
126
|
-
const
|
|
125
|
+
const config = {
|
|
127
126
|
backgroundColor: "#FFFFFF",
|
|
128
127
|
titleColor: "#000000",
|
|
129
128
|
subtitleColor: "#333333",
|
|
130
129
|
progressViewTint: "#4CAF50",
|
|
131
130
|
progressViewLabelColor: "#FFFFFF",
|
|
131
|
+
deepLinkUrl: "/dashboard",
|
|
132
132
|
timerType: "circular"
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
-
const activityId = LiveActivity.startActivity(state,
|
|
135
|
+
const activityId = LiveActivity.startActivity(state, config);
|
|
136
136
|
// Store activityId for future reference
|
|
137
137
|
```
|
|
138
138
|
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.
|
|
@@ -150,3 +150,32 @@ useEffect(() => {
|
|
|
150
150
|
return () => subscription.remove();
|
|
151
151
|
}, []);
|
|
152
152
|
```
|
|
153
|
+
## Push notifications
|
|
154
|
+
By default, updating live activity is possible only via API. If you want to have possibility to update live activity using push notifications, you can enable that feature by adding `"enablePushNotifications": true` in the plugin config in your `app.json` or `app.config.ts` file. Then, the notification payload should look like this:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"aps":{
|
|
159
|
+
"event":"update",
|
|
160
|
+
"content-state":{
|
|
161
|
+
"title":"Hello",
|
|
162
|
+
"subtitle":"World",
|
|
163
|
+
"timerEndDateInMilliseconds":1754064245000,
|
|
164
|
+
"imageName": "live_activity_image",
|
|
165
|
+
"dynamicIslandImageName": "dynamic_island_image"
|
|
166
|
+
},
|
|
167
|
+
"timestamp":1754063621319 // timestamp of when the push notification was sent
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Where `timerEndDateInMilliseconds` value is a timestamp in milliseconds corresponding to the target point of the counter displayed in live activity view.
|
|
173
|
+
|
|
174
|
+
## Image support
|
|
175
|
+
Live activity view also supports image display. There are two dedicated fields in the `state` object for that:
|
|
176
|
+
- `imageName`
|
|
177
|
+
- `dynamicIslandImageName`
|
|
178
|
+
|
|
179
|
+
The value of each field can be:
|
|
180
|
+
- a string which maps to an asset name
|
|
181
|
+
- a URL to remote image - currently, it's possible to use this option only via API, but we plan on to add that feature to push notifications as well. It also requires adding "App Groups" capability to both "main app" and "live activity" targets.
|
package/build/index.d.ts
CHANGED
|
@@ -7,12 +7,13 @@ export type LiveActivityState = {
|
|
|
7
7
|
imageName?: string;
|
|
8
8
|
dynamicIslandImageName?: string;
|
|
9
9
|
};
|
|
10
|
-
export type
|
|
10
|
+
export type LiveActivityConfig = {
|
|
11
11
|
backgroundColor?: string;
|
|
12
12
|
titleColor?: string;
|
|
13
13
|
subtitleColor?: string;
|
|
14
14
|
progressViewTint?: string;
|
|
15
15
|
progressViewLabelColor?: string;
|
|
16
|
+
deepLinkUrl?: string;
|
|
16
17
|
timerType?: DynamicIslandTimerType;
|
|
17
18
|
};
|
|
18
19
|
export type ActivityTokenReceivedEvent = {
|
|
@@ -24,22 +25,19 @@ export type LiveActivityModuleEvents = {
|
|
|
24
25
|
};
|
|
25
26
|
/**
|
|
26
27
|
* @param {LiveActivityState} state The state for the live activity.
|
|
27
|
-
* @param {
|
|
28
|
-
* @returns {string} The identifier of the started activity.
|
|
29
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
28
|
+
* @param {LiveActivityConfig} config Live activity config object.
|
|
29
|
+
* @returns {string} The identifier of the started activity or undefined if creating live activity failed.
|
|
30
30
|
*/
|
|
31
|
-
export declare function startActivity(state: LiveActivityState,
|
|
31
|
+
export declare function startActivity(state: LiveActivityState, config?: LiveActivityConfig): string | undefined;
|
|
32
32
|
/**
|
|
33
33
|
* @param {string} id The identifier of the activity to stop.
|
|
34
34
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
35
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
36
35
|
*/
|
|
37
36
|
export declare function stopActivity(id: string, state: LiveActivityState): any;
|
|
38
37
|
/**
|
|
39
38
|
* @param {string} id The identifier of the activity to update.
|
|
40
39
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
41
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
42
40
|
*/
|
|
43
41
|
export declare function updateActivity(id: string, state: LiveActivityState): any;
|
|
44
|
-
export declare function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription;
|
|
42
|
+
export declare function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription | undefined;
|
|
45
43
|
//# 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":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,SAAS,CAAA;AAE3D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,eAAe,EAAE,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAC;CAC/D,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,SAAS,CAAA;AAE3D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,eAAe,EAAE,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAC;CAC/D,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAWvG;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,OAShE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,OASlE;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAAG,iBAAiB,GAAG,SAAS,CAM7H"}
|
package/build/index.js
CHANGED
|
@@ -2,41 +2,56 @@ import ExpoLiveActivityModule from "./ExpoLiveActivityModule";
|
|
|
2
2
|
import { Platform } from "react-native";
|
|
3
3
|
/**
|
|
4
4
|
* @param {LiveActivityState} state The state for the live activity.
|
|
5
|
-
* @param {
|
|
6
|
-
* @returns {string} The identifier of the started activity.
|
|
7
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
5
|
+
* @param {LiveActivityConfig} config Live activity config object.
|
|
6
|
+
* @returns {string} The identifier of the started activity or undefined if creating live activity failed.
|
|
8
7
|
*/
|
|
9
|
-
export function startActivity(state,
|
|
8
|
+
export function startActivity(state, config) {
|
|
10
9
|
if (Platform.OS !== "ios") {
|
|
11
|
-
|
|
10
|
+
console.error("startActivity is only available on iOS");
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
return ExpoLiveActivityModule.startActivity(state, config);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.error(`startActivity failed with an error: ${error}`);
|
|
18
|
+
return undefined;
|
|
12
19
|
}
|
|
13
|
-
return ExpoLiveActivityModule.startActivity(state, styles);
|
|
14
20
|
}
|
|
15
21
|
/**
|
|
16
22
|
* @param {string} id The identifier of the activity to stop.
|
|
17
23
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
18
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
19
24
|
*/
|
|
20
25
|
export function stopActivity(id, state) {
|
|
21
26
|
if (Platform.OS !== "ios") {
|
|
22
|
-
|
|
27
|
+
console.error("stopActivity is only available on iOS");
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
return ExpoLiveActivityModule.stopActivity(id, state);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error(`stopActivity failed with an error: ${error}`);
|
|
23
34
|
}
|
|
24
|
-
return ExpoLiveActivityModule.stopActivity(id, state);
|
|
25
35
|
}
|
|
26
36
|
/**
|
|
27
37
|
* @param {string} id The identifier of the activity to update.
|
|
28
38
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
29
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
30
39
|
*/
|
|
31
40
|
export function updateActivity(id, state) {
|
|
32
41
|
if (Platform.OS !== "ios") {
|
|
33
|
-
|
|
42
|
+
console.error("updateActivity is only available on iOS");
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return ExpoLiveActivityModule.updateActivity(id, state);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error(`updateActivity failed with an error: ${error}`);
|
|
34
49
|
}
|
|
35
|
-
return ExpoLiveActivityModule.updateActivity(id, state);
|
|
36
50
|
}
|
|
37
51
|
export function addActivityTokenListener(listener) {
|
|
38
52
|
if (Platform.OS !== "ios") {
|
|
39
|
-
|
|
53
|
+
console.error("addActivityTokenListener is only available on iOS");
|
|
54
|
+
return undefined;
|
|
40
55
|
}
|
|
41
56
|
return ExpoLiveActivityModule.addListener('onTokenReceived', listener);
|
|
42
57
|
}
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAgCxC;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAwB,EAAE,MAA2B;IACjF,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACxD,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,IAAI,CAAC;QACH,OAAO,sBAAsB,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,uCAAuC,KAAK,EAAE,CAAC,CAAC;QAC9D,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,EAAU,EAAE,KAAwB;IAC/D,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,CAAC;QACH,OAAO,sBAAsB,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,sCAAsC,KAAK,EAAE,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,EAAU,EAAE,KAAwB;IACjE,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC;QACH,OAAO,sBAAsB,CAAC,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,wCAAwC,KAAK,EAAE,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,QAAqD;IAC5F,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACnE,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,OAAO,sBAAsB,CAAC,WAAW,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;AACzE,CAAC","sourcesContent":["import ExpoLiveActivityModule from \"./ExpoLiveActivityModule\";\nimport { Platform } from \"react-native\";\nimport { EventSubscription } from 'expo-modules-core';\n\nexport type DynamicIslandTimerType = 'circular' | 'digital'\n\nexport type LiveActivityState = {\n title: string;\n subtitle?: string;\n date?: number;\n imageName?: string;\n dynamicIslandImageName?: string;\n};\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};\n\nexport type ActivityTokenReceivedEvent = {\n activityID: string;\n activityPushToken: string;\n};\n\nexport type LiveActivityModuleEvents = {\n onTokenReceived: (params: ActivityTokenReceivedEvent) => void;\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): string | undefined {\n if (Platform.OS !== \"ios\") {\n console.error(\"startActivity is only available on iOS\");\n return undefined\n }\n try {\n return ExpoLiveActivityModule.startActivity(state, config);\n } catch (error) {\n console.error(`startActivity failed with an error: ${error}`);\n return undefined\n }\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 (Platform.OS !== \"ios\") {\n console.error(\"stopActivity is only available on iOS\");\n }\n try {\n return ExpoLiveActivityModule.stopActivity(id, state);\n } catch (error) {\n console.error(`stopActivity failed with an error: ${error}`);\n }\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 (Platform.OS !== \"ios\") {\n console.error(\"updateActivity is only available on iOS\");\n }\n try {\n return ExpoLiveActivityModule.updateActivity(id, state);\n } catch (error) {\n console.error(`updateActivity failed with an error: ${error}`);\n }\n}\n\nexport function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription | undefined {\n if (Platform.OS !== \"ios\") {\n console.error(\"addActivityTokenListener is only available on iOS\");\n return undefined\n }\n return ExpoLiveActivityModule.addListener('onTokenReceived', listener);\n}\n"]}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import ActivityKit
|
|
2
2
|
import ExpoModulesCore
|
|
3
3
|
|
|
4
|
-
enum
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
enum LiveActivityErrors: Error {
|
|
5
|
+
case unsupportedOS
|
|
6
|
+
case liveActivitiesNotEnabled
|
|
7
|
+
case unexpetedError(Error)
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
public class ExpoLiveActivityModule: Module {
|
|
@@ -24,7 +25,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
24
25
|
var dynamicIslandImageName: String?
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
struct
|
|
28
|
+
struct LiveActivityConfig: Record {
|
|
28
29
|
@Field
|
|
29
30
|
var backgroundColor: String?
|
|
30
31
|
|
|
@@ -39,6 +40,9 @@ public class ExpoLiveActivityModule: Module {
|
|
|
39
40
|
|
|
40
41
|
@Field
|
|
41
42
|
var progressViewLabelColor: String?
|
|
43
|
+
|
|
44
|
+
@Field
|
|
45
|
+
var deepLinkUrl: String?
|
|
42
46
|
|
|
43
47
|
@Field
|
|
44
48
|
var timerType: DynamicIslandTimerType?
|
|
@@ -59,10 +63,6 @@ public class ExpoLiveActivityModule: Module {
|
|
|
59
63
|
)
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
func toContentStateDate(date: Double?) -> Date? {
|
|
63
|
-
return date.map { Date(timeIntervalSince1970: $0 / 1000) }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
66
|
func updateImages(state: LiveActivityState, newState: inout LiveActivityAttributes.ContentState) async throws {
|
|
67
67
|
if let name = state.imageName {
|
|
68
68
|
print("imageName: \(name)")
|
|
@@ -80,31 +80,33 @@ public class ExpoLiveActivityModule: Module {
|
|
|
80
80
|
|
|
81
81
|
Events("onTokenReceived")
|
|
82
82
|
|
|
83
|
-
Function("startActivity") { (state: LiveActivityState,
|
|
83
|
+
Function("startActivity") { (state: LiveActivityState, maybeConfig: LiveActivityConfig?) -> String in
|
|
84
84
|
print("Starting activity")
|
|
85
85
|
if #available(iOS 16.2, *) {
|
|
86
86
|
if ActivityAuthorizationInfo().areActivitiesEnabled {
|
|
87
87
|
do {
|
|
88
|
+
let config = maybeConfig ?? LiveActivityConfig()
|
|
88
89
|
let attributes = LiveActivityAttributes(
|
|
89
90
|
name: "ExpoLiveActivity",
|
|
90
|
-
backgroundColor:
|
|
91
|
-
titleColor:
|
|
92
|
-
subtitleColor:
|
|
93
|
-
progressViewTint:
|
|
94
|
-
progressViewLabelColor:
|
|
95
|
-
|
|
91
|
+
backgroundColor: config.backgroundColor,
|
|
92
|
+
titleColor: config.titleColor,
|
|
93
|
+
subtitleColor: config.subtitleColor,
|
|
94
|
+
progressViewTint: config.progressViewTint,
|
|
95
|
+
progressViewLabelColor: config.progressViewLabelColor,
|
|
96
|
+
deepLinkUrl: config.deepLinkUrl,
|
|
97
|
+
timerType: config.timerType == .digital ? .digital : .circular
|
|
96
98
|
)
|
|
97
99
|
let initialState = LiveActivityAttributes.ContentState(
|
|
98
100
|
title: state.title,
|
|
99
101
|
subtitle: state.subtitle,
|
|
100
|
-
|
|
102
|
+
timerEndDateInMilliseconds: state.date,
|
|
101
103
|
)
|
|
102
104
|
let pushNotificationsEnabled =
|
|
103
|
-
Bundle.main.object(forInfoDictionaryKey: "ExpoLiveActivity_EnablePushNotifications")
|
|
105
|
+
Bundle.main.object(forInfoDictionaryKey: "ExpoLiveActivity_EnablePushNotifications") as? Bool
|
|
104
106
|
let activity = try Activity.request(
|
|
105
107
|
attributes: attributes,
|
|
106
108
|
content: .init(state: initialState, staleDate: nil),
|
|
107
|
-
pushType: pushNotificationsEnabled ==
|
|
109
|
+
pushType: pushNotificationsEnabled == true ? .token : nil
|
|
108
110
|
)
|
|
109
111
|
|
|
110
112
|
Task {
|
|
@@ -124,11 +126,12 @@ public class ExpoLiveActivityModule: Module {
|
|
|
124
126
|
return activity.id
|
|
125
127
|
} catch (let error) {
|
|
126
128
|
print("Error with live activity: \(error)")
|
|
129
|
+
throw LiveActivityErrors.unexpetedError(error)
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
|
-
throw
|
|
132
|
+
throw LiveActivityErrors.liveActivitiesNotEnabled
|
|
130
133
|
} else {
|
|
131
|
-
throw
|
|
134
|
+
throw LiveActivityErrors.unsupportedOS
|
|
132
135
|
}
|
|
133
136
|
}
|
|
134
137
|
|
|
@@ -138,7 +141,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
138
141
|
var newState = LiveActivityAttributes.ContentState(
|
|
139
142
|
title: state.title,
|
|
140
143
|
subtitle: state.subtitle,
|
|
141
|
-
|
|
144
|
+
timerEndDateInMilliseconds: state.date,
|
|
142
145
|
)
|
|
143
146
|
if let activity = Activity<LiveActivityAttributes>.activities.first(where: {
|
|
144
147
|
$0.id == activityId
|
|
@@ -155,7 +158,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
155
158
|
print("Didn't find activity with ID \(activityId)")
|
|
156
159
|
}
|
|
157
160
|
} else {
|
|
158
|
-
throw
|
|
161
|
+
throw LiveActivityErrors.unsupportedOS
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
|
|
@@ -165,7 +168,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
165
168
|
var newState = LiveActivityAttributes.ContentState(
|
|
166
169
|
title: state.title,
|
|
167
170
|
subtitle: state.subtitle,
|
|
168
|
-
|
|
171
|
+
timerEndDateInMilliseconds: state.date,
|
|
169
172
|
)
|
|
170
173
|
if let activity = Activity<LiveActivityAttributes>.activities.first(where: {
|
|
171
174
|
$0.id == activityId
|
|
@@ -179,7 +182,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
179
182
|
print("Didn't find activity with ID \(activityId)")
|
|
180
183
|
}
|
|
181
184
|
} else {
|
|
182
|
-
throw
|
|
185
|
+
throw LiveActivityErrors.unsupportedOS
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
188
|
}
|
|
@@ -12,7 +12,7 @@ struct LiveActivityAttributes: ActivityAttributes {
|
|
|
12
12
|
public struct ContentState: Codable, Hashable {
|
|
13
13
|
var title: String
|
|
14
14
|
var subtitle: String?
|
|
15
|
-
var
|
|
15
|
+
var timerEndDateInMilliseconds: Double?
|
|
16
16
|
var imageName: String?
|
|
17
17
|
var dynamicIslandImageName: String?
|
|
18
18
|
}
|
|
@@ -23,7 +23,8 @@ struct LiveActivityAttributes: ActivityAttributes {
|
|
|
23
23
|
var subtitleColor: String?
|
|
24
24
|
var progressViewTint: String?
|
|
25
25
|
var progressViewLabelColor: String?
|
|
26
|
-
var
|
|
26
|
+
var deepLinkUrl: String?
|
|
27
|
+
var timerType: DynamicIslandTimerType?
|
|
27
28
|
|
|
28
29
|
enum DynamicIslandTimerType: String, Codable {
|
|
29
30
|
case circular
|
|
@@ -54,7 +54,7 @@ import WidgetKit
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
if let date = contentState.
|
|
57
|
+
if let date = contentState.timerEndDateInMilliseconds {
|
|
58
58
|
ProgressView(timerInterval: Date.toTimerInterval(miliseconds: date))
|
|
59
59
|
.tint(progressViewTint)
|
|
60
60
|
.modifier(ConditionalForegroundViewModifier(color: attributes.progressViewLabelColor))
|
|
@@ -13,7 +13,7 @@ struct LiveActivityAttributes: ActivityAttributes {
|
|
|
13
13
|
public struct ContentState: Codable, Hashable {
|
|
14
14
|
var title: String
|
|
15
15
|
var subtitle: String?
|
|
16
|
-
var
|
|
16
|
+
var timerEndDateInMilliseconds: Double?
|
|
17
17
|
var imageName: String?
|
|
18
18
|
var dynamicIslandImageName: String?
|
|
19
19
|
}
|
|
@@ -24,7 +24,8 @@ struct LiveActivityAttributes: ActivityAttributes {
|
|
|
24
24
|
var subtitleColor: String?
|
|
25
25
|
var progressViewTint: String?
|
|
26
26
|
var progressViewLabelColor: String?
|
|
27
|
-
var
|
|
27
|
+
var deepLinkUrl: String?
|
|
28
|
+
var timerType: DynamicIslandTimerType?
|
|
28
29
|
|
|
29
30
|
enum DynamicIslandTimerType: String, Codable {
|
|
30
31
|
case circular
|
|
@@ -37,49 +38,53 @@ struct LiveActivityWidget: Widget {
|
|
|
37
38
|
ActivityConfiguration(for: LiveActivityAttributes.self) { context in
|
|
38
39
|
LiveActivityView(contentState: context.state, attributes: context.attributes)
|
|
39
40
|
.activityBackgroundTint(
|
|
40
|
-
context.attributes.backgroundColor
|
|
41
|
+
context.attributes.backgroundColor.map { Color(hex: $0) }
|
|
41
42
|
)
|
|
42
43
|
.activitySystemActionForegroundColor(Color.black)
|
|
43
|
-
|
|
44
|
+
.applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
44
45
|
} dynamicIsland: { context in
|
|
45
46
|
DynamicIsland {
|
|
46
47
|
DynamicIslandExpandedRegion(.leading, priority: 1) {
|
|
47
48
|
dynamicIslandExpandedLeading(title: context.state.title, subtitle: context.state.subtitle)
|
|
48
49
|
.dynamicIsland(verticalPlacement: .belowIfTooWide)
|
|
49
50
|
.padding(.leading, 5)
|
|
51
|
+
.applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
50
52
|
}
|
|
51
53
|
DynamicIslandExpandedRegion(.trailing) {
|
|
52
54
|
if let imageName = context.state.imageName {
|
|
53
55
|
dynamicIslandExpandedTrailing(imageName: imageName)
|
|
54
56
|
.padding(.trailing, 5)
|
|
57
|
+
.applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
DynamicIslandExpandedRegion(.bottom) {
|
|
58
|
-
if let date = context.state.
|
|
61
|
+
if let date = context.state.timerEndDateInMilliseconds {
|
|
59
62
|
dynamicIslandExpandedBottom(endDate: date, progressViewTint: context.attributes.progressViewTint)
|
|
60
63
|
.padding(.horizontal, 5)
|
|
64
|
+
.applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
} compactLeading: {
|
|
64
68
|
if let dynamicIslandImageName = context.state.dynamicIslandImageName {
|
|
65
69
|
resizableImage(imageName: dynamicIslandImageName)
|
|
66
70
|
.frame(maxWidth: 23, maxHeight: 23)
|
|
71
|
+
.applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
67
72
|
}
|
|
68
73
|
} compactTrailing: {
|
|
69
|
-
if let date = context.state.
|
|
74
|
+
if let date = context.state.timerEndDateInMilliseconds {
|
|
70
75
|
compactTimer(
|
|
71
76
|
endDate: date,
|
|
72
|
-
timerType: context.attributes.timerType,
|
|
77
|
+
timerType: context.attributes.timerType ?? .circular,
|
|
73
78
|
progressViewTint: context.attributes.progressViewTint
|
|
74
|
-
)
|
|
79
|
+
).applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
75
80
|
}
|
|
76
81
|
} minimal: {
|
|
77
|
-
if let date = context.state.
|
|
82
|
+
if let date = context.state.timerEndDateInMilliseconds {
|
|
78
83
|
compactTimer(
|
|
79
84
|
endDate: date,
|
|
80
|
-
timerType: context.attributes.timerType,
|
|
85
|
+
timerType: context.attributes.timerType ?? .circular,
|
|
81
86
|
progressViewTint: context.attributes.progressViewTint
|
|
82
|
-
)
|
|
87
|
+
).applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
}
|
|
@@ -100,7 +105,7 @@ struct LiveActivityWidget: Widget {
|
|
|
100
105
|
.multilineTextAlignment(.trailing)
|
|
101
106
|
} else {
|
|
102
107
|
circularTimer(endDate: endDate)
|
|
103
|
-
.tint(progressViewTint
|
|
108
|
+
.tint(progressViewTint.map { Color(hex: $0) })
|
|
104
109
|
}
|
|
105
110
|
}
|
|
106
111
|
|
|
@@ -133,7 +138,7 @@ struct LiveActivityWidget: Widget {
|
|
|
133
138
|
private func dynamicIslandExpandedBottom(endDate: Double, progressViewTint: String?) -> some View {
|
|
134
139
|
ProgressView(timerInterval: Date.toTimerInterval(miliseconds: endDate))
|
|
135
140
|
.foregroundStyle(.white)
|
|
136
|
-
.tint(progressViewTint
|
|
141
|
+
.tint(progressViewTint.map { Color(hex: $0) })
|
|
137
142
|
.padding(.top, 5)
|
|
138
143
|
}
|
|
139
144
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//
|
|
2
|
+
// View+applyIfPresent.swift
|
|
3
|
+
//
|
|
4
|
+
//
|
|
5
|
+
// Created by Artur Bilski on 05/08/2025.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import SwiftUI
|
|
9
|
+
|
|
10
|
+
extension View {
|
|
11
|
+
@ViewBuilder
|
|
12
|
+
func applyIfPresent<T>(_ value: T?, transform: (Self, T) -> some View) -> some View {
|
|
13
|
+
if let value {
|
|
14
|
+
transform(self, value)
|
|
15
|
+
} else {
|
|
16
|
+
self
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//
|
|
2
|
+
// View+applyWidgetURL.swift
|
|
3
|
+
//
|
|
4
|
+
//
|
|
5
|
+
// Created by Artur Bilski on 05/08/2025.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import SwiftUI
|
|
9
|
+
|
|
10
|
+
extension View {
|
|
11
|
+
@ViewBuilder
|
|
12
|
+
func applyWidgetURL(from urlString: String?) -> some View {
|
|
13
|
+
applyIfPresent(urlString) { view, string in view.widgetURL(URL(string: string))}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -97,7 +97,7 @@ function getWidgetFiles(targetPath) {
|
|
|
97
97
|
const imagesXcassetsSource = path.join(liveActivityFilesPath, "Assets.xcassets");
|
|
98
98
|
copyFolderRecursiveSync(imagesXcassetsSource, targetPath);
|
|
99
99
|
// Move images to assets directory
|
|
100
|
-
if (fs.lstatSync(imageAssetsPath).isDirectory()) {
|
|
100
|
+
if (fs.existsSync(imageAssetsPath) && fs.lstatSync(imageAssetsPath).isDirectory()) {
|
|
101
101
|
const imagesXcassetsTarget = path.join(targetPath, "Assets.xcassets");
|
|
102
102
|
const files = fs.readdirSync(imageAssetsPath);
|
|
103
103
|
files.forEach((file) => {
|
|
@@ -128,6 +128,9 @@ function getWidgetFiles(targetPath) {
|
|
|
128
128
|
}
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
|
+
else {
|
|
132
|
+
console.warn(`Warning: Skipping adding images to live activity because directory does not exist at path: ${imageAssetsPath}`);
|
|
133
|
+
}
|
|
131
134
|
return widgetFiles;
|
|
132
135
|
}
|
|
133
136
|
function copyFileSync(source, target) {
|
|
@@ -75,7 +75,7 @@ export function getWidgetFiles(
|
|
|
75
75
|
copyFolderRecursiveSync(imagesXcassetsSource, targetPath);
|
|
76
76
|
|
|
77
77
|
// Move images to assets directory
|
|
78
|
-
if (fs.lstatSync(imageAssetsPath).isDirectory()) {
|
|
78
|
+
if (fs.existsSync(imageAssetsPath) && fs.lstatSync(imageAssetsPath).isDirectory()) {
|
|
79
79
|
const imagesXcassetsTarget = path.join(targetPath, "Assets.xcassets");
|
|
80
80
|
|
|
81
81
|
const files = fs.readdirSync(imageAssetsPath);
|
|
@@ -114,6 +114,8 @@ export function getWidgetFiles(
|
|
|
114
114
|
);
|
|
115
115
|
}
|
|
116
116
|
})
|
|
117
|
+
} else {
|
|
118
|
+
console.warn(`Warning: Skipping adding images to live activity because directory does not exist at path: ${imageAssetsPath}`);
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
return widgetFiles;
|
package/src/index.ts
CHANGED
|
@@ -12,12 +12,13 @@ export type LiveActivityState = {
|
|
|
12
12
|
dynamicIslandImageName?: string;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export type
|
|
15
|
+
export type LiveActivityConfig = {
|
|
16
16
|
backgroundColor?: string;
|
|
17
17
|
titleColor?: string;
|
|
18
18
|
subtitleColor?: string;
|
|
19
19
|
progressViewTint?: string;
|
|
20
20
|
progressViewLabelColor?: string;
|
|
21
|
+
deepLinkUrl?: string;
|
|
21
22
|
timerType?: DynamicIslandTimerType;
|
|
22
23
|
};
|
|
23
24
|
|
|
@@ -32,44 +33,56 @@ export type LiveActivityModuleEvents = {
|
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* @param {LiveActivityState} state The state for the live activity.
|
|
35
|
-
* @param {
|
|
36
|
-
* @returns {string} The identifier of the started activity.
|
|
37
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
36
|
+
* @param {LiveActivityConfig} config Live activity config object.
|
|
37
|
+
* @returns {string} The identifier of the started activity or undefined if creating live activity failed.
|
|
38
38
|
*/
|
|
39
|
-
export function startActivity(state: LiveActivityState,
|
|
39
|
+
export function startActivity(state: LiveActivityState, config?: LiveActivityConfig): string | undefined {
|
|
40
40
|
if (Platform.OS !== "ios") {
|
|
41
|
-
|
|
41
|
+
console.error("startActivity is only available on iOS");
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return ExpoLiveActivityModule.startActivity(state, config);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(`startActivity failed with an error: ${error}`);
|
|
48
|
+
return undefined
|
|
42
49
|
}
|
|
43
|
-
return ExpoLiveActivityModule.startActivity(state, styles);
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/**
|
|
47
53
|
* @param {string} id The identifier of the activity to stop.
|
|
48
54
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
49
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
50
55
|
*/
|
|
51
56
|
export function stopActivity(id: string, state: LiveActivityState) {
|
|
52
57
|
if (Platform.OS !== "ios") {
|
|
53
|
-
|
|
58
|
+
console.error("stopActivity is only available on iOS");
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
return ExpoLiveActivityModule.stopActivity(id, state);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`stopActivity failed with an error: ${error}`);
|
|
54
64
|
}
|
|
55
|
-
return ExpoLiveActivityModule.stopActivity(id, state);
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
/**
|
|
59
68
|
* @param {string} id The identifier of the activity to update.
|
|
60
69
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
61
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
62
70
|
*/
|
|
63
71
|
export function updateActivity(id: string, state: LiveActivityState) {
|
|
64
72
|
if (Platform.OS !== "ios") {
|
|
65
|
-
|
|
73
|
+
console.error("updateActivity is only available on iOS");
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return ExpoLiveActivityModule.updateActivity(id, state);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(`updateActivity failed with an error: ${error}`);
|
|
66
79
|
}
|
|
67
|
-
return ExpoLiveActivityModule.updateActivity(id, state);
|
|
68
80
|
}
|
|
69
81
|
|
|
70
|
-
export function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription {
|
|
82
|
+
export function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription | undefined {
|
|
71
83
|
if (Platform.OS !== "ios") {
|
|
72
|
-
|
|
84
|
+
console.error("addActivityTokenListener is only available on iOS");
|
|
85
|
+
return undefined
|
|
73
86
|
}
|
|
74
87
|
return ExpoLiveActivityModule.addListener('onTokenReceived', listener);
|
|
75
88
|
}
|