expo-live-activity 0.1.0 → 0.2.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +84 -19
- package/app.plugin.js +2 -0
- package/build/ExpoLiveActivityModule.d.ts +1 -8
- package/build/ExpoLiveActivityModule.d.ts.map +1 -1
- package/build/ExpoLiveActivityModule.js +2 -3
- package/build/ExpoLiveActivityModule.js.map +1 -1
- package/build/index.d.ts +36 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +37 -5
- package/build/index.js.map +1 -1
- package/expo-module.config.json +3 -11
- package/ios/ExpoLiveActivity.podspec +1 -1
- package/ios/ExpoLiveActivityModule.swift +130 -38
- package/ios/LiveActivityAttributes.swift +32 -0
- package/ios-files/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/ios-files/Assets.xcassets/AppIcon.appiconset/Contents.json +35 -0
- package/ios-files/Assets.xcassets/Contents.json +6 -0
- package/ios-files/Assets.xcassets/WidgetBackground.colorset/Contents.json +11 -0
- package/ios-files/Color+hex.swift +37 -0
- package/ios-files/Info.plist +11 -0
- package/ios-files/LiveActivityBundle.swift +16 -0
- package/ios-files/LiveActivityLiveActivity.swift +142 -0
- package/ios-files/LiveActivityView.swift +73 -0
- package/package.json +15 -14
- package/plugin/build/index.d.ts +3 -0
- package/plugin/build/index.js +35 -0
- package/plugin/build/lib/getWidgetExtensionEntitlements.d.ts +3 -0
- package/plugin/build/lib/getWidgetExtensionEntitlements.js +16 -0
- package/plugin/build/lib/getWidgetFiles.d.ts +10 -0
- package/plugin/build/lib/getWidgetFiles.js +157 -0
- package/plugin/build/withConfig.d.ts +6 -0
- package/plugin/build/withConfig.js +54 -0
- package/plugin/build/withPodfile.d.ts +4 -0
- package/plugin/build/withPodfile.js +92 -0
- package/plugin/build/withWidgetExtensionEntitlements.d.ts +6 -0
- package/plugin/build/withWidgetExtensionEntitlements.js +56 -0
- package/plugin/build/withXcode.d.ts +6 -0
- package/plugin/build/withXcode.js +88 -0
- package/plugin/build/xcode/addBuildPhases.d.ts +13 -0
- package/plugin/build/xcode/addBuildPhases.js +57 -0
- package/plugin/build/xcode/addPbxGroup.d.ts +6 -0
- package/plugin/build/xcode/addPbxGroup.js +25 -0
- package/plugin/build/xcode/addProductFile.d.ts +5 -0
- package/plugin/build/xcode/addProductFile.js +21 -0
- package/plugin/build/xcode/addTargetDependency.d.ts +4 -0
- package/plugin/build/xcode/addTargetDependency.js +14 -0
- package/plugin/build/xcode/addToPbxNativeTargetSection.d.ts +24 -0
- package/plugin/build/xcode/addToPbxNativeTargetSection.js +29 -0
- package/plugin/build/xcode/addToPbxProjectSection.d.ts +4 -0
- package/plugin/build/xcode/addToPbxProjectSection.js +14 -0
- package/plugin/build/xcode/addXCConfigurationList.d.ts +8 -0
- package/plugin/build/xcode/addXCConfigurationList.js +61 -0
- package/plugin/src/index.ts +43 -0
- package/plugin/src/lib/getWidgetExtensionEntitlements.ts +26 -0
- package/plugin/src/lib/getWidgetFiles.ts +149 -0
- package/plugin/src/withConfig.ts +62 -0
- package/plugin/src/withPodfile.ts +72 -0
- package/plugin/src/withWidgetExtensionEntitlements.ts +25 -0
- package/plugin/src/withXcode.ts +76 -0
- package/plugin/src/xcode/addBuildPhases.ts +83 -0
- package/plugin/src/xcode/addPbxGroup.ts +48 -0
- package/plugin/src/xcode/addProductFile.ts +25 -0
- package/plugin/src/xcode/addTargetDependency.ts +17 -0
- package/plugin/src/xcode/addToPbxNativeTargetSection.ts +46 -0
- package/plugin/src/xcode/addToPbxProjectSection.ts +23 -0
- package/plugin/src/xcode/addXCConfigurationList.ts +83 -0
- package/plugin/tsconfig.json +9 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/src/ExpoLiveActivityModule.ts +2 -11
- package/src/index.ts +59 -5
- package/android/build.gradle +0 -43
- package/android/src/main/AndroidManifest.xml +0 -2
- package/android/src/main/java/expo/modules/liveactivity/ExpoLiveActivityModule.kt +0 -50
- package/android/src/main/java/expo/modules/liveactivity/ExpoLiveActivityView.kt +0 -30
- package/build/ExpoLiveActivity.types.d.ts +0 -18
- package/build/ExpoLiveActivity.types.d.ts.map +0 -1
- package/build/ExpoLiveActivity.types.js +0 -2
- package/build/ExpoLiveActivity.types.js.map +0 -1
- package/build/ExpoLiveActivityModule.web.d.ts +0 -10
- package/build/ExpoLiveActivityModule.web.d.ts.map +0 -1
- package/build/ExpoLiveActivityModule.web.js +0 -12
- package/build/ExpoLiveActivityModule.web.js.map +0 -1
- package/build/ExpoLiveActivityView.d.ts +0 -4
- package/build/ExpoLiveActivityView.d.ts.map +0 -1
- package/build/ExpoLiveActivityView.js +0 -7
- package/build/ExpoLiveActivityView.js.map +0 -1
- package/build/ExpoLiveActivityView.web.d.ts +0 -4
- package/build/ExpoLiveActivityView.web.d.ts.map +0 -1
- package/build/ExpoLiveActivityView.web.js +0 -7
- package/build/ExpoLiveActivityView.web.js.map +0 -1
- package/ios/ExpoLiveActivityView.swift +0 -38
- package/src/ExpoLiveActivity.types.ts +0 -19
- package/src/ExpoLiveActivityModule.web.ts +0 -15
- package/src/ExpoLiveActivityView.tsx +0 -11
- package/src/ExpoLiveActivityView.web.tsx +0 -15
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Software Mansion <swmansion.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,35 +1,100 @@
|
|
|
1
1
|
# expo-live-activity
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`expo-live-activity` is a React Native module designed for use with Expo to manage and display live activities on iOS devices exclusively. This module leverages the Live Activities feature introduced in iOS 16, allowing developers to deliver timely updates right on the lock screen.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Features
|
|
6
|
+
- Start, update, and stop live activities directly from your React Native application.
|
|
7
|
+
- Easy integration with a comprehensive API.
|
|
8
|
+
- Custom image support within live activities with a pre-configured path.
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
## Platform compatibility
|
|
11
|
+
**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.
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
## Installation
|
|
14
|
+
To begin using `expo-live-activity`, follow the installation and configuration steps outlined below:
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
|
|
17
|
-
|
|
18
|
-
### Add the package to your npm dependencies
|
|
19
|
-
|
|
20
|
-
```
|
|
16
|
+
### Step 1: Installation
|
|
17
|
+
Run the following command to add the expo-live-activity module to your project:
|
|
18
|
+
```sh
|
|
21
19
|
npm install expo-live-activity
|
|
22
20
|
```
|
|
23
21
|
|
|
24
|
-
###
|
|
22
|
+
### Step 2: Config Plugin Setup
|
|
23
|
+
The module comes with a built-in config plugin that creates a target in iOS with all the necessary files. The images used in live activities should be added to a pre-defined folder in your assets directory:
|
|
24
|
+
1. **Add the config plugin to your app.json or app.config.js:**
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"expo": {
|
|
28
|
+
"plugins": ["expo-live-activity"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
2. **Assets configuration:**
|
|
33
|
+
Place images intended for live activities in the `assets/live-activity` folder. The plugin manages these assets automatically.
|
|
34
|
+
|
|
35
|
+
### Step 3: Usage in Your React Native App
|
|
36
|
+
Import the functionalities provided by the `expo-live-activity` module in your JavaScript or TypeScript files:
|
|
37
|
+
```javascript
|
|
38
|
+
import * as LiveActivity from "expo-live-activity";
|
|
39
|
+
```
|
|
25
40
|
|
|
41
|
+
## API
|
|
42
|
+
`expo-live-activity` module exports three primary functions to manage live activities:
|
|
26
43
|
|
|
44
|
+
- **`startActivity(state, styles)`**:
|
|
45
|
+
Start a new live activity. Takes a `state` configuration object for initial activity state and an optional `styles` object to customize appearance. It returns the `ID` of the created live activity, which should be stored for future reference.
|
|
27
46
|
|
|
47
|
+
- **`updateActivity(activityId, state)`**:
|
|
48
|
+
Update an existing live activity. The `state` object should contain updated information. The `activityId` indicates which activity should be updated.
|
|
28
49
|
|
|
29
|
-
|
|
50
|
+
- **`stopActivity(activityId, state)`**:
|
|
51
|
+
Terminate an ongoing live activity. The `state` object should contain the final state of the activity. The `activityId` indicates which activity should be stopped.
|
|
30
52
|
|
|
31
|
-
|
|
53
|
+
### State Object Structure
|
|
54
|
+
The `state` object should include:
|
|
55
|
+
```javascript
|
|
56
|
+
{
|
|
57
|
+
title: string;
|
|
58
|
+
subtitle?: string;
|
|
59
|
+
date?: number; // Set as epoch time in milliseconds
|
|
60
|
+
imageName?: string; // Matches the name of the image in 'assets/live-activity'
|
|
61
|
+
dynamicIslandImageName?: string; // Matches the name of the image in 'assets/live-activity'
|
|
62
|
+
};
|
|
63
|
+
```
|
|
32
64
|
|
|
33
|
-
|
|
65
|
+
### Styles Object Structure
|
|
66
|
+
The `styles` object should include:
|
|
67
|
+
```typescript
|
|
68
|
+
{
|
|
69
|
+
backgroundColor?: string;
|
|
70
|
+
titleColor?: string;
|
|
71
|
+
subtitleColor?: string;
|
|
72
|
+
progressViewTint?: string;
|
|
73
|
+
progressViewLabelColor?: string;
|
|
74
|
+
timerType?: DynamicIslandTimerType; // "circular" | "digital" - defines timer appereance on the dynamic island
|
|
75
|
+
};
|
|
76
|
+
```
|
|
34
77
|
|
|
35
|
-
|
|
78
|
+
## Example Usage
|
|
79
|
+
```javascript
|
|
80
|
+
const state = {
|
|
81
|
+
title: "Title",
|
|
82
|
+
subtitle: "This is a subtitle",
|
|
83
|
+
date: new Date(Date.now() + 60 * 1000 * 5).getTime(),
|
|
84
|
+
imageName: "live_activity_image",
|
|
85
|
+
dynamicIslandImageName: "dynamic_island_image"
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const styles = {
|
|
89
|
+
backgroundColor: "#FFFFFF",
|
|
90
|
+
titleColor: "#000000",
|
|
91
|
+
subtitleColor: "#333333",
|
|
92
|
+
progressViewTint: "#4CAF50",
|
|
93
|
+
progressViewLabelColor: "#FFFFFF",
|
|
94
|
+
timerType: "circular"
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const activityId = LiveActivity.startActivity(state, styles);
|
|
98
|
+
// Store activityId for future reference
|
|
99
|
+
```
|
|
100
|
+
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.
|
package/app.plugin.js
ADDED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import { ExpoLiveActivityModuleEvents } from './ExpoLiveActivity.types';
|
|
3
|
-
declare class ExpoLiveActivityModule extends NativeModule<ExpoLiveActivityModuleEvents> {
|
|
4
|
-
PI: number;
|
|
5
|
-
hello(): string;
|
|
6
|
-
setValueAsync(value: string): Promise<void>;
|
|
7
|
-
}
|
|
8
|
-
declare const _default: ExpoLiveActivityModule;
|
|
1
|
+
declare const _default: any;
|
|
9
2
|
export default _default;
|
|
10
3
|
//# sourceMappingURL=ExpoLiveActivityModule.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoLiveActivityModule.d.ts","sourceRoot":"","sources":["../src/ExpoLiveActivityModule.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ExpoLiveActivityModule.d.ts","sourceRoot":"","sources":["../src/ExpoLiveActivityModule.ts"],"names":[],"mappings":";AAEA,wBAA+D"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export default requireNativeModule('ExpoLiveActivity');
|
|
1
|
+
import { requireOptionalNativeModule } from 'expo';
|
|
2
|
+
export default requireOptionalNativeModule('ExpoLiveActivity');
|
|
4
3
|
//# sourceMappingURL=ExpoLiveActivityModule.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoLiveActivityModule.js","sourceRoot":"","sources":["../src/ExpoLiveActivityModule.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ExpoLiveActivityModule.js","sourceRoot":"","sources":["../src/ExpoLiveActivityModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,MAAM,CAAC;AAEnD,eAAe,2BAA2B,CAAC,kBAAkB,CAAC,CAAC","sourcesContent":["import { requireOptionalNativeModule } from 'expo';\n\nexport default requireOptionalNativeModule('ExpoLiveActivity');\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
|
|
1
|
+
export type DynamicIslandTimerType = 'circular' | 'digital';
|
|
2
|
+
export type LiveActivityState = {
|
|
3
|
+
title: string;
|
|
4
|
+
subtitle?: string;
|
|
5
|
+
date?: number;
|
|
6
|
+
imageName?: string;
|
|
7
|
+
dynamicIslandImageName?: string;
|
|
8
|
+
};
|
|
9
|
+
export type LiveActivityStyles = {
|
|
10
|
+
backgroundColor?: string;
|
|
11
|
+
titleColor?: string;
|
|
12
|
+
subtitleColor?: string;
|
|
13
|
+
progressViewTint?: string;
|
|
14
|
+
progressViewLabelColor?: string;
|
|
15
|
+
timerType: DynamicIslandTimerType;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* @param {LiveActivityState} state The state for the live activity.
|
|
19
|
+
* @returns {string} The identifier of the started activity.
|
|
20
|
+
* @throws {Error} When function is called on platform different than iOS.
|
|
21
|
+
*/
|
|
22
|
+
export declare function startActivity(state: LiveActivityState, styles?: LiveActivityStyles): string;
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} id The identifier of the activity to stop.
|
|
25
|
+
* @param {LiveActivityState} state The updated state for the live activity.
|
|
26
|
+
* @returns {string} The identifier of the stopped activity.
|
|
27
|
+
* @throws {Error} When function is called on platform different than iOS.
|
|
28
|
+
*/
|
|
29
|
+
export declare function stopActivity(id: string, state: LiveActivityState): string;
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} id The identifier of the activity to update.
|
|
32
|
+
* @param {LiveActivityState} state The updated state for the live activity.
|
|
33
|
+
* @returns {string} The identifier of the updated activity.
|
|
34
|
+
* @throws {Error} When function is called on platform different than iOS.
|
|
35
|
+
*/
|
|
36
|
+
export declare function updateActivity(id: string, state: LiveActivityState): string;
|
|
4
37
|
//# 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":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,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,EAAE,sBAAsB,CAAC;CACnC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAK3F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAKzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAK3E"}
|
package/build/index.js
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import ExpoLiveActivityModule from "./ExpoLiveActivityModule";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
/**
|
|
4
|
+
* @param {LiveActivityState} state The state for the live activity.
|
|
5
|
+
* @returns {string} The identifier of the started activity.
|
|
6
|
+
* @throws {Error} When function is called on platform different than iOS.
|
|
7
|
+
*/
|
|
8
|
+
export function startActivity(state, styles) {
|
|
9
|
+
if (Platform.OS !== "ios") {
|
|
10
|
+
throw new Error("startActivity is only available on iOS");
|
|
11
|
+
}
|
|
12
|
+
return ExpoLiveActivityModule.startActivity(state, styles);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} id The identifier of the activity to stop.
|
|
16
|
+
* @param {LiveActivityState} state The updated state for the live activity.
|
|
17
|
+
* @returns {string} The identifier of the stopped activity.
|
|
18
|
+
* @throws {Error} When function is called on platform different than iOS.
|
|
19
|
+
*/
|
|
20
|
+
export function stopActivity(id, state) {
|
|
21
|
+
if (Platform.OS !== "ios") {
|
|
22
|
+
throw new Error("stopActivity is only available on iOS");
|
|
23
|
+
}
|
|
24
|
+
return ExpoLiveActivityModule.stopActivity(id, state);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} id The identifier of the activity to update.
|
|
28
|
+
* @param {LiveActivityState} state The updated state for the live activity.
|
|
29
|
+
* @returns {string} The identifier of the updated activity.
|
|
30
|
+
* @throws {Error} When function is called on platform different than iOS.
|
|
31
|
+
*/
|
|
32
|
+
export function updateActivity(id, state) {
|
|
33
|
+
if (Platform.OS !== "ios") {
|
|
34
|
+
throw new Error("updateActivity is only available on iOS");
|
|
35
|
+
}
|
|
36
|
+
return ExpoLiveActivityModule.updateActivity(id, state);
|
|
37
|
+
}
|
|
6
38
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
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;AAqBxC;;;;GAIG;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;;;;;GAKG;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;;;;;GAKG;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","sourcesContent":["import ExpoLiveActivityModule from \"./ExpoLiveActivityModule\";\nimport { Platform } from \"react-native\";\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 LiveActivityStyles = {\n backgroundColor?: string;\n titleColor?: string;\n subtitleColor?: string;\n progressViewTint?: string;\n progressViewLabelColor?: string;\n timerType: DynamicIslandTimerType;\n};\n\n/**\n * @param {LiveActivityState} state The state for the live activity.\n * @returns {string} The identifier of the started activity.\n * @throws {Error} When function is called on platform different than iOS.\n */\nexport function startActivity(state: LiveActivityState, styles?: LiveActivityStyles): string {\n if (Platform.OS !== \"ios\") {\n throw new Error(\"startActivity is only available on iOS\");\n }\n return ExpoLiveActivityModule.startActivity(state, styles);\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 * @returns {string} The identifier of the stopped activity.\n * @throws {Error} When function is called on platform different than iOS.\n */\nexport function stopActivity(id: string, state: LiveActivityState): string {\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 * @returns {string} The identifier of the updated activity.\n * @throws {Error} When function is called on platform different than iOS.\n */\nexport function updateActivity(id: string, state: LiveActivityState): string {\n if (Platform.OS !== \"ios\") {\n throw new Error(\"updateActivity is only available on iOS\");\n }\n return ExpoLiveActivityModule.updateActivity(id, state);\n}\n"]}
|
package/expo-module.config.json
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"platforms": [
|
|
3
|
-
"apple",
|
|
4
|
-
"android",
|
|
5
|
-
"web"
|
|
6
|
-
],
|
|
2
|
+
"platforms": ["apple", "android", "web"],
|
|
7
3
|
"apple": {
|
|
8
|
-
"modules": [
|
|
9
|
-
"ExpoLiveActivityModule"
|
|
10
|
-
]
|
|
4
|
+
"modules": ["ExpoLiveActivityModule"]
|
|
11
5
|
},
|
|
12
6
|
"android": {
|
|
13
|
-
"modules": [
|
|
14
|
-
"expo.modules.liveactivity.ExpoLiveActivityModule"
|
|
15
|
-
]
|
|
7
|
+
"modules": ["expo.modules.liveactivity.ExpoLiveActivityModule"]
|
|
16
8
|
}
|
|
17
9
|
}
|
|
@@ -15,7 +15,7 @@ Pod::Spec.new do |s|
|
|
|
15
15
|
:tvos => '15.1'
|
|
16
16
|
}
|
|
17
17
|
s.swift_version = '5.4'
|
|
18
|
-
s.source = { git: 'https://github.com/
|
|
18
|
+
s.source = { git: 'https://github.com/anna1901/expo-live-activity' }
|
|
19
19
|
s.static_framework = true
|
|
20
20
|
|
|
21
21
|
s.dependency 'ExpoModulesCore'
|
|
@@ -1,48 +1,140 @@
|
|
|
1
|
+
import ActivityKit
|
|
1
2
|
import ExpoModulesCore
|
|
2
3
|
|
|
4
|
+
enum ModuleErrors: Error {
|
|
5
|
+
case unsupported
|
|
6
|
+
case liveActivitiesNotEnabled
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
public class ExpoLiveActivityModule: Module {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
])
|
|
17
|
-
|
|
18
|
-
// Defines event names that the module can send to JavaScript.
|
|
19
|
-
Events("onChange")
|
|
20
|
-
|
|
21
|
-
// Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
|
|
22
|
-
Function("hello") {
|
|
23
|
-
return "Hello world! 👋"
|
|
24
|
-
}
|
|
10
|
+
struct LiveActivityState: Record {
|
|
11
|
+
@Field
|
|
12
|
+
var title: String
|
|
13
|
+
|
|
14
|
+
@Field
|
|
15
|
+
var subtitle: String?
|
|
16
|
+
|
|
17
|
+
@Field
|
|
18
|
+
var date: Double?
|
|
19
|
+
|
|
20
|
+
@Field
|
|
21
|
+
var imageName: String?
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
AsyncFunction("setValueAsync") { (value: String) in
|
|
29
|
-
// Send an event to JavaScript.
|
|
30
|
-
self.sendEvent("onChange", [
|
|
31
|
-
"value": value
|
|
32
|
-
])
|
|
23
|
+
@Field
|
|
24
|
+
var dynamicIslandImageName: String?
|
|
33
25
|
}
|
|
26
|
+
|
|
27
|
+
struct LiveActivityStyles: Record {
|
|
28
|
+
@Field
|
|
29
|
+
var backgroundColor: String?
|
|
30
|
+
|
|
31
|
+
@Field
|
|
32
|
+
var titleColor: String?
|
|
33
|
+
|
|
34
|
+
@Field
|
|
35
|
+
var subtitleColor: String?
|
|
36
|
+
|
|
37
|
+
@Field
|
|
38
|
+
var progressViewTint: String?
|
|
39
|
+
|
|
40
|
+
@Field
|
|
41
|
+
var progressViewLabelColor: String?
|
|
42
|
+
|
|
43
|
+
@Field
|
|
44
|
+
var timerType: DynamicIslandTimerType?
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
enum DynamicIslandTimerType: String, Enumerable {
|
|
48
|
+
case circular
|
|
49
|
+
case digital
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public func definition() -> ModuleDefinition {
|
|
53
|
+
Name("ExpoLiveActivity")
|
|
34
54
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
Function("startActivity") { (state: LiveActivityState, styles: LiveActivityStyles? ) -> String in
|
|
56
|
+
let date = state.date != nil ? Date(timeIntervalSince1970: state.date! / 1000) : nil
|
|
57
|
+
print("Starting activity")
|
|
58
|
+
if #available(iOS 16.2, *) {
|
|
59
|
+
if ActivityAuthorizationInfo().areActivitiesEnabled {
|
|
60
|
+
do {
|
|
61
|
+
let counterState = LiveActivityAttributes(
|
|
62
|
+
name: "ExpoLiveActivity",
|
|
63
|
+
backgroundColor: styles?.backgroundColor,
|
|
64
|
+
titleColor: styles?.titleColor,
|
|
65
|
+
subtitleColor: styles?.subtitleColor,
|
|
66
|
+
progressViewTint: styles?.progressViewTint,
|
|
67
|
+
progressViewLabelColor: styles?.progressViewLabelColor,
|
|
68
|
+
timerType: styles?.timerType == .digital ? .digital : .circular
|
|
69
|
+
)
|
|
70
|
+
let initialState = LiveActivityAttributes.ContentState(
|
|
71
|
+
title: state.title,
|
|
72
|
+
subtitle: state.subtitle,
|
|
73
|
+
date: date,
|
|
74
|
+
imageName: state.imageName,
|
|
75
|
+
dynamicIslandImageName: state.dynamicIslandImageName)
|
|
76
|
+
let activity = try Activity.request(
|
|
77
|
+
attributes: counterState,
|
|
78
|
+
content: .init(state: initialState, staleDate: nil), pushType: nil)
|
|
79
|
+
return activity.id
|
|
80
|
+
} catch (let error) {
|
|
81
|
+
print("Error with live activity: \(error)")
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw ModuleErrors.liveActivitiesNotEnabled
|
|
85
|
+
} else {
|
|
86
|
+
throw ModuleErrors.unsupported
|
|
87
|
+
}
|
|
42
88
|
}
|
|
43
|
-
}
|
|
44
89
|
|
|
45
|
-
|
|
90
|
+
Function("stopActivity") { (activityId: String, state: LiveActivityState) -> Void in
|
|
91
|
+
if #available(iOS 16.2, *) {
|
|
92
|
+
print("Attempting to stop")
|
|
93
|
+
let endState = LiveActivityAttributes.ContentState(
|
|
94
|
+
title: state.title,
|
|
95
|
+
subtitle: state.subtitle,
|
|
96
|
+
date: state.date != nil ? Date(timeIntervalSince1970: state.date! / 1000) : nil,
|
|
97
|
+
imageName: state.imageName,
|
|
98
|
+
dynamicIslandImageName: state.dynamicIslandImageName)
|
|
99
|
+
if let activity = Activity<LiveActivityAttributes>.activities.first(where: {
|
|
100
|
+
$0.id == activityId
|
|
101
|
+
}) {
|
|
102
|
+
Task {
|
|
103
|
+
print("Stopping activity with id: \(activityId)")
|
|
104
|
+
await activity.end(
|
|
105
|
+
ActivityContent(state: endState, staleDate: nil),
|
|
106
|
+
dismissalPolicy: .immediate)
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
print("Didn't find activity with ID \(activityId)")
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
throw ModuleErrors.unsupported
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Function("updateActivity") { (activityId: String, state: LiveActivityState) -> Void in
|
|
117
|
+
if #available(iOS 16.2, *) {
|
|
118
|
+
print("Attempting to update")
|
|
119
|
+
let newState = LiveActivityAttributes.ContentState(
|
|
120
|
+
title: state.title,
|
|
121
|
+
subtitle: state.subtitle,
|
|
122
|
+
date: state.date != nil ? Date(timeIntervalSince1970: state.date! / 1000) : nil,
|
|
123
|
+
imageName: state.imageName,
|
|
124
|
+
dynamicIslandImageName: state.dynamicIslandImageName)
|
|
125
|
+
if let activity = Activity<LiveActivityAttributes>.activities.first(where: {
|
|
126
|
+
$0.id == activityId
|
|
127
|
+
}) {
|
|
128
|
+
Task {
|
|
129
|
+
print("Updating activity with id: \(activityId)")
|
|
130
|
+
await activity.update(ActivityContent(state: newState, staleDate: nil))
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
print("Didn't find activity with ID \(activityId)")
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
throw ModuleErrors.unsupported
|
|
137
|
+
}
|
|
138
|
+
}
|
|
46
139
|
}
|
|
47
|
-
}
|
|
48
140
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// LiveActivityAttributes.swift
|
|
3
|
+
// ExpoLiveActivity
|
|
4
|
+
//
|
|
5
|
+
// Created by Anna Olak on 03/06/2025.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import ActivityKit
|
|
10
|
+
|
|
11
|
+
struct LiveActivityAttributes: ActivityAttributes {
|
|
12
|
+
public struct ContentState: Codable, Hashable {
|
|
13
|
+
var title: String
|
|
14
|
+
var subtitle: String?
|
|
15
|
+
var date: Date?
|
|
16
|
+
var imageName: String?
|
|
17
|
+
var dynamicIslandImageName: String?
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var name: String
|
|
21
|
+
var backgroundColor: String?
|
|
22
|
+
var titleColor: String?
|
|
23
|
+
var subtitleColor: String?
|
|
24
|
+
var progressViewTint: String?
|
|
25
|
+
var progressViewLabelColor: String?
|
|
26
|
+
var timerType: DynamicIslandTimerType
|
|
27
|
+
|
|
28
|
+
enum DynamicIslandTimerType: String, Codable {
|
|
29
|
+
case circular
|
|
30
|
+
case digital
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"images" : [
|
|
3
|
+
{
|
|
4
|
+
"idiom" : "universal",
|
|
5
|
+
"platform" : "ios",
|
|
6
|
+
"size" : "1024x1024"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"appearances" : [
|
|
10
|
+
{
|
|
11
|
+
"appearance" : "luminosity",
|
|
12
|
+
"value" : "dark"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"idiom" : "universal",
|
|
16
|
+
"platform" : "ios",
|
|
17
|
+
"size" : "1024x1024"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"appearances" : [
|
|
21
|
+
{
|
|
22
|
+
"appearance" : "luminosity",
|
|
23
|
+
"value" : "tinted"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"idiom" : "universal",
|
|
27
|
+
"platform" : "ios",
|
|
28
|
+
"size" : "1024x1024"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"info" : {
|
|
32
|
+
"author" : "xcode",
|
|
33
|
+
"version" : 1
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
extension Color {
|
|
4
|
+
init(hex: String) {
|
|
5
|
+
var cString: String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
|
|
6
|
+
|
|
7
|
+
if (cString.hasPrefix("#")) {
|
|
8
|
+
cString.remove(at: cString.startIndex)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if ((cString.count) != 6 && (cString.count) != 8) {
|
|
12
|
+
self.init(.white)
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
var rgbValue: UInt64 = 0
|
|
17
|
+
Scanner(string: cString).scanHexInt64(&rgbValue)
|
|
18
|
+
|
|
19
|
+
if ((cString.count) == 8) {
|
|
20
|
+
self.init(
|
|
21
|
+
.sRGB,
|
|
22
|
+
red: Double((rgbValue >> 24) & 0xff) / 255,
|
|
23
|
+
green: Double((rgbValue >> 16) & 0xff) / 255,
|
|
24
|
+
blue: Double((rgbValue >> 08) & 0xff) / 255,
|
|
25
|
+
opacity: Double((rgbValue >> 00) & 0xff) / 255,
|
|
26
|
+
)
|
|
27
|
+
} else {
|
|
28
|
+
self.init(
|
|
29
|
+
.sRGB,
|
|
30
|
+
red: Double((rgbValue >> 16) & 0xff) / 255,
|
|
31
|
+
green: Double((rgbValue >> 08) & 0xff) / 255,
|
|
32
|
+
blue: Double((rgbValue >> 00) & 0xff) / 255,
|
|
33
|
+
opacity: 1
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>NSExtension</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>NSExtensionPointIdentifier</key>
|
|
8
|
+
<string>com.apple.widgetkit-extension</string>
|
|
9
|
+
</dict>
|
|
10
|
+
</dict>
|
|
11
|
+
</plist>
|