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 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 throw an error, ensuring that they are used in the correct context.
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.remove();
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
@@ -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;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAK3F;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,OAKhE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,OAKlE;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAAG,iBAAiB,CAKjH"}
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
- throw new Error("startActivity is only available on iOS");
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
- throw new Error("stopActivity is only available on iOS");
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
- throw new Error("updateActivity is only available on iOS");
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
- throw new Error("updateActivity is only available on iOS");
53
+ console.error("addActivityTokenListener is only available on iOS");
54
+ return undefined;
40
55
  }
41
56
  return ExpoLiveActivityModule.addListener('onTokenReceived', listener);
42
57
  }
@@ -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;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAwB,EAAE,MAA2B;IACjF,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,sBAAsB,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAC7D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,EAAU,EAAE,KAAwB;IAC/D,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,sBAAsB,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AACxD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,EAAU,EAAE,KAAwB;IACjE,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,sBAAsB,CAAC,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,QAAqD;IAC5F,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,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.\n * @throws {Error} When function is called on a platform different from iOS.\n */\nexport function startActivity(state: LiveActivityState, config?: LiveActivityConfig): string {\n if (Platform.OS !== \"ios\") {\n throw new Error(\"startActivity is only available on iOS\");\n }\n return ExpoLiveActivityModule.startActivity(state, 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 * @throws {Error} When function is called on a platform different from iOS.\n */\nexport function stopActivity(id: string, state: LiveActivityState) {\n if (Platform.OS !== \"ios\") {\n throw new Error(\"stopActivity is only available on iOS\");\n }\n 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 * @throws {Error} When function is called on a platform different from iOS.\n */\nexport function updateActivity(id: string, state: LiveActivityState) {\n if (Platform.OS !== \"ios\") {\n throw new Error(\"updateActivity is only available on iOS\");\n }\n return ExpoLiveActivityModule.updateActivity(id, state);\n}\n\nexport function addActivityTokenListener(listener: (event: ActivityTokenReceivedEvent) => void): EventSubscription {\n if (Platform.OS !== \"ios\") {\n throw new Error(\"updateActivity is only available on iOS\");\n }\n return ExpoLiveActivityModule.addListener('onTokenReceived', listener);\n}\n"]}
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 ModuleErrors: Error {
5
- case unsupported
6
- case liveActivitiesNotEnabled
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
- date: toContentStateDate(date: state.date),
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 ModuleErrors.liveActivitiesNotEnabled
132
+ throw LiveActivityErrors.liveActivitiesNotEnabled
135
133
  } else {
136
- throw ModuleErrors.unsupported
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
- date: toContentStateDate(date: state.date),
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 ModuleErrors.unsupported
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
- date: toContentStateDate(date: state.date),
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 ModuleErrors.unsupported
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 date: Date?
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.date {
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 date: Double?
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.date {
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.date {
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.date {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-live-activity",
3
- "version": "0.2.0-alpha4",
3
+ "version": "0.2.0",
4
4
  "description": "A module for adding Live Activity to a React Native app for iOS.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -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
- throw new Error("startActivity is only available on iOS");
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
- throw new Error("stopActivity is only available on iOS");
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
- throw new Error("updateActivity is only available on iOS");
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
- throw new Error("updateActivity is only available on iOS");
84
+ console.error("addActivityTokenListener is only available on iOS");
85
+ return undefined
74
86
  }
75
87
  return ExpoLiveActivityModule.addListener('onTokenReceived', listener);
76
88
  }