@timmo001/groundwork-mobile 0.13.0 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-mobile",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "The page side of Groundwork's Android shell: the app config, the typed bridge to the shell, and a fallback to the PWA outside it",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -33,8 +33,8 @@
33
33
  "format:check": "prettier --check ."
34
34
  },
35
35
  "dependencies": {
36
- "@timmo001/groundwork-pwa": "0.13.0",
37
- "@timmo001/groundwork-tokens": "0.13.0",
36
+ "@timmo001/groundwork-pwa": "0.14.0",
37
+ "@timmo001/groundwork-tokens": "0.14.0",
38
38
  "effect": "4.1.2"
39
39
  },
40
40
  "devDependencies": {
package/src/bridge.ts CHANGED
@@ -26,13 +26,43 @@ export const Notice = Schema.Struct({
26
26
 
27
27
  export interface Notice extends Schema.Schema.Type<typeof Notice> {}
28
28
 
29
- /** A share from another app, the same shape the PWA's share target gives. */
29
+ /**
30
+ * A share from another app, the same shape the PWA's share target gives,
31
+ * plus the id of the sharing shortcut it was sent to, in the shell.
32
+ */
30
33
  export const ShellShare = Schema.Struct({
31
34
  title: Schema.optionalKey(Schema.String),
32
35
  text: Schema.optionalKey(Schema.String),
33
36
  url: Schema.optionalKey(Schema.String),
37
+ shortcutId: Schema.optionalKey(Schema.String),
38
+ });
39
+
40
+ export interface ShellShare extends Schema.Schema.Type<typeof ShellShare> {}
41
+
42
+ /**
43
+ * A launcher shortcut that opens `url` as a deep link: on a long press of
44
+ * the app's icon, pinned to the home screen, and with `share` in the share
45
+ * sheet's direct row too.
46
+ */
47
+ export const Shortcut = Schema.Struct({
48
+ /** Stays the same across updates, so a pinned one keeps working. */
49
+ id: Schema.String,
50
+ label: Schema.String,
51
+ /** Shown where there's room, such as in the long-press menu. */
52
+ longLabel: Schema.optionalKey(Schema.String),
53
+ /** An allowed origin's page, or the app's own scheme. */
54
+ url: Schema.String,
55
+ /** An SVG path on a 24 unit grid, such as a Material Design Icon. */
56
+ icon: Schema.optionalKey(Schema.String),
57
+ /**
58
+ * A sharing shortcut, whose shares carry its id as `shortcutId`. It shows
59
+ * on a long press as well, so list it after the ones meant for that.
60
+ */
61
+ share: Schema.optionalKey(Schema.Boolean),
34
62
  });
35
63
 
64
+ export interface Shortcut extends Schema.Schema.Type<typeof Shortcut> {}
65
+
36
66
  export const DeepLink = Schema.Struct({ url: Schema.String });
37
67
 
38
68
  export interface DeepLink extends Schema.Schema.Type<typeof DeepLink> {}
@@ -145,6 +175,12 @@ export const mobileRequests = {
145
175
  discoverServices: Schema.Array(DiscoveredService),
146
176
  /** Scans a QR code with the camera: its text, or `null` when cancelled. */
147
177
  scanQr: Schema.NullOr(Schema.String),
178
+ /** Replaces the dynamic shortcuts: how many the launcher took. */
179
+ setShortcuts: Schema.Number,
180
+ /** Asks the launcher to pin a shortcut: false where it can't. */
181
+ pinShortcut: Schema.Boolean,
182
+ /** Removes shortcuts, greying out pinned ones. */
183
+ disableShortcuts: Schema.Boolean,
148
184
  } as const;
149
185
 
150
186
  /** The shell's messages, each with the schema its data is read with. */
package/src/view.ts CHANGED
@@ -23,7 +23,6 @@ import {
23
23
  unsubscribePush,
24
24
  type Pwa,
25
25
  type PwaConfig,
26
- type Share,
27
26
  } from "@timmo001/groundwork-pwa/view";
28
27
  import { Option, Schema } from "effect";
29
28
 
@@ -35,6 +34,7 @@ import {
35
34
  mobileRequests,
36
35
  Notice,
37
36
  ShellShare,
37
+ Shortcut,
38
38
  type BridgeMessage,
39
39
  type DiscoveredService,
40
40
  type DiscoveryMode,
@@ -77,8 +77,11 @@ export interface MobilePushOptions {
77
77
  export interface ConnectMobileOptions {
78
78
  /** The PWA config, for the floor's service worker, install and share target. */
79
79
  readonly pwa?: PwaConfig;
80
- /** Called with each share, from the shell or the share target. */
81
- readonly onShare?: (share: Share) => void;
80
+ /**
81
+ * Called with each share, from the shell or the share target, with the
82
+ * sharing shortcut's id when it was sent to one.
83
+ */
84
+ readonly onShare?: (share: ShellShare) => void;
82
85
  /** Called with each deep link the shell opens the app with. */
83
86
  readonly onDeepLink?: (url: string) => void;
84
87
  /**
@@ -146,6 +149,25 @@ export interface Mobile {
146
149
  readonly scanQr: (options?: {
147
150
  readonly hint?: string;
148
151
  }) => Promise<string | undefined>;
152
+ /**
153
+ * Replaces the dynamic shortcuts with these, in order, and answers how
154
+ * many the launcher took. Answers 0 outside the shell, where the PWA
155
+ * manifest's `shortcuts` give the long-press menu.
156
+ */
157
+ readonly setShortcuts: (shortcuts: readonly Shortcut[]) => Promise<number>;
158
+ /**
159
+ * Asks the launcher to pin a shortcut to the home screen. False where it
160
+ * can't, and outside the shell.
161
+ */
162
+ readonly pinShortcut: (shortcut: Shortcut) => Promise<boolean>;
163
+ /**
164
+ * Removes the shortcuts with these ids. Pinned ones stay, greyed out,
165
+ * showing `message` when tapped. Does nothing outside the shell.
166
+ */
167
+ readonly disableShortcuts: (
168
+ ids: readonly string[],
169
+ message?: string,
170
+ ) => Promise<void>;
149
171
  }
150
172
 
151
173
  interface Waiting {
@@ -171,13 +193,18 @@ const read = <S extends Schema.ConstraintDecoder<unknown>>(
171
193
 
172
194
  const noticeJson = Schema.encodeSync(Schema.toCodecJson(Notice));
173
195
 
196
+ const shortcutJson = Schema.encodeSync(Schema.toCodecJson(Shortcut));
197
+
198
+ /** The `disableShortcuts` request's params. */
199
+ type DisableParams = { ids: string[]; message?: string };
200
+
174
201
  const pushParams = ({ service, client, headers = {} }: MobilePushOptions) => ({
175
202
  service,
176
203
  client: { id: client.id, name: client.name, platform: client.platform },
177
204
  headers: { ...headers },
178
205
  });
179
206
 
180
- const dispatchShare = (share: Share) =>
207
+ const dispatchShare = (share: ShellShare) =>
181
208
  window.dispatchEvent(new CustomEvent("gw-share", { detail: share }));
182
209
 
183
210
  const dispatchDeepLink = (link: DeepLink) =>
@@ -240,6 +267,9 @@ const connectFloor = (
240
267
  scanQr: async () => {
241
268
  throw new Error("The QR scanner needs the Android shell");
242
269
  },
270
+ setShortcuts: async () => 0,
271
+ pinShortcut: async () => false,
272
+ disableShortcuts: async () => {},
243
273
  };
244
274
  };
245
275
 
@@ -395,5 +425,18 @@ export const connectMobile = async (
395
425
 
396
426
  return (await call("scanQr", mobileRequests.scanQr, params)) ?? undefined;
397
427
  },
428
+ setShortcuts: (shortcuts) =>
429
+ call("setShortcuts", mobileRequests.setShortcuts, {
430
+ shortcuts: shortcuts.map((shortcut) => shortcutJson(shortcut)),
431
+ }),
432
+ pinShortcut: (shortcut) =>
433
+ call("pinShortcut", mobileRequests.pinShortcut, shortcutJson(shortcut)),
434
+ disableShortcuts: async (ids, message) => {
435
+ const params: DisableParams = { ids: [...ids] };
436
+
437
+ if (message !== undefined) params["message"] = message;
438
+
439
+ await call("disableShortcuts", mobileRequests.disableShortcuts, params);
440
+ },
398
441
  };
399
442
  };