expo-live-activity 0.2.0-alpha4 → 0.2.0
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 +64 -36
- package/build/index.d.ts +3 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +26 -11
- package/build/index.js.map +1 -1
- package/ios/ExpoLiveActivityModule.swift +12 -14
- package/ios/LiveActivityAttributes.swift +2 -2
- package/ios-files/LiveActivityView.swift +1 -1
- package/ios-files/LiveActivityWidget.swift +7 -7
- 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 +25 -13
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, config?: LiveActivityConfig)`**:
|
|
78
|
-
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.
|
|
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,9 +78,10 @@ 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
|
+
|
|
90
85
|
### Deep linking
|
|
91
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.
|
|
92
87
|
|
|
@@ -96,7 +91,7 @@ The `state` object should include:
|
|
|
96
91
|
{
|
|
97
92
|
title: string;
|
|
98
93
|
subtitle?: string;
|
|
99
|
-
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.
|
|
100
95
|
imageName?: string; // Matches the name of the image in 'assets/live-activity'
|
|
101
96
|
dynamicIslandImageName?: string; // Matches the name of the image in 'assets/live-activity'
|
|
102
97
|
};
|
|
@@ -119,7 +114,7 @@ The `config` object should include:
|
|
|
119
114
|
## Example Usage
|
|
120
115
|
Managing a live activity:
|
|
121
116
|
```javascript
|
|
122
|
-
const state = {
|
|
117
|
+
const state: LiveActivity.LiveActivityState = {
|
|
123
118
|
title: "Title",
|
|
124
119
|
subtitle: "This is a subtitle",
|
|
125
120
|
date: new Date(Date.now() + 60 * 1000 * 5).getTime(),
|
|
@@ -127,7 +122,7 @@ const state = {
|
|
|
127
122
|
dynamicIslandImageName: "dynamic_island_image"
|
|
128
123
|
};
|
|
129
124
|
|
|
130
|
-
const config = {
|
|
125
|
+
const config: LiveActivity.LiveActivityConfig = {
|
|
131
126
|
backgroundColor: "#FFFFFF",
|
|
132
127
|
titleColor: "#000000",
|
|
133
128
|
subtitleColor: "#333333",
|
|
@@ -152,6 +147,39 @@ useEffect(() => {
|
|
|
152
147
|
// Send token to a remote server to update live activity with push notifications
|
|
153
148
|
});
|
|
154
149
|
|
|
155
|
-
return () => subscription
|
|
150
|
+
return () => subscription?.remove();
|
|
156
151
|
}, []);
|
|
157
152
|
```
|
|
153
|
+
|
|
154
|
+
> [!NOTE]
|
|
155
|
+
> Receiving push token may not work on simulators. Make sure to use physical device when testing this functionality.
|
|
156
|
+
|
|
157
|
+
## Push notifications
|
|
158
|
+
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:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"aps":{
|
|
163
|
+
"event":"update",
|
|
164
|
+
"content-state":{
|
|
165
|
+
"title":"Hello",
|
|
166
|
+
"subtitle":"World",
|
|
167
|
+
"timerEndDateInMilliseconds":1754064245000,
|
|
168
|
+
"imageName": "live_activity_image",
|
|
169
|
+
"dynamicIslandImageName": "dynamic_island_image"
|
|
170
|
+
},
|
|
171
|
+
"timestamp":1754063621319 // timestamp of when the push notification was sent
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Where `timerEndDateInMilliseconds` value is a timestamp in milliseconds corresponding to the target point of the counter displayed in live activity view.
|
|
177
|
+
|
|
178
|
+
## Image support
|
|
179
|
+
Live activity view also supports image display. There are two dedicated fields in the `state` object for that:
|
|
180
|
+
- `imageName`
|
|
181
|
+
- `dynamicIslandImageName`
|
|
182
|
+
|
|
183
|
+
The value of each field can be:
|
|
184
|
+
- a string which maps to an asset name
|
|
185
|
+
- 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
|
@@ -26,21 +26,18 @@ export type LiveActivityModuleEvents = {
|
|
|
26
26
|
/**
|
|
27
27
|
* @param {LiveActivityState} state The state for the live activity.
|
|
28
28
|
* @param {LiveActivityConfig} config Live activity config object.
|
|
29
|
-
* @returns {string} The identifier of the started activity.
|
|
30
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
29
|
+
* @returns {string} The identifier of the started activity or undefined if creating live activity failed.
|
|
31
30
|
*/
|
|
32
|
-
export declare function startActivity(state: LiveActivityState, config?: LiveActivityConfig): string;
|
|
31
|
+
export declare function startActivity(state: LiveActivityState, config?: LiveActivityConfig): string | undefined;
|
|
33
32
|
/**
|
|
34
33
|
* @param {string} id The identifier of the activity to stop.
|
|
35
34
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
36
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
37
35
|
*/
|
|
38
36
|
export declare function stopActivity(id: string, state: LiveActivityState): any;
|
|
39
37
|
/**
|
|
40
38
|
* @param {string} id The identifier of the activity to update.
|
|
41
39
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
42
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
43
40
|
*/
|
|
44
41
|
export declare function updateActivity(id: string, state: LiveActivityState): any;
|
|
45
|
-
export declare function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription;
|
|
42
|
+
export declare function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription | undefined;
|
|
46
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,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
|
|
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
|
@@ -3,40 +3,55 @@ import { Platform } from "react-native";
|
|
|
3
3
|
/**
|
|
4
4
|
* @param {LiveActivityState} state The state for the live activity.
|
|
5
5
|
* @param {LiveActivityConfig} config Live activity config object.
|
|
6
|
-
* @returns {string} The identifier of the started activity.
|
|
7
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
6
|
+
* @returns {string} The identifier of the started activity or undefined if creating live activity failed.
|
|
8
7
|
*/
|
|
9
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, config);
|
|
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;AAgCxC
|
|
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 {
|
|
@@ -62,10 +63,6 @@ public class ExpoLiveActivityModule: Module {
|
|
|
62
63
|
)
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
func toContentStateDate(date: Double?) -> Date? {
|
|
66
|
-
return date.map { Date(timeIntervalSince1970: $0 / 1000) }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
66
|
func updateImages(state: LiveActivityState, newState: inout LiveActivityAttributes.ContentState) async throws {
|
|
70
67
|
if let name = state.imageName {
|
|
71
68
|
print("imageName: \(name)")
|
|
@@ -102,7 +99,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
102
99
|
let initialState = LiveActivityAttributes.ContentState(
|
|
103
100
|
title: state.title,
|
|
104
101
|
subtitle: state.subtitle,
|
|
105
|
-
|
|
102
|
+
timerEndDateInMilliseconds: state.date,
|
|
106
103
|
)
|
|
107
104
|
let pushNotificationsEnabled =
|
|
108
105
|
Bundle.main.object(forInfoDictionaryKey: "ExpoLiveActivity_EnablePushNotifications") as? Bool
|
|
@@ -129,11 +126,12 @@ public class ExpoLiveActivityModule: Module {
|
|
|
129
126
|
return activity.id
|
|
130
127
|
} catch (let error) {
|
|
131
128
|
print("Error with live activity: \(error)")
|
|
129
|
+
throw LiveActivityErrors.unexpetedError(error)
|
|
132
130
|
}
|
|
133
131
|
}
|
|
134
|
-
throw
|
|
132
|
+
throw LiveActivityErrors.liveActivitiesNotEnabled
|
|
135
133
|
} else {
|
|
136
|
-
throw
|
|
134
|
+
throw LiveActivityErrors.unsupportedOS
|
|
137
135
|
}
|
|
138
136
|
}
|
|
139
137
|
|
|
@@ -143,7 +141,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
143
141
|
var newState = LiveActivityAttributes.ContentState(
|
|
144
142
|
title: state.title,
|
|
145
143
|
subtitle: state.subtitle,
|
|
146
|
-
|
|
144
|
+
timerEndDateInMilliseconds: state.date,
|
|
147
145
|
)
|
|
148
146
|
if let activity = Activity<LiveActivityAttributes>.activities.first(where: {
|
|
149
147
|
$0.id == activityId
|
|
@@ -160,7 +158,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
160
158
|
print("Didn't find activity with ID \(activityId)")
|
|
161
159
|
}
|
|
162
160
|
} else {
|
|
163
|
-
throw
|
|
161
|
+
throw LiveActivityErrors.unsupportedOS
|
|
164
162
|
}
|
|
165
163
|
}
|
|
166
164
|
|
|
@@ -170,7 +168,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
170
168
|
var newState = LiveActivityAttributes.ContentState(
|
|
171
169
|
title: state.title,
|
|
172
170
|
subtitle: state.subtitle,
|
|
173
|
-
|
|
171
|
+
timerEndDateInMilliseconds: state.date,
|
|
174
172
|
)
|
|
175
173
|
if let activity = Activity<LiveActivityAttributes>.activities.first(where: {
|
|
176
174
|
$0.id == activityId
|
|
@@ -184,7 +182,7 @@ public class ExpoLiveActivityModule: Module {
|
|
|
184
182
|
print("Didn't find activity with ID \(activityId)")
|
|
185
183
|
}
|
|
186
184
|
} else {
|
|
187
|
-
throw
|
|
185
|
+
throw LiveActivityErrors.unsupportedOS
|
|
188
186
|
}
|
|
189
187
|
}
|
|
190
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
|
}
|
|
@@ -24,7 +24,7 @@ struct LiveActivityAttributes: ActivityAttributes {
|
|
|
24
24
|
var progressViewTint: String?
|
|
25
25
|
var progressViewLabelColor: String?
|
|
26
26
|
var deepLinkUrl: String?
|
|
27
|
-
var timerType: DynamicIslandTimerType
|
|
27
|
+
var timerType: DynamicIslandTimerType?
|
|
28
28
|
|
|
29
29
|
enum DynamicIslandTimerType: String, Codable {
|
|
30
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
|
}
|
|
@@ -25,7 +25,7 @@ struct LiveActivityAttributes: ActivityAttributes {
|
|
|
25
25
|
var progressViewTint: String?
|
|
26
26
|
var progressViewLabelColor: String?
|
|
27
27
|
var deepLinkUrl: String?
|
|
28
|
-
var timerType: DynamicIslandTimerType
|
|
28
|
+
var timerType: DynamicIslandTimerType?
|
|
29
29
|
|
|
30
30
|
enum DynamicIslandTimerType: String, Codable {
|
|
31
31
|
case circular
|
|
@@ -58,7 +58,7 @@ struct LiveActivityWidget: Widget {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
DynamicIslandExpandedRegion(.bottom) {
|
|
61
|
-
if let date = context.state.
|
|
61
|
+
if let date = context.state.timerEndDateInMilliseconds {
|
|
62
62
|
dynamicIslandExpandedBottom(endDate: date, progressViewTint: context.attributes.progressViewTint)
|
|
63
63
|
.padding(.horizontal, 5)
|
|
64
64
|
.applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
@@ -71,18 +71,18 @@ struct LiveActivityWidget: Widget {
|
|
|
71
71
|
.applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
72
72
|
}
|
|
73
73
|
} compactTrailing: {
|
|
74
|
-
if let date = context.state.
|
|
74
|
+
if let date = context.state.timerEndDateInMilliseconds {
|
|
75
75
|
compactTimer(
|
|
76
76
|
endDate: date,
|
|
77
|
-
timerType: context.attributes.timerType,
|
|
77
|
+
timerType: context.attributes.timerType ?? .circular,
|
|
78
78
|
progressViewTint: context.attributes.progressViewTint
|
|
79
79
|
).applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
80
80
|
}
|
|
81
81
|
} minimal: {
|
|
82
|
-
if let date = context.state.
|
|
82
|
+
if let date = context.state.timerEndDateInMilliseconds {
|
|
83
83
|
compactTimer(
|
|
84
84
|
endDate: date,
|
|
85
|
-
timerType: context.attributes.timerType,
|
|
85
|
+
timerType: context.attributes.timerType ?? .circular,
|
|
86
86
|
progressViewTint: context.attributes.progressViewTint
|
|
87
87
|
).applyWidgetURL(from: context.attributes.deepLinkUrl)
|
|
88
88
|
}
|
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
|
@@ -34,43 +34,55 @@ export type LiveActivityModuleEvents = {
|
|
|
34
34
|
/**
|
|
35
35
|
* @param {LiveActivityState} state The state for the live activity.
|
|
36
36
|
* @param {LiveActivityConfig} config Live activity config object.
|
|
37
|
-
* @returns {string} The identifier of the started activity.
|
|
38
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
37
|
+
* @returns {string} The identifier of the started activity or undefined if creating live activity failed.
|
|
39
38
|
*/
|
|
40
|
-
export function startActivity(state: LiveActivityState, config?: LiveActivityConfig): string {
|
|
39
|
+
export function startActivity(state: LiveActivityState, config?: LiveActivityConfig): string | undefined {
|
|
41
40
|
if (Platform.OS !== "ios") {
|
|
42
|
-
|
|
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
|
|
43
49
|
}
|
|
44
|
-
return ExpoLiveActivityModule.startActivity(state, config);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
/**
|
|
48
53
|
* @param {string} id The identifier of the activity to stop.
|
|
49
54
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
50
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
51
55
|
*/
|
|
52
56
|
export function stopActivity(id: string, state: LiveActivityState) {
|
|
53
57
|
if (Platform.OS !== "ios") {
|
|
54
|
-
|
|
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}`);
|
|
55
64
|
}
|
|
56
|
-
return ExpoLiveActivityModule.stopActivity(id, state);
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
/**
|
|
60
68
|
* @param {string} id The identifier of the activity to update.
|
|
61
69
|
* @param {LiveActivityState} state The updated state for the live activity.
|
|
62
|
-
* @throws {Error} When function is called on a platform different from iOS.
|
|
63
70
|
*/
|
|
64
71
|
export function updateActivity(id: string, state: LiveActivityState) {
|
|
65
72
|
if (Platform.OS !== "ios") {
|
|
66
|
-
|
|
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}`);
|
|
67
79
|
}
|
|
68
|
-
return ExpoLiveActivityModule.updateActivity(id, state);
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
export function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription {
|
|
82
|
+
export function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription | undefined {
|
|
72
83
|
if (Platform.OS !== "ios") {
|
|
73
|
-
|
|
84
|
+
console.error("addActivityTokenListener is only available on iOS");
|
|
85
|
+
return undefined
|
|
74
86
|
}
|
|
75
87
|
return ExpoLiveActivityModule.addListener('onTokenReceived', listener);
|
|
76
88
|
}
|