tauri-plugin-live-activity 0.1.0-alpha.3 → 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.ko.md +524 -53
- package/README.md +522 -75
- package/dist-js/index.d.ts +17 -1
- package/dist-js/index.js +26 -0
- package/dist-js/layout.d.ts +32 -0
- package/dist-js/layout.js +61 -0
- package/dist-js/types.d.ts +253 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# tauri-plugin-live-activity
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/tauri-plugin-live-activity)
|
|
4
4
|
[](https://crates.io/crates/tauri-plugin-live-activity)
|
|
5
5
|
[](https://github.com/1eedaegon/tauri-plugin-live-activity/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
Live Activities
|
|
9
|
-
Community plugin. **
|
|
8
|
+
A Tauri 2 plugin for iOS Live Activities (ActivityKit and Dynamic Island) and Android Live Updates, with an ongoing notification fallback.
|
|
9
|
+
Community plugin. **Before 1.0, a minor version may change the API.** [한국어](https://github.com/1eedaegon/tauri-plugin-live-activity/blob/main/README.ko.md)
|
|
10
10
|
|
|
11
11
|
| Platform | Backend |
|
|
12
12
|
| --- | --- |
|
|
@@ -14,67 +14,115 @@ Community plugin. **Early alpha.** [한국어](https://github.com/1eedaegon/taur
|
|
|
14
14
|
| Android API 26+ | Ongoing notifications · Live Update promotion on eligible API 36+ devices |
|
|
15
15
|
| macOS / Windows / Linux | Not implemented |
|
|
16
16
|
|
|
17
|
+
## Demo
|
|
18
|
+
|
|
17
19
|
<table>
|
|
18
|
-
<tr><th>
|
|
20
|
+
<tr><th>iOS Lock Screen</th><th>iOS Dynamic Island</th></tr>
|
|
21
|
+
<tr>
|
|
22
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-paths.png" width="400" alt="Mochi's face drawn from SVG paths on the iOS Lock Screen"></td>
|
|
23
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-island-expanded.png" width="400" alt="Mochi's snack in the expanded Dynamic Island"></td>
|
|
24
|
+
</tr>
|
|
25
|
+
<tr><th>Android notification</th><th>Samsung Now Bar</th></tr>
|
|
19
26
|
<tr>
|
|
20
|
-
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/
|
|
21
|
-
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/
|
|
22
|
-
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/mochi-android-live-activity.png" width="240" alt="Mochi's snack delivery shown by Android's native notification UI"></td>
|
|
27
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-layout-android.png" width="400" alt="Mochi's nap as an Android notification with a progress bar and a Wake Mochi action"></td>
|
|
28
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/samsung-now-bar.png" width="400" alt="Mochi's snack delivery in the One UI Now Bar on a locked Galaxy S23"></td>
|
|
23
29
|
</tr>
|
|
24
30
|
</table>
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
On older Android versions, the same example uses an
|
|
30
|
-
[ongoing notification](https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/mochi-android-notification-fallback.png).
|
|
32
|
+
These come from the [demo app](https://github.com/1eedaegon/tauri-plugin-live-activity/tree/main/examples/demo),
|
|
33
|
+
which follows a cat named Mochi. iOS: iPhone 17 simulator, iOS 26.3. Android: emulator,
|
|
34
|
+
Android 16 (API 36). Now Bar: Galaxy S23, One UI 9 (Android 17), locked.
|
|
31
35
|
|
|
32
36
|
## Install
|
|
33
37
|
|
|
34
|
-
Requires Tauri 2 and Rust 1.90+.
|
|
35
|
-
|
|
36
|
-
See the [version policy](https://github.com/1eedaegon/tauri-plugin-live-activity/blob/main/docs/compatibility.md) for feature and toolchain requirements.
|
|
38
|
+
Requires Tauri 2 and Rust 1.90+. Run each line from the app's root folder. The npm package
|
|
39
|
+
and the crate must have the same minor version.
|
|
37
40
|
|
|
38
41
|
```sh
|
|
39
|
-
npm
|
|
42
|
+
npm i tauri-plugin-live-activity@0.2 && cargo add tauri-plugin-live-activity@0.2 --manifest-path src-tauri/Cargo.toml
|
|
43
|
+
for p in default allow-request-permissions allow-start allow-update allow-end allow-start-layout allow-update-layout allow-end-layout; do npx tauri permission add live-activity:$p; done
|
|
40
44
|
```
|
|
41
45
|
|
|
42
|
-
`src-tauri/
|
|
43
|
-
|
|
44
|
-
```toml
|
|
45
|
-
[dependencies]
|
|
46
|
-
tauri-plugin-live-activity = "=0.1.0-alpha.3"
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Register the plugin in `src-tauri/src/lib.rs`:
|
|
46
|
+
Then register the plugin in `src-tauri/src/lib.rs`:
|
|
50
47
|
|
|
51
48
|
```rust
|
|
52
49
|
tauri::Builder::default()
|
|
53
50
|
.plugin(tauri_plugin_live_activity::init())
|
|
54
|
-
// ...
|
|
55
51
|
```
|
|
56
52
|
|
|
57
|
-
|
|
53
|
+
### iOS
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
A widget extension built from the plugin's template draws the activities. Run these on a Mac
|
|
56
|
+
after `tauri ios init`, in one shell. In order, they:
|
|
57
|
+
|
|
58
|
+
1. find the plugin's folder (`jq` comes with macOS 15 and later);
|
|
59
|
+
2. copy the widget, the button intent and the extension's Info.plist, which sets
|
|
60
|
+
`TauriLiveActivityLayout` to 2;
|
|
61
|
+
3. add `NSSupportsLiveActivities` to `src-tauri/Info.ios.plist`, which Tauri merges into the
|
|
62
|
+
app's Info.plist;
|
|
63
|
+
4. prepare the Tauri dependency of the plugin's Swift package. On an Apple silicon Mac, use
|
|
64
|
+
`--target aarch64-apple-ios-sim`.
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
LA=$(cargo metadata --format-version 1 --manifest-path src-tauri/Cargo.toml | jq -r '.packages[] | select(.name == "tauri-plugin-live-activity") | .manifest_path | rtrimstr("Cargo.toml")')
|
|
68
|
+
mkdir -p src-tauri/gen/apple/LiveActivityWidget && cp "$LA"templates/ios/* src-tauri/gen/apple/LiveActivityWidget/
|
|
69
|
+
/usr/libexec/PlistBuddy -c 'Add :NSSupportsLiveActivities bool true' src-tauri/Info.ios.plist
|
|
70
|
+
IPHONEOS_DEPLOYMENT_TARGET=16.1 cargo check --manifest-path src-tauri/Cargo.toml --target x86_64-apple-ios
|
|
67
71
|
```
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
Tauri builds the Xcode project with XcodeGen, and a new target is a block of XcodeGen YAML, so
|
|
74
|
+
this step does not fit on one line. Paste the block, then the line after it:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
cat > src-tauri/gen/apple/live-activity.yml <<EOF
|
|
78
|
+
packages:
|
|
79
|
+
LiveActivity: { path: "${LA}ios" }
|
|
80
|
+
targets:
|
|
81
|
+
$(sed -n 's/^name: //p' src-tauri/gen/apple/project.yml)_iOS:
|
|
82
|
+
sources: [LiveActivityWidget/LiveActivityActionIntent.swift]
|
|
83
|
+
dependencies: [{ target: LiveActivityWidget }]
|
|
84
|
+
settings: { base: { SWIFT_VERSION: "5.0" } }
|
|
85
|
+
LiveActivityWidget:
|
|
86
|
+
type: app-extension
|
|
87
|
+
platform: iOS
|
|
88
|
+
deploymentTarget: "16.1"
|
|
89
|
+
sources: [LiveActivityWidget]
|
|
90
|
+
dependencies: [{ package: LiveActivity, product: LiveActivityShared }, { package: LiveActivity, product: LiveActivityUI }]
|
|
91
|
+
info: { path: LiveActivityWidget/Info.plist, properties: { NSExtension: { NSExtensionPointIdentifier: com.apple.widgetkit-extension }, TauriLiveActivityLayout: 2 } }
|
|
92
|
+
settings: { base: { PRODUCT_BUNDLE_IDENTIFIER: $(jq -r .identifier src-tauri/tauri.conf.json).widget, SWIFT_VERSION: "5.0" } }
|
|
93
|
+
EOF
|
|
94
|
+
printf '\ninclude: [live-activity.yml]\n' >> src-tauri/gen/apple/project.yml && xcodegen generate --spec src-tauri/gen/apple/project.yml
|
|
95
|
+
```
|
|
71
96
|
|
|
72
|
-
|
|
97
|
+
The intent file goes into both targets: the widget builds its buttons from it, and the system
|
|
98
|
+
runs it in the app. `tauri ios init` writes `project.yml` again; run the last line after it.
|
|
99
|
+
The extension gets version 1.0 (1); give it the app's version before an App Store upload.
|
|
100
|
+
|
|
101
|
+
### Android
|
|
102
|
+
|
|
103
|
+
The plugin's manifest adds the notification permissions and its receivers. Builds need Android
|
|
104
|
+
SDK 37. The minimum is Android 8 (API 26); a new Tauri app starts at 24. The first line sets it
|
|
105
|
+
for `tauri android init`, the second for a project generated before:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
node -e 'f="src-tauri/tauri.conf.json";c=require("./"+f);(c.bundle.android??={}).minSdkVersion=26;require("fs").writeFileSync(f,JSON.stringify(c,null,2)+"\n")'
|
|
109
|
+
perl -pi -e 's/minSdk = 2[0-5]$/minSdk = 26/' src-tauri/gen/android/app/build.gradle.kts
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
The plugin shows state that the app sends. The app still owns the task, background work and
|
|
115
|
+
how often it updates. OS permissions, Focus and promotion rules decide where the activity
|
|
116
|
+
appears. `fallback: 'none'` rejects an unavailable native backend; it cannot force Android to
|
|
117
|
+
promote a notification.
|
|
118
|
+
|
|
119
|
+
Captures: iOS 26.3 on the iPhone 17 simulator, Android 16 on an emulator. The demo adds
|
|
120
|
+
Mochi's colors and padding to the code shown here.
|
|
121
|
+
|
|
122
|
+
### Progress
|
|
73
123
|
|
|
74
124
|
```ts
|
|
75
|
-
import {
|
|
76
|
-
getCapabilities, requestPermissions, start, update, end,
|
|
77
|
-
} from 'tauri-plugin-live-activity';
|
|
125
|
+
import { getCapabilities, requestPermissions, start, update, end } from 'tauri-plugin-live-activity';
|
|
78
126
|
|
|
79
127
|
const { platform } = await getCapabilities();
|
|
80
128
|
await requestPermissions(platform === 'android' ? 'notifications' : 'liveActivities');
|
|
@@ -84,55 +132,454 @@ const snack = await start({
|
|
|
84
132
|
schemaVersion: 1,
|
|
85
133
|
template: 'progress',
|
|
86
134
|
fallback: 'notification',
|
|
87
|
-
snapshot: {
|
|
88
|
-
revision: 0,
|
|
89
|
-
title: 'Mochi’s snack delivery',
|
|
90
|
-
status: 'Packing a tiny treat',
|
|
91
|
-
progress: 0.15,
|
|
92
|
-
},
|
|
135
|
+
snapshot: { revision: 0, title: 'Mochi’s snack delivery', status: 'Packing a tiny treat', progress: 0.15 },
|
|
93
136
|
});
|
|
94
|
-
|
|
95
137
|
await update({
|
|
96
138
|
id: snack.id,
|
|
139
|
+
snapshot: { revision: 1, title: 'Mochi’s snack delivery', status: 'On the way to Mochi', progress: 0.65 },
|
|
140
|
+
});
|
|
141
|
+
await end({ id: snack.id, policy: { kind: 'immediate' } });
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Each update sends a complete snapshot with a higher revision. `list()` finds the activities
|
|
145
|
+
again after a restart.
|
|
146
|
+
|
|
147
|
+
<table>
|
|
148
|
+
<tr><th>iOS 26.3 simulator</th><th>Android 16 emulator</th></tr>
|
|
149
|
+
<tr>
|
|
150
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-v1-progress.png" width="400" alt="Mochi's snack delivery at 65% on the iOS Lock Screen"></td>
|
|
151
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-v1-progress.png" width="400" alt="Mochi's snack delivery with a progress bar in the Android notification shade"></td>
|
|
152
|
+
</tr>
|
|
153
|
+
</table>
|
|
154
|
+
|
|
155
|
+
On iOS the cat comes from the demo's own widget. The plugin's template shows the same title,
|
|
156
|
+
status and bar.
|
|
157
|
+
|
|
158
|
+
### Timer
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { start } from 'tauri-plugin-live-activity';
|
|
162
|
+
|
|
163
|
+
const now = Date.now();
|
|
164
|
+
await start({
|
|
165
|
+
activityKey: 'mochi:nap',
|
|
166
|
+
schemaVersion: 1,
|
|
167
|
+
template: 'timer',
|
|
168
|
+
fallback: 'notification',
|
|
97
169
|
snapshot: {
|
|
98
|
-
revision:
|
|
99
|
-
|
|
100
|
-
status: 'On the way to Mochi',
|
|
101
|
-
progress: 0.65,
|
|
170
|
+
revision: 0, title: 'Mochi’s nap', status: 'A little snooze, a little peace',
|
|
171
|
+
timer: { startedAt: now, endsAt: now + 5 * 60_000 },
|
|
102
172
|
},
|
|
103
173
|
});
|
|
174
|
+
```
|
|
104
175
|
|
|
105
|
-
|
|
106
|
-
|
|
176
|
+
Times are Unix milliseconds. The system counts down; the app sends nothing per second.
|
|
177
|
+
|
|
178
|
+
<table>
|
|
179
|
+
<tr><th>iOS 26.3 simulator</th><th>Android 16 emulator</th></tr>
|
|
180
|
+
<tr>
|
|
181
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-v1-timer.png" width="400" alt="Mochi's nap counting down from 4:48 on the iOS Lock Screen"></td>
|
|
182
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-v1-timer.png" width="400" alt="Mochi's nap with the countdown in the Android notification header"></td>
|
|
183
|
+
</tr>
|
|
184
|
+
</table>
|
|
185
|
+
|
|
186
|
+
## Layouts
|
|
187
|
+
|
|
188
|
+
With a layout the app designs the activity itself: a tree of nodes, a table of styles and a
|
|
189
|
+
map of values. On iOS the plugin's widget template draws it, with no SwiftUI in the app.
|
|
190
|
+
Android shows part of it as a notification. The tree is fixed at start; an update sends new
|
|
191
|
+
values with the same keys and types. Layout, styles and values share ActivityKit's 4 KiB
|
|
192
|
+
limit.
|
|
193
|
+
|
|
194
|
+
### Text, values and styles
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { layout, startLayout, updateLayout, endLayout } from 'tauri-plugin-live-activity';
|
|
198
|
+
|
|
199
|
+
const { vstack, bound, text } = layout;
|
|
200
|
+
|
|
201
|
+
const card = await startLayout({
|
|
202
|
+
activityKey: 'mochi:text',
|
|
203
|
+
schemaVersion: 2,
|
|
204
|
+
fallback: 'notification',
|
|
205
|
+
layout: {
|
|
206
|
+
lockScreen: vstack([
|
|
207
|
+
bound('title', { style: 'title' }),
|
|
208
|
+
bound('status', { style: 'status', unless: 'arrived' }),
|
|
209
|
+
text('Snack time! Made it home', { style: 'done', if: 'arrived' }),
|
|
210
|
+
], { alignment: 'leading' }),
|
|
211
|
+
},
|
|
212
|
+
styles: {
|
|
213
|
+
title: { font: 'headline' },
|
|
214
|
+
status: { font: 'subheadline', color: 'secondary' },
|
|
215
|
+
done: { font: 'subheadline', weight: 'bold', color: '#C9654D' },
|
|
216
|
+
},
|
|
217
|
+
snapshot: { revision: 0, values: { title: 'Mochi’s snack', status: 'Packing a tiny treat', arrived: false } },
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await updateLayout({
|
|
221
|
+
id: card.id,
|
|
222
|
+
snapshot: { revision: 1, values: { title: 'Mochi’s snack', status: 'Packing a tiny treat', arrived: true } },
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await endLayout({ id: card.id, policy: { kind: 'immediate' } });
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`bound` shows a value, `text` a literal. `if` shows a node only while a value is truthy,
|
|
229
|
+
`unless` only while it is not. The examples below show only the members after `fallback`; the
|
|
230
|
+
builders are all in `layout`.
|
|
231
|
+
|
|
232
|
+
<table>
|
|
233
|
+
<tr><th></th><th>iOS 26.3 simulator</th><th>Android 16 emulator</th></tr>
|
|
234
|
+
<tr>
|
|
235
|
+
<td><code>arrived: false</code></td>
|
|
236
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-text.png" width="380" alt="Title and status text on the iOS Lock Screen"></td>
|
|
237
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-layout-text.png" width="380" alt="The same title and status as an Android notification"></td>
|
|
238
|
+
</tr>
|
|
239
|
+
<tr>
|
|
240
|
+
<td><code>arrived: true</code></td>
|
|
241
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-text-arrived.png" width="380" alt="The status replaced by Snack time in bold coral on iOS"></td>
|
|
242
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-layout-text-arrived.png" width="380" alt="The body replaced by Snack time on Android"></td>
|
|
243
|
+
</tr>
|
|
244
|
+
</table>
|
|
245
|
+
|
|
246
|
+
Android drops styles: the first text is the title and the second the body.
|
|
247
|
+
|
|
248
|
+
### SF Symbols
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
layout: {
|
|
252
|
+
lockScreen: hstack([
|
|
253
|
+
symbol('cat.fill'),
|
|
254
|
+
symbol('heart.fill', { rendering: 'multicolor' }),
|
|
255
|
+
symbol('speaker.wave.3.fill', { variableBind: 'purr' }),
|
|
256
|
+
bound('title'),
|
|
257
|
+
], { spacing: 12 }),
|
|
258
|
+
},
|
|
259
|
+
snapshot: { revision: 0, values: { title: 'Purr meter', purr: 0.35 } },
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`variableBind` fills a variable symbol from a number between 0 and 1.
|
|
263
|
+
|
|
264
|
+
| iOS 26.3 simulator | Android |
|
|
265
|
+
| --- | --- |
|
|
266
|
+
| <img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-symbols.png" width="400" alt="A cat, a multicolor heart and a speaker filled to 35% on the iOS Lock Screen"> | Not shown. Listed as `layout.symbol` in `degradedFeatures`. |
|
|
267
|
+
|
|
268
|
+
### Image
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
layout: {
|
|
272
|
+
lockScreen: hstack([image('mochi', { resizable: true, style: 'portrait' }), bound('title')], { spacing: 12 }),
|
|
273
|
+
},
|
|
274
|
+
styles: { portrait: { frame: { width: 56, height: 56 } } },
|
|
275
|
+
snapshot: { revision: 0, values: { title: 'Mochi’s portrait' } },
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`image` names a picture in the widget extension's asset catalog. Put an `Assets.xcassets`
|
|
279
|
+
folder in `src-tauri/gen/apple/LiveActivityWidget` and run
|
|
280
|
+
`xcodegen generate --spec src-tauri/gen/apple/project.yml`; the widget target picks it up. The
|
|
281
|
+
demo's `mochi` is a PNG drawn from an SVG file.
|
|
282
|
+
|
|
283
|
+
| iOS 26.3 simulator | Android |
|
|
284
|
+
| --- | --- |
|
|
285
|
+
| <img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-image.png" width="400" alt="Mochi's portrait from the widget's asset catalog on the iOS Lock Screen"> | Not shown. Listed as `layout.image`. |
|
|
286
|
+
|
|
287
|
+
### Paths (SVG path data)
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
const head = 'M14 40C12 32 11 22 14 14Q15 11 18 13L35 27Q50 23 65 27L82 13Q85 11 86 14C89 22 88 32 86 40C93 48 94 61 87 71C79 81 66 85 50 85C34 85 21 81 13 71C6 61 7 48 14 40Z';
|
|
291
|
+
layout: {
|
|
292
|
+
lockScreen: zstack([
|
|
293
|
+
path(head, [100, 90], { style: 'fur' }),
|
|
294
|
+
path(head, [100, 90], { stroke: 1.5 }),
|
|
295
|
+
path('M28 54Q33 58 38 54M62 54Q67 58 72 54', [100, 90], { stroke: 2 }),
|
|
296
|
+
path('M48 64Q50 62 52 64Q50 66 48 64Z', [100, 90], { style: 'nose' }),
|
|
297
|
+
], { style: 'face' }),
|
|
298
|
+
},
|
|
299
|
+
styles: { face: { frame: { width: 60, height: 54 } }, fur: { color: '#F6E9CD' }, nose: { color: '#C9654D' } },
|
|
300
|
+
snapshot: { revision: 0, values: {} },
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
`path` draws the `d` of an SVG `<path>` in a `box` of `[width, height]`, scaled to fit. It is
|
|
304
|
+
filled, or stroked when `stroke` gives a line width. `d` takes `M L H V C S Q T Z`, up to
|
|
305
|
+
512 bytes. Arcs (`A`) are not supported; convert them to curves first.
|
|
306
|
+
|
|
307
|
+
| iOS 26.3 simulator | Android |
|
|
308
|
+
| --- | --- |
|
|
309
|
+
| <img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-paths.png" width="400" alt="Mochi's face drawn from four paths on the iOS Lock Screen"> | Not shown. Listed as `layout.path`. |
|
|
310
|
+
|
|
311
|
+
### Gauge
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
layout: {
|
|
315
|
+
lockScreen: hstack([gauge('bowl', { max: 100, kind: 'circular' }), gauge('bowl', { max: 100 })], { spacing: 12 }),
|
|
316
|
+
},
|
|
317
|
+
snapshot: { revision: 0, values: { bowl: 80 } },
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
A gauge shows a number between `min` and `max` (0 and 1 when left out), as a ring or a bar.
|
|
321
|
+
|
|
322
|
+
| iOS 26.3 simulator | Android |
|
|
323
|
+
| --- | --- |
|
|
324
|
+
| <img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-gauge.png" width="400" alt="A circular and a linear gauge at 80 of 100 on the iOS Lock Screen"> | Not shown. Listed as `layout.gauge`. |
|
|
325
|
+
|
|
326
|
+
### Progress
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
layout: {
|
|
330
|
+
lockScreen: vstack([bound('title'), progress('done', { kind: 'circular' }), progress('done')]),
|
|
331
|
+
},
|
|
332
|
+
snapshot: { revision: 0, values: { title: 'Mochi’s snack delivery', done: 0.65 } },
|
|
107
333
|
```
|
|
108
334
|
|
|
109
|
-
|
|
110
|
-
reconciles activities after an app restart. A timer uses `template: 'timer'` and
|
|
111
|
-
`timer: { startedAt, endsAt }` instead of `progress`; times are Unix milliseconds.
|
|
335
|
+
A progress view shows a number from 0 to 1 as a bar or a ring.
|
|
112
336
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
337
|
+
<table>
|
|
338
|
+
<tr><th>iOS 26.3 simulator</th><th>Android 16 emulator</th></tr>
|
|
339
|
+
<tr>
|
|
340
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-progress.png" width="400" alt="A ring and a bar at 65% on the iOS Lock Screen"></td>
|
|
341
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-layout-progress.png" width="400" alt="The first progress view as the bar of an Android notification"></td>
|
|
342
|
+
</tr>
|
|
343
|
+
</table>
|
|
117
344
|
|
|
118
|
-
|
|
345
|
+
Android turns the first progress view into the notification's bar.
|
|
346
|
+
|
|
347
|
+
### Timer
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
layout: {
|
|
351
|
+
lockScreen: vstack([bound('title'), timerText('start', 'end')]),
|
|
352
|
+
},
|
|
353
|
+
snapshot: { revision: 0, values: { title: 'Mochi’s nap', start: Date.now(), end: Date.now() + 5 * 60_000 } },
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`timerText` counts down to the `end` value, or up from `start` with `countsDown: false`.
|
|
357
|
+
|
|
358
|
+
<table>
|
|
359
|
+
<tr><th>iOS 26.3 simulator</th><th>Android 16 emulator</th></tr>
|
|
360
|
+
<tr>
|
|
361
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-timer.png" width="400" alt="A countdown at 4:50 under Mochi's nap on the iOS Lock Screen"></td>
|
|
362
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-layout-timer.png" width="400" alt="The countdown as the chronometer in the Android notification header"></td>
|
|
363
|
+
</tr>
|
|
364
|
+
</table>
|
|
365
|
+
|
|
366
|
+
Android shows the first timer as the notification's chronometer.
|
|
367
|
+
|
|
368
|
+
### Stacks
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
layout: {
|
|
372
|
+
lockScreen: vstack([
|
|
373
|
+
hstack([zstack([symbol('circle.fill'), symbol('pawprint.fill', { style: 'mark' })]), bound('title'), spacer(), bound('eta')]),
|
|
374
|
+
divider(),
|
|
375
|
+
hstack([bound('from'), spacer(), bound('to')]),
|
|
376
|
+
], { spacing: 10 }),
|
|
377
|
+
},
|
|
378
|
+
styles: { mark: { size: 13, color: 'white' } },
|
|
379
|
+
snapshot: { revision: 0, values: { title: 'Snack route', eta: '4 min', from: 'Kitchen', to: 'Mochi’s bowl' } },
|
|
380
|
+
```
|
|
119
381
|
|
|
120
|
-
|
|
121
|
-
|
|
382
|
+
`vstack`, `hstack` and `zstack` put nodes in a column, in a row or on top of each other.
|
|
383
|
+
`spacer` takes the free space and `divider` draws a line.
|
|
384
|
+
|
|
385
|
+
| iOS 26.3 simulator | Android |
|
|
386
|
+
| --- | --- |
|
|
387
|
+
| <img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-stacks.png" width="400" alt="A paw badge, title and time in a row, a line, and From and To below on the iOS Lock Screen"> | Only the first two texts, as the title and the body. Not captured. |
|
|
388
|
+
|
|
389
|
+
### Buttons
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
layout: {
|
|
393
|
+
lockScreen: vstack([
|
|
394
|
+
bound('title'),
|
|
395
|
+
bound('status'),
|
|
396
|
+
hstack([button('pet', text('Pet Mochi')), button('done', text('All done'))], { spacing: 8 }),
|
|
397
|
+
], { alignment: 'leading' }),
|
|
398
|
+
},
|
|
399
|
+
snapshot: { revision: 0, values: { title: 'Mochi wants attention', status: 'No pets yet', pets: 0 } },
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
A tap reports the button's action ID. It does not change the activity: update or end it in a
|
|
403
|
+
handler. `onAction` hears taps while the web view runs:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import { endLayout, onAction } from 'tauri-plugin-live-activity';
|
|
407
|
+
|
|
408
|
+
await onAction(async ({ actionId, activityId }) => {
|
|
409
|
+
if (actionId === 'done') await endLayout({ id: activityId, policy: { kind: 'immediate' } });
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
The system can start the app in the background for a tap, and Tauri may then create no web
|
|
414
|
+
view. Taps that must work then belong in Rust. Register the handler on the built `App`
|
|
415
|
+
before `run`:
|
|
416
|
+
|
|
417
|
+
```rust
|
|
418
|
+
use tauri_plugin_live_activity::{EndInput, EndPolicy, LiveActivityExt};
|
|
419
|
+
|
|
420
|
+
let app = tauri::Builder::default()
|
|
421
|
+
.plugin(tauri_plugin_live_activity::init())
|
|
422
|
+
.build(tauri::generate_context!())
|
|
423
|
+
.expect("error while building tauri application");
|
|
424
|
+
let handle = app.handle().clone();
|
|
425
|
+
app.live_activity().on_action(move |event| {
|
|
426
|
+
if event.action_id == "done" {
|
|
427
|
+
let input = EndInput { id: event.activity_id, policy: EndPolicy::Immediate {} };
|
|
428
|
+
let _ = handle.live_activity().end_layout(input);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
app.run(|_, _| {});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Rust handlers run first, on a plugin thread, and may call the plugin. `onAction` needs
|
|
435
|
+
`core:event:default`, which a new app has.
|
|
436
|
+
|
|
437
|
+
<table>
|
|
438
|
+
<tr><th></th><th>iOS 26.3 simulator</th><th>Android 16 emulator</th></tr>
|
|
439
|
+
<tr>
|
|
440
|
+
<td>Start</td>
|
|
441
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-buttons-before.png" width="380" alt="Pet Mochi and All done buttons on the iOS Lock Screen"></td>
|
|
442
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-layout-buttons-before.png" width="380" alt="Pet Mochi and All done as Android notification actions"></td>
|
|
443
|
+
</tr>
|
|
444
|
+
<tr>
|
|
445
|
+
<td>After taps on Pet Mochi</td>
|
|
446
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-layout-buttons-after.png" width="380" alt="The status reads Purring. 1 pet so far after one tap on iOS"></td>
|
|
447
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-layout-buttons-after.png" width="380" alt="The body reads Purring. 2 pets so far after two taps on Android"></td>
|
|
448
|
+
</tr>
|
|
449
|
+
</table>
|
|
450
|
+
|
|
451
|
+
The demo's Rust handler counts the pets and updates the status. Buttons need iOS 17
|
|
452
|
+
(`getCapabilities()` reports `actions`). Android shows up to three buttons as notification
|
|
453
|
+
actions. On Android, a tap that starts a killed app waits in memory and is lost if the Tauri
|
|
454
|
+
runtime does not start.
|
|
455
|
+
|
|
456
|
+
### Dynamic Island
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
layout: {
|
|
460
|
+
lockScreen: vstack([bound('title'), bound('status'), progress('done')]),
|
|
461
|
+
island: {
|
|
462
|
+
expanded: {
|
|
463
|
+
leading: image('mochi', { resizable: true, style: 'portrait' }),
|
|
464
|
+
center: bound('title'),
|
|
465
|
+
trailing: bound('eta'),
|
|
466
|
+
bottom: vstack([bound('status'), progress('done')]),
|
|
467
|
+
},
|
|
468
|
+
compactLeading: symbol('pawprint.fill'),
|
|
469
|
+
compactTrailing: bound('eta'),
|
|
470
|
+
minimal: progress('done', { kind: 'circular' }),
|
|
471
|
+
},
|
|
472
|
+
keyline: '#A3D9C2',
|
|
473
|
+
},
|
|
474
|
+
styles: { portrait: { frame: { width: 44, height: 44 } } },
|
|
475
|
+
snapshot: { revision: 0, values: { title: 'Mochi’s snack', status: 'Packing a tiny treat', done: 0.15, eta: '9 min' } },
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
`island` gives each part its own nodes. A part left out gets a default: the expanded view
|
|
479
|
+
shows the lock screen tree, compact leading and minimal show its first symbol. `keyline` tints
|
|
480
|
+
the island's edge.
|
|
481
|
+
|
|
482
|
+
<table>
|
|
483
|
+
<tr><th>Compact</th><th>Minimal</th><th>Expanded</th></tr>
|
|
484
|
+
<tr>
|
|
485
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-island-compact.png" width="270" alt="A paw on the left and 9 min on the right of the compact Dynamic Island"></td>
|
|
486
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-island-minimal.png" width="180" alt="A progress ring in the minimal Dynamic Island"></td>
|
|
487
|
+
<td><img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-island-expanded.png" width="265" alt="Mochi's portrait, title, time, status and bar in the expanded Dynamic Island"></td>
|
|
488
|
+
</tr>
|
|
489
|
+
</table>
|
|
490
|
+
|
|
491
|
+
iOS 26.3 on the iPhone 17 simulator. Android has no Dynamic Island and lists `layout.island`.
|
|
492
|
+
|
|
493
|
+
### Android
|
|
494
|
+
|
|
495
|
+
A notification cannot draw a layout. Android takes these parts of the lock screen tree, in
|
|
496
|
+
order, with the current values:
|
|
497
|
+
|
|
498
|
+
| Layout | Notification |
|
|
499
|
+
| --- | --- |
|
|
500
|
+
| first `text` | title |
|
|
501
|
+
| second `text` | body |
|
|
502
|
+
| first `progress` | progress bar |
|
|
503
|
+
| first `timerText` | chronometer |
|
|
504
|
+
| up to three `button`s | actions, labelled with their first text |
|
|
505
|
+
| `android.visibility` | lock screen visibility: `public`, `private` (default) or `secret` |
|
|
506
|
+
|
|
507
|
+
Everything else is listed in `degradedFeatures`, here `layout.symbol` and `layout.styles`:
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
layout: {
|
|
511
|
+
lockScreen: vstack([
|
|
512
|
+
hstack([symbol('moon.zzz.fill'), bound('title', { style: 'title' })], { spacing: 8 }),
|
|
513
|
+
bound('status'),
|
|
514
|
+
progress('sleep'),
|
|
515
|
+
button('wake', text('Wake Mochi')),
|
|
516
|
+
], { alignment: 'leading' }),
|
|
517
|
+
},
|
|
518
|
+
styles: { title: { font: 'headline' } },
|
|
519
|
+
snapshot: { revision: 0, values: { title: 'Mochi’s nap', status: 'Dozing off', sleep: 0.2 } },
|
|
520
|
+
android: { visibility: 'public' },
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
| iOS | Android 16 emulator |
|
|
524
|
+
| --- | --- |
|
|
525
|
+
| Drawn as written, like the examples above. | <img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/android-layout-android.png" width="400" alt="Mochi's nap at 60% with a Wake Mochi action in the Android notification shade"> |
|
|
526
|
+
|
|
527
|
+
The capture is after an update to `Fast asleep` and `0.6`. On Android 16 and later the plugin
|
|
528
|
+
requests promotion to a Live Update where the app may post one; `promotionState` reports what
|
|
529
|
+
Android did.
|
|
530
|
+
|
|
531
|
+
### Samsung Now Bar
|
|
532
|
+
|
|
533
|
+
One UI shows a promoted notification in the Now Bar only for apps on Samsung's allow list, or
|
|
534
|
+
for every app while the user has the developer option "Live notifications for all apps" on.
|
|
535
|
+
Samsung adds apps to the list privately; no public way to apply was found. To show the title
|
|
536
|
+
and the body there on Android 17 and later, turn on the plugin's One UI extras:
|
|
537
|
+
|
|
538
|
+
```sh
|
|
539
|
+
node -e 'f="src-tauri/tauri.conf.json";c=require("./"+f);((c.plugins??={})["live-activity"]??={}).android={samsungNowBar:true};require("fs").writeFileSync(f,JSON.stringify(c,null,2)+"\n")'
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
| Galaxy S23, One UI 9 (Android 17), locked |
|
|
543
|
+
| --- |
|
|
544
|
+
| <img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/samsung-now-bar.png" width="400" alt="Mochi's snack delivery and On the way to Mochi in the One UI Now Bar"> |
|
|
545
|
+
|
|
546
|
+
This is the demo's Now Bar example (title, status and a progress bar, with
|
|
547
|
+
`android: { visibility: 'public' }`) after one update, with the developer option on. The
|
|
548
|
+
extras do not put the app on the allow list. An app that Samsung lists without its `RON` flag
|
|
549
|
+
also needs this manifest entry, which the plugin does not add:
|
|
550
|
+
|
|
551
|
+
```sh
|
|
552
|
+
f=src-tauri/gen/android/app/src/main/AndroidManifest.xml; grep -q ongoing_activity $f || perl -0pi -e 's|<application\b[^>]*>|$&\n <meta-data android:name="com.samsung.android.support.ongoing_activity" android:value="true" />|' $f
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
## Plugin configuration
|
|
556
|
+
|
|
557
|
+
`plugins.live-activity` in `tauri.conf.json` is optional:
|
|
558
|
+
|
|
559
|
+
```json
|
|
560
|
+
{ "plugins": { "live-activity": { "receipts": "redacted", "android": { "samsungNowBar": true } } } }
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
- `receipts`: iOS stores a receipt of each accepted request, so that a late ActivityKit read
|
|
564
|
+
cannot bring back an older revision, also after a restart. `full`, the default, keeps the
|
|
565
|
+
snapshot and its text. `redacted` keeps only a SHA-256 digest of it; a digest hides only
|
|
566
|
+
values that cannot be guessed. Android ignores the option for now and stores records in
|
|
567
|
+
full in app-private preferences.
|
|
568
|
+
- `android.samsungNowBar`: the [Samsung Now Bar](#samsung-now-bar) extras; off when absent.
|
|
569
|
+
|
|
570
|
+
## Try the demo
|
|
571
|
+
|
|
572
|
+
<img src="https://raw.githubusercontent.com/1eedaegon/tauri-plugin-live-activity/main/docs/images/ios-demo-picker.png" width="300" alt="The demo's Layouts tab with one chip per example">
|
|
573
|
+
|
|
574
|
+
The demo's Layouts tab starts each layout example of this page. From the repository root,
|
|
575
|
+
with the mobile toolchain configured:
|
|
122
576
|
|
|
123
577
|
```sh
|
|
124
|
-
npm ci
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
npm run tauri -- android dev
|
|
130
|
-
# or: npm run tauri -- ios dev
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
See [validation results](https://github.com/1eedaegon/tauri-plugin-live-activity/blob/main/docs/validation.md)
|
|
134
|
-
and [OS coverage](https://github.com/1eedaegon/tauri-plugin-live-activity/blob/main/docs/platform-coverage.md)
|
|
135
|
-
for tested versions and remaining device checks.
|
|
578
|
+
npm ci && npm run build:demo && cd examples/demo && npm run tauri -- ios dev
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Use `android dev` for Android. The [docs](https://github.com/1eedaegon/tauri-plugin-live-activity/tree/main/docs)
|
|
582
|
+
hold the request contract, the setup details and the test records.
|
|
136
583
|
|
|
137
584
|
## License
|
|
138
585
|
|