@glassly/engine 0.1.0-dev.31 → 0.1.0-dev.35

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.
@@ -1,9 +1,9 @@
1
1
  export const ENGINE_RELEASE_METADATA = Object.freeze({
2
2
  "schemaVersion": 1,
3
3
  "familyBaseVersion": "0.1.0",
4
- "releaseIdentity": "0.1.0-dev.31",
5
- "releaseSetId": "glassly-0.1.0-dev.31",
6
- "sourceCommit": "f344cf647ef3cd00444902f1154a7f5ad7a0fe52",
4
+ "releaseIdentity": "0.1.0-dev.35",
5
+ "releaseSetId": "glassly-0.1.0-dev.35",
6
+ "sourceCommit": "6909e863991061a55c153d51bd2c4384a0f4c0a3",
7
7
  "otaManifestUrl": null,
8
8
  "otaManifestSha256": null
9
9
  });
@@ -1 +1 @@
1
- {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,cAAc;IACjC,cAAc,EAAE,sBAAsB;IACtC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.1.0\",\n \"releaseIdentity\": \"0.1.0-dev.31\",\n \"releaseSetId\": \"glassly-0.1.0-dev.31\",\n \"sourceCommit\": \"f344cf647ef3cd00444902f1154a7f5ad7a0fe52\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
1
+ {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,cAAc;IACjC,cAAc,EAAE,sBAAsB;IACtC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.1.0\",\n \"releaseIdentity\": \"0.1.0-dev.35\",\n \"releaseSetId\": \"glassly-0.1.0-dev.35\",\n \"sourceCommit\": \"6909e863991061a55c153d51bd2c4384a0f4c0a3\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
@@ -4,7 +4,8 @@ declare class CustomDashboardService {
4
4
  private view;
5
5
  private mainIndex;
6
6
  private appsIndex;
7
- private calendarAvailable;
7
+ /** "loading" until the first fetch resolves; "no-access" only for a real permission denial. */
8
+ private calendarStatus;
8
9
  /** packageName → base64 icon bytes, or null when the icon can't be loaded (monogram fallback). */
9
10
  private iconCache;
10
11
  private iconLoads;
@@ -1 +1 @@
1
- {"version":3,"file":"CustomDashboardService.d.ts","sourceRoot":"","sources":["../../src/services/CustomDashboardService.ts"],"names":[],"mappings":"AAiEA,cAAM,sBAAsB;IAC1B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,iBAAiB,CAAQ;IACjC,kGAAkG;IAClG,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,aAAa,CAAsB;IAC3C,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAQ;IAExB,KAAK,IAAI,IAAI;IA+Cb;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAevB,IAAI,IAAI,IAAI;IAcZ,UAAU,IAAI,OAAO;IAYrB,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI;IAU5B,0DAA0D;IAC1D,MAAM,IAAI,OAAO;IAcjB,uEAAuE;IACvE,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAgB3C,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,iBAAiB;IAwBzB,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,OAAO;YAID,KAAK;YAQL,sBAAsB;YAOtB,SAAS;IAWvB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,yBAAyB;YASnB,wBAAwB;IAyBtC,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,YAAY;IA2FpB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IAiChB,OAAO,CAAC,eAAe;CAgFxB;AAED,QAAA,MAAM,sBAAsB,wBAA+B,CAAA;AAC3D,eAAe,sBAAsB,CAAA;AACrC,OAAO,EAAC,sBAAsB,EAAC,CAAA"}
1
+ {"version":3,"file":"CustomDashboardService.d.ts","sourceRoot":"","sources":["../../src/services/CustomDashboardService.ts"],"names":[],"mappings":"AAwEA,cAAM,sBAAsB;IAC1B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,SAAS,CAAI;IACrB,+FAA+F;IAC/F,OAAO,CAAC,cAAc,CAA4D;IAClF,kGAAkG;IAClG,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,aAAa,CAAsB;IAC3C,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAQ;IAExB,KAAK,IAAI,IAAI;IA+Cb;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAevB,IAAI,IAAI,IAAI;IAcZ,UAAU,IAAI,OAAO;IAYrB,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI;IAU5B,0DAA0D;IAC1D,MAAM,IAAI,OAAO;IAcjB,uEAAuE;IACvE,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAgB3C,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,iBAAiB;IAwBzB,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,OAAO;YAID,KAAK;YAQL,sBAAsB;YAOtB,SAAS;IAWvB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,yBAAyB;YASnB,wBAAwB;IAiCtC,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,YAAY;IA8FpB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IAiChB,OAAO,CAAC,eAAe;CAmFxB;AAED,QAAA,MAAM,sBAAsB,wBAA+B,CAAA;AAC3D,eAAe,sBAAsB,CAAA;AACrC,OAAO,EAAC,sBAAsB,EAAC,CAAA"}
@@ -12,13 +12,14 @@
12
12
  */
13
13
  import { File } from "expo-file-system";
14
14
  import { BluetoothSdk } from "@glassly/bluetooth-sdk";
15
+ import { MiniappErrorCode } from "@glassly/miniapp";
15
16
  import { useAppStatusStore } from "../stores/apps";
16
17
  import { useGlassesStore } from "../stores/glasses";
17
18
  import { SETTINGS, useSettingsStore } from "../stores/settings";
18
19
  import { BgTimer } from "../utils/timers";
19
20
  import { appMenuActions, dashboardActions, formatDashboardClock, isCustomDashboardEligible, normalizeDashboardWidgets, orderedDashboardApps, } from "./CustomDashboardModel";
20
21
  import { isGlassesConnected } from "./GlassesReadiness";
21
- import { listPhoneCalendarEvents } from "./PhoneCalendarService";
22
+ import { listPhoneCalendarEvents, PhoneCalendarError } from "./PhoneCalendarService";
22
23
  import sceneRenderer from "./SceneRenderer";
23
24
  const DASHBOARD_APP_ID = "system.custom-dashboard";
24
25
  const CLOCK_REFRESH_MS = 30_000;
@@ -32,11 +33,18 @@ const WIDGET_GAP = 10;
32
33
  /** Row height for the glasses' builtin 20px font engine. */
33
34
  const LINE_H = 24;
34
35
  const BUTTON_H = 34;
35
- /** App-menu icon grid: 4 per page (G2 firmware image-container pool). */
36
- const APPS_PER_PAGE = 4;
37
- const APP_CELL = 72;
38
- const APP_CELL_GAP = 16;
39
- const APP_ICON_INSET = 4;
36
+ /**
37
+ * App-menu icon grid: 2 rows × 4 columns per page. CFW rasterizes scenes
38
+ * phone-side, so the stock firmware's 4-image container pool does not bound
39
+ * the page (getEffectiveCapabilities lifts the element caps while the CFW
40
+ * canvas is active).
41
+ */
42
+ const APP_GRID_COLS = 4;
43
+ const APP_GRID_ROWS = 2;
44
+ const APPS_PER_PAGE = APP_GRID_COLS * APP_GRID_ROWS;
45
+ const APP_CELL = 36;
46
+ const APP_CELL_GAP = 12;
47
+ const APP_ICON_INSET = 2;
40
48
  const APP_ICON = APP_CELL - APP_ICON_INSET * 2;
41
49
  function eventLine(event) {
42
50
  const when = event.allDay
@@ -50,7 +58,8 @@ class CustomDashboardService {
50
58
  view = "main";
51
59
  mainIndex = 0;
52
60
  appsIndex = 0;
53
- calendarAvailable = false;
61
+ /** "loading" until the first fetch resolves; "no-access" only for a real permission denial. */
62
+ calendarStatus = "loading";
54
63
  /** packageName → base64 icon bytes, or null when the icon can't be loaded (monogram fallback). */
55
64
  iconCache = new Map();
56
65
  iconLoads = new Set();
@@ -295,7 +304,7 @@ class CustomDashboardService {
295
304
  }
296
305
  async refreshCalendarAndRender() {
297
306
  if (!this.calendarWidgetEnabled()) {
298
- this.calendarAvailable = false;
307
+ this.calendarStatus = "loading";
299
308
  this.calendarEvents = [];
300
309
  this.render();
301
310
  return;
@@ -306,15 +315,24 @@ class CustomDashboardService {
306
315
  const startsAt = new Date();
307
316
  const endsAt = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000);
308
317
  try {
309
- const result = await listPhoneCalendarEvents({ startsAt, endsAt, limit: Math.max(1, Math.min(3, count)) });
310
- this.calendarAvailable = true;
318
+ const result = await listPhoneCalendarEvents({
319
+ startsAt: startsAt.toISOString(),
320
+ endsAt: endsAt.toISOString(),
321
+ limit: Math.max(1, Math.min(3, count)),
322
+ });
323
+ this.calendarStatus = "ok";
311
324
  this.calendarEvents = result.events;
312
325
  }
313
- catch {
326
+ catch (error) {
314
327
  // Permission is requested from the Custom Dashboard settings screen;
315
- // without access the widget silently stays off the glasses.
316
- this.calendarAvailable = false;
328
+ // only a real denial should read as one — any other failure is
329
+ // "Unavailable" (and logged) so it can't masquerade as a permission
330
+ // problem.
331
+ const denied = error instanceof PhoneCalendarError && error.code === MiniappErrorCode.PERMISSION_DENIED;
332
+ this.calendarStatus = denied ? "no-access" : "unavailable";
317
333
  this.calendarEvents = [];
334
+ if (!denied)
335
+ console.warn("CustomDashboard: calendar fetch failed", error);
318
336
  }
319
337
  this.render();
320
338
  }
@@ -379,16 +397,18 @@ class CustomDashboardService {
379
397
  }
380
398
  else if (widget.id === "calendar") {
381
399
  // Right half, top-aligned, independent of the left stack. A missing
382
- // calendar (permission not granted, fetch failed) shows a hint
383
- // instead of silently vanishing.
384
- const lines = this.calendarAvailable
400
+ // calendar shows a hint instead of silently vanishing — "No access"
401
+ // strictly for permission denial, "Unavailable" for any other failure.
402
+ const lines = this.calendarStatus === "ok"
385
403
  ? [
386
404
  "Calendar",
387
405
  ...(this.calendarEvents.length
388
406
  ? this.calendarEvents.slice(0, 3).map(eventLine)
389
407
  : ["No upcoming events"]),
390
408
  ]
391
- : ["Calendar", "No access"];
409
+ : this.calendarStatus === "loading"
410
+ ? ["Calendar"]
411
+ : ["Calendar", this.calendarStatus === "no-access" ? "No access" : "Unavailable"];
392
412
  elements.push({
393
413
  type: "text",
394
414
  id: "calendar",
@@ -492,16 +512,17 @@ class CustomDashboardService {
492
512
  });
493
513
  return elements;
494
514
  }
495
- // Icon grid, paged: the G2 firmware image-container pool is 4, so at most
496
- // four icon images may be on a page. Selection is the bordered cell.
515
+ // Icon grid, paged, row-major. Selection is the bordered cell.
497
516
  const perPage = APPS_PER_PAGE;
498
517
  const pageStart = Math.floor(this.appsIndex / perPage) * perPage;
499
518
  const page = apps.slice(pageStart, pageStart + perPage);
500
- const totalW = page.length * APP_CELL + (page.length - 1) * APP_CELL_GAP;
519
+ const cols = Math.min(page.length, APP_GRID_COLS);
520
+ const totalW = cols * APP_CELL + (cols - 1) * APP_CELL_GAP;
501
521
  const startX = Math.max(MARGIN_X, Math.round((canvas.width - totalW) / 2));
502
- page.forEach((app, column) => {
503
- const x = startX + column * (APP_CELL + APP_CELL_GAP);
504
- const selected = pageStart + column === this.appsIndex;
522
+ page.forEach((app, cell) => {
523
+ const x = startX + (cell % APP_GRID_COLS) * (APP_CELL + APP_CELL_GAP);
524
+ const y = listTop + Math.floor(cell / APP_GRID_COLS) * (APP_CELL + APP_CELL_GAP);
525
+ const selected = pageStart + cell === this.appsIndex;
505
526
  const icon = this.iconCache.get(app.packageName);
506
527
  if (icon === undefined)
507
528
  this.loadIcon(app);
@@ -509,7 +530,7 @@ class CustomDashboardService {
509
530
  elements.push({
510
531
  type: "image",
511
532
  id: `app-icon:${app.packageName}`,
512
- box: { x: x + APP_ICON_INSET, y: listTop + APP_ICON_INSET, w: APP_ICON, h: APP_ICON },
533
+ box: { x: x + APP_ICON_INSET, y: y + APP_ICON_INSET, w: APP_ICON, h: APP_ICON },
513
534
  data: icon,
514
535
  });
515
536
  }
@@ -518,27 +539,29 @@ class CustomDashboardService {
518
539
  elements.push({
519
540
  type: "text",
520
541
  id: `app-icon:${app.packageName}`,
521
- box: { x: x + APP_ICON_INSET, y: listTop + APP_ICON_INSET, w: APP_ICON, h: APP_ICON },
542
+ box: { x: x + APP_ICON_INSET, y: y + APP_ICON_INSET, w: APP_ICON, h: APP_ICON },
522
543
  text: (app.name.trim()[0] ?? "?").toUpperCase(),
523
- style: { font: "mono", fontSize: 40 },
544
+ style: { font: "mono", fontSize: 20 },
524
545
  });
525
546
  }
526
547
  if (selected) {
527
548
  elements.push({
528
549
  type: "rect",
529
550
  id: "apps-selection",
530
- box: { x, y: listTop, w: APP_CELL, h: APP_CELL },
531
- style: { border: 2, radius: 8 },
551
+ box: { x, y, w: APP_CELL, h: APP_CELL },
552
+ style: { border: 2, radius: 6 },
532
553
  });
533
554
  }
534
555
  });
535
556
  // Grayscale mini icons are ambiguous on their own: caption the selection.
557
+ const gridRows = Math.ceil(page.length / APP_GRID_COLS);
558
+ const gridH = gridRows * APP_CELL + (gridRows - 1) * APP_CELL_GAP;
536
559
  const selectedApp = apps[this.appsIndex];
537
560
  if (selectedApp) {
538
561
  elements.push({
539
562
  type: "text",
540
563
  id: "apps-selected-name",
541
- box: { x: MARGIN_X, y: listTop + APP_CELL + 12, w: width, h: LINE_H },
564
+ box: { x: MARGIN_X, y: listTop + gridH + 12, w: width, h: LINE_H },
542
565
  text: `${selectedApp.name}${selectedApp.running ? " ●" : ""}`,
543
566
  style: { font: "builtin", breakMode: "strict-word", overflow: "ellipsis" },
544
567
  });
@@ -1 +1 @@
1
- {"version":3,"file":"CustomDashboardService.js","sourceRoot":"","sources":["../../src/services/CustomDashboardService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene API dashboard for G2 custom firmware.\n *\n * G2.swift marks the published firmware version after it detects the CFW\n * capability advertisement. The service retains a dashboard frame, owns\n * dashboard gestures while head-up is active, and leaves every other device\n * on its native behavior.\n *\n * The dashboard is composed of user-orderable widgets (clock, calendar, apps\n * button, notifications button) stacked vertically on the CFW scene canvas.\n * The apps button switches to a second, dashboard-rendered app menu view.\n */\nimport {File} from \"expo-file-system\"\n\nimport {BluetoothSdk} from \"@glassly/bluetooth-sdk\"\nimport type {CalendarEvent} from \"@glassly/miniapp\"\n\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport type {ClientApp} from \"../types/applet\"\nimport type {SceneElementInput} from \"../utils/display/scene\"\nimport {BgTimer} from \"../utils/timers\"\nimport {\n appMenuActions,\n dashboardActions,\n formatDashboardClock,\n isCustomDashboardEligible,\n normalizeDashboardWidgets,\n orderedDashboardApps,\n type CustomDashboardClockStyle,\n type DashboardWidgetSetting,\n type MenuAppSetting,\n} from \"./CustomDashboardModel\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {listPhoneCalendarEvents} from \"./PhoneCalendarService\"\nimport sceneRenderer from \"./SceneRenderer\"\n\nconst DASHBOARD_APP_ID = \"system.custom-dashboard\"\nconst CLOCK_REFRESH_MS = 30_000\nconst CALENDAR_REFRESH_MS = 5 * 60_000\n\n// CFW blits the stock compositor's 576×288 canvas; used when the firmware has\n// not reported scene capabilities yet.\nconst FALLBACK_CANVAS = {width: 576, height: 288}\nconst MARGIN_X = 16\nconst MARGIN_TOP = 10\nconst WIDGET_GAP = 10\n/** Row height for the glasses' builtin 20px font engine. */\nconst LINE_H = 24\nconst BUTTON_H = 34\n/** App-menu icon grid: 4 per page (G2 firmware image-container pool). */\nconst APPS_PER_PAGE = 4\nconst APP_CELL = 72\nconst APP_CELL_GAP = 16\nconst APP_ICON_INSET = 4\nconst APP_ICON = APP_CELL - APP_ICON_INSET * 2\n\nfunction eventLine(event: CalendarEvent): string {\n const when = event.allDay\n ? \"All day\"\n : new Intl.DateTimeFormat(undefined, {hour: \"numeric\", minute: \"2-digit\"}).format(new Date(event.startsAt))\n return `${when} ${event.title}`\n}\n\nclass CustomDashboardService {\n private started = false\n private open = false\n private view: \"main\" | \"apps\" = \"main\"\n private mainIndex = 0\n private appsIndex = 0\n private calendarAvailable = false\n /** packageName → base64 icon bytes, or null when the icon can't be loaded (monogram fallback). */\n private iconCache = new Map<string, string | null>()\n private iconLoads = new Set<string>()\n private calendarEvents: CalendarEvent[] = []\n private unsubs: Array<() => void> = []\n private clockTimer: number | null = null\n private calendarTimer: number | null = null\n /** Last observed eligibility, for transition detection. */\n private eligible = false\n\n start(): void {\n if (this.started) return\n this.started = true\n this.unsubs.push(\n useGlassesStore.subscribe(\n (state) => `${state.connection.state}:${state.deviceModel}:${state.firmwareVersion}`,\n () => {\n if (!this.syncEligibility()) return\n void this.refreshCalendarAndRender()\n },\n ),\n // Selector: only dashboard-relevant settings retrigger a refresh. The\n // whole-store form fired a calendar query on every settings write.\n useSettingsStore.subscribe(\n (state) =>\n JSON.stringify([\n state.getSetting(SETTINGS.custom_dashboard_enabled.key),\n state.getSetting(SETTINGS.custom_dashboard_widgets.key),\n state.getSetting(SETTINGS.custom_dashboard_clock_style.key),\n state.getSetting(SETTINGS.custom_dashboard_clock_show_date.key),\n state.getSetting(SETTINGS.twelve_hour_time.key),\n state.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key),\n state.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key),\n state.getSetting(SETTINGS.menu_apps.key),\n ]),\n () => {\n if (!this.syncEligibility()) return\n void this.refreshCalendarAndRender()\n },\n ),\n useAppStatusStore.subscribe(() => {\n if (this.isEligible()) this.render()\n }),\n )\n this.clockTimer = BgTimer.setInterval(() => {\n if (this.isEligible()) this.render()\n }, CLOCK_REFRESH_MS)\n this.calendarTimer = BgTimer.setInterval(() => {\n if (this.isEligible()) void this.refreshCalendarAndRender()\n }, CALENDAR_REFRESH_MS)\n this.eligible = this.isEligible()\n if (this.eligible) {\n this.ensureContextualDashboard()\n void this.refreshCalendarAndRender()\n }\n }\n\n /**\n * Track eligible⇄ineligible transitions from any trigger (setting toggle,\n * connect/disconnect, firmware change). Entering enables the contextual\n * dashboard once — not on every settings write, so a user's manual\n * contextual-dashboard choice is no longer ratcheted back. Leaving closes\n * the dashboard, blanks the glasses view, and drops the retained scene so a\n * stale frame can't be replayed after the feature was turned off.\n */\n private syncEligibility(): boolean {\n const eligible = this.isEligible()\n if (eligible === this.eligible) return eligible\n this.eligible = eligible\n if (eligible) {\n this.ensureContextualDashboard()\n } else {\n this.open = false\n this.resetNavigation()\n sceneRenderer.emitScene(DASHBOARD_APP_ID, \"dashboard\", [])\n sceneRenderer.clearApp(DASHBOARD_APP_ID)\n }\n return eligible\n }\n\n stop(): void {\n for (const unsub of this.unsubs) unsub()\n this.unsubs = []\n if (this.clockTimer !== null) BgTimer.clearInterval(this.clockTimer)\n if (this.calendarTimer !== null) BgTimer.clearInterval(this.calendarTimer)\n this.clockTimer = null\n this.calendarTimer = null\n this.open = false\n this.eligible = false\n this.resetNavigation()\n this.started = false\n sceneRenderer.clearApp(DASHBOARD_APP_ID)\n }\n\n isEligible(): boolean {\n if (useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_enabled.key) === false) return false\n const state = useGlassesStore.getState()\n return (\n isGlassesConnected(state.connection) &&\n isCustomDashboardEligible({\n model: state.deviceModel,\n firmwareVersion: state.firmwareVersion,\n })\n )\n }\n\n get isOpen(): boolean {\n return this.open\n }\n\n setHeadUp(up: boolean): void {\n if (!this.isEligible()) {\n this.open = false\n return\n }\n if (up && !this.open) this.resetNavigation()\n this.open = up\n if (up) void this.refreshCalendarAndRender()\n }\n\n /** Toggle the custom dashboard for an eligible device. */\n toggle(): boolean {\n if (!this.isEligible()) return false\n this.open = !this.open\n if (this.open) {\n this.resetNavigation()\n void this.refreshCalendarAndRender()\n }\n void Promise.resolve(BluetoothSdk.toggleHeadUp()).catch((error) => {\n this.open = !this.open\n console.error(\"CustomDashboard: toggleHeadUp failed\", error)\n })\n return true\n }\n\n /** Returns true when an open custom dashboard consumed the gesture. */\n handleGesture(gestureName: string): boolean {\n if (!this.open || !this.isEligible()) return false\n if (gestureName === \"double_tap\") {\n // In the app menu a double tap backs out to the top of the dashboard\n // (there is no Back row); on the main view it closes the dashboard.\n if (this.view === \"apps\") {\n this.resetNavigation()\n this.render()\n return true\n }\n return this.toggle()\n }\n if (this.view === \"apps\") return this.handleAppMenuGesture(gestureName)\n return this.handleMainGesture(gestureName)\n }\n\n private resetNavigation(): void {\n this.view = \"main\"\n this.mainIndex = 0\n this.appsIndex = 0\n }\n\n private handleMainGesture(gestureName: string): boolean {\n const actions = dashboardActions(this.widgets())\n if (this.mainIndex >= actions.length) this.mainIndex = Math.max(0, actions.length - 1)\n if (gestureName === \"swipe_down\" || gestureName === \"swipe_up\") {\n if (actions.length === 0) return true\n const delta = gestureName === \"swipe_down\" ? 1 : -1\n this.mainIndex = (this.mainIndex + delta + actions.length) % actions.length\n this.render()\n return true\n }\n if (gestureName === \"single_tap\") {\n const action = actions[this.mainIndex]\n if (action?.kind === \"apps-menu\") {\n this.view = \"apps\"\n this.appsIndex = 0\n this.render()\n } else if (action?.kind === \"notifications\") {\n void this.openNotificationsPanel()\n }\n return true\n }\n return false\n }\n\n private handleAppMenuGesture(gestureName: string): boolean {\n const rows = appMenuActions(this.menuApps())\n if (this.appsIndex >= rows.length) this.appsIndex = Math.max(0, rows.length - 1)\n if (gestureName === \"swipe_down\" || gestureName === \"swipe_up\") {\n const delta = gestureName === \"swipe_down\" ? 1 : -1\n this.appsIndex = (this.appsIndex + delta + rows.length) % rows.length\n this.render()\n return true\n }\n if (gestureName === \"single_tap\") {\n const row = rows[this.appsIndex]\n if (row?.kind === \"app\") {\n void this.toggleApp(row.packageName)\n }\n return true\n }\n return false\n }\n\n private menuApps(): ClientApp[] {\n const menu = useSettingsStore.getState().getSetting(SETTINGS.menu_apps.key) as MenuAppSetting[] | null\n return orderedDashboardApps(menu, useAppStatusStore.getState().apps)\n }\n\n private widgets(): DashboardWidgetSetting[] {\n return normalizeDashboardWidgets(useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_widgets.key))\n }\n\n private async close(): Promise<void> {\n if (!this.open) return\n this.open = false\n await Promise.resolve(BluetoothSdk.toggleHeadUp()).catch((error) => {\n console.error(\"CustomDashboard: close failed\", error)\n })\n }\n\n private async openNotificationsPanel(): Promise<void> {\n await this.close()\n await BluetoothSdk.showNativeNotificationPanel().catch((error) => {\n console.error(\"CustomDashboard: notification panel failed\", error)\n })\n }\n\n private async toggleApp(packageName: string): Promise<void> {\n await this.close()\n const app = useAppStatusStore.getState().apps.find((candidate) => candidate.packageName === packageName)\n if (!app) return\n if (app.running) {\n await useAppStatusStore.getState().stop(app.packageName)\n } else {\n await useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }\n\n private calendarWidgetEnabled(): boolean {\n return this.widgets().some((widget) => widget.id === \"calendar\" && widget.enabled)\n }\n\n private ensureContextualDashboard(): void {\n const settings = useSettingsStore.getState()\n if (settings.getSetting(SETTINGS.contextual_dashboard.key) !== true) {\n // DeviceManager selects its retained dashboard Scene only while this is\n // enabled. Custom firmware eligibility makes that dashboard automatic.\n void settings.setSetting(SETTINGS.contextual_dashboard.key, true, false)\n }\n }\n\n private async refreshCalendarAndRender(): Promise<void> {\n if (!this.calendarWidgetEnabled()) {\n this.calendarAvailable = false\n this.calendarEvents = []\n this.render()\n return\n }\n const settings = useSettingsStore.getState()\n const count = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key) ?? 2)\n const days = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key) ?? 3)\n const startsAt = new Date()\n const endsAt = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000)\n try {\n const result = await listPhoneCalendarEvents({startsAt, endsAt, limit: Math.max(1, Math.min(3, count))})\n this.calendarAvailable = true\n this.calendarEvents = result.events\n } catch {\n // Permission is requested from the Custom Dashboard settings screen;\n // without access the widget silently stays off the glasses.\n this.calendarAvailable = false\n this.calendarEvents = []\n }\n this.render()\n }\n\n private canvas(): {width: number; height: number} {\n const {sceneCanvasWidth, sceneCanvasHeight} = useGlassesStore.getState()\n if (sceneCanvasWidth && sceneCanvasWidth > 0 && sceneCanvasHeight && sceneCanvasHeight > 0) {\n return {width: sceneCanvasWidth, height: sceneCanvasHeight}\n }\n return FALLBACK_CANVAS\n }\n\n private render(): void {\n if (!this.isEligible()) return\n const elements = this.view === \"apps\" ? this.appMenuElements() : this.mainElements()\n sceneRenderer.emitScene(DASHBOARD_APP_ID, \"dashboard\", elements)\n }\n\n private mainElements(): SceneElementInput[] {\n const canvas = this.canvas()\n const settings = useSettingsStore.getState()\n const widgets = this.widgets().filter((widget) => widget.enabled)\n const actions = dashboardActions(widgets)\n if (this.mainIndex >= actions.length) this.mainIndex = Math.max(0, actions.length - 1)\n const selected = actions[this.mainIndex]\n\n const clockStyle =\n (settings.getSetting(SETTINGS.custom_dashboard_clock_style.key) as CustomDashboardClockStyle) === \"large\"\n ? \"large\"\n : \"compact\"\n const calendarEnabled = widgets.some((widget) => widget.id === \"calendar\")\n // Calendar lives in the right half; everything else stacks in the left\n // half (full width when the calendar widget is off).\n const halfW = Math.floor(canvas.width / 2) - MARGIN_X - Math.floor(WIDGET_GAP / 2)\n const width = calendarEnabled ? halfW : canvas.width - MARGIN_X * 2\n const rightX = Math.floor(canvas.width / 2) + Math.floor(WIDGET_GAP / 2)\n\n const elements: SceneElementInput[] = []\n let y = MARGIN_TOP\n for (const widget of widgets) {\n if (widget.id === \"clock\") {\n const fontSize = clockStyle === \"large\" ? 48 : 32\n const clock = formatDashboardClock(new Date(), {\n twelveHour: settings.getSetting(SETTINGS.twelve_hour_time.key) === true,\n showDate: settings.getSetting(SETTINGS.custom_dashboard_clock_show_date.key) !== false,\n })\n elements.push({\n type: \"text\",\n id: \"clock\",\n box: {x: MARGIN_X, y, w: width, h: fontSize + 6},\n text: clock.time,\n style: {font: \"mono\", fontSize, breakMode: \"strict-word\"},\n })\n y += fontSize + 6\n if (clock.date) {\n elements.push({\n type: \"text\",\n id: \"clock-date\",\n box: {x: MARGIN_X, y, w: width, h: LINE_H},\n text: clock.date,\n style: {font: \"builtin\", breakMode: \"strict-word\"},\n })\n y += LINE_H\n }\n y += WIDGET_GAP\n } else if (widget.id === \"calendar\") {\n // Right half, top-aligned, independent of the left stack. A missing\n // calendar (permission not granted, fetch failed) shows a hint\n // instead of silently vanishing.\n const lines = this.calendarAvailable\n ? [\n \"Calendar\",\n ...(this.calendarEvents.length\n ? this.calendarEvents.slice(0, 3).map(eventLine)\n : [\"No upcoming events\"]),\n ]\n : [\"Calendar\", \"No access\"]\n elements.push({\n type: \"text\",\n id: \"calendar\",\n box: {x: rightX, y: MARGIN_TOP, w: halfW, h: lines.length * LINE_H},\n text: lines.join(\"\\n\"),\n style: {font: \"builtin\", breakMode: \"strict-word\", overflow: \"ellipsis\"},\n })\n } else if (widget.id === \"apps\") {\n const isSelected = selected?.kind === \"apps-menu\"\n elements.push({\n type: \"text\",\n id: \"apps-button\",\n box: {x: MARGIN_X, y, w: width, h: BUTTON_H},\n text: \"Apps\",\n style: {font: \"builtin\", border: isSelected ? 2 : 0, radius: 8},\n })\n y += BUTTON_H + WIDGET_GAP\n } else if (widget.id === \"notifications\") {\n const isSelected = selected?.kind === \"notifications\"\n elements.push({\n type: \"text\",\n id: \"notifications\",\n box: {x: MARGIN_X, y, w: width, h: BUTTON_H},\n text: \"Notifications\",\n style: {font: \"builtin\", border: isSelected ? 2 : 0, radius: 8},\n })\n y += BUTTON_H + WIDGET_GAP\n }\n }\n return elements\n }\n\n /**\n * Resolve a miniapp icon to base64 for the glasses. Local/bundled miniapps\n * carry a file:// icon.png; store apps an https URL; the offline system\n * tiles a bundler asset id (unsupported here — monogram fallback). Loads\n * are async and cached; a finished load repaints the open app menu.\n */\n private loadIcon(app: ClientApp): void {\n if (this.iconCache.has(app.packageName) || this.iconLoads.has(app.packageName)) return\n const url = typeof app.logoUrl === \"string\" ? app.logoUrl : \"\"\n if (!/^(file|https?):/.test(url)) {\n this.iconCache.set(app.packageName, null)\n return\n }\n this.iconLoads.add(app.packageName)\n void (async () => {\n try {\n let base64: string\n if (url.startsWith(\"file:\")) {\n base64 = await new File(url).base64()\n } else {\n const response = await fetch(url)\n const blob = await response.blob()\n base64 = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(String(reader.result).split(\",\")[1] ?? \"\")\n reader.onerror = () => reject(reader.error)\n reader.readAsDataURL(blob)\n })\n }\n this.iconCache.set(app.packageName, base64 || null)\n } catch {\n this.iconCache.set(app.packageName, null)\n } finally {\n this.iconLoads.delete(app.packageName)\n if (this.open && this.view === \"apps\") this.render()\n }\n })()\n }\n\n private appMenuElements(): SceneElementInput[] {\n const canvas = this.canvas()\n const width = canvas.width - MARGIN_X * 2\n const apps = this.menuApps()\n const rows = appMenuActions(apps)\n if (this.appsIndex >= rows.length) this.appsIndex = Math.max(0, rows.length - 1)\n\n const elements: SceneElementInput[] = [\n {\n type: \"text\",\n id: \"apps-title\",\n box: {x: MARGIN_X, y: MARGIN_TOP, w: width, h: LINE_H},\n text: \"Apps\",\n style: {font: \"builtin\"},\n },\n ]\n const listTop = MARGIN_TOP + LINE_H + 16\n if (apps.length === 0) {\n elements.push({\n type: \"text\",\n id: \"apps-empty\",\n box: {x: MARGIN_X, y: listTop, w: width, h: LINE_H},\n text: \"No apps in Glasses Menu\",\n style: {font: \"builtin\"},\n })\n return elements\n }\n\n // Icon grid, paged: the G2 firmware image-container pool is 4, so at most\n // four icon images may be on a page. Selection is the bordered cell.\n const perPage = APPS_PER_PAGE\n const pageStart = Math.floor(this.appsIndex / perPage) * perPage\n const page = apps.slice(pageStart, pageStart + perPage)\n const totalW = page.length * APP_CELL + (page.length - 1) * APP_CELL_GAP\n const startX = Math.max(MARGIN_X, Math.round((canvas.width - totalW) / 2))\n page.forEach((app, column) => {\n const x = startX + column * (APP_CELL + APP_CELL_GAP)\n const selected = pageStart + column === this.appsIndex\n const icon = this.iconCache.get(app.packageName)\n if (icon === undefined) this.loadIcon(app)\n if (icon) {\n elements.push({\n type: \"image\",\n id: `app-icon:${app.packageName}`,\n box: {x: x + APP_ICON_INSET, y: listTop + APP_ICON_INSET, w: APP_ICON, h: APP_ICON},\n data: icon,\n })\n } else {\n // Monogram fallback for icons that can't load (bundler-asset tiles).\n elements.push({\n type: \"text\",\n id: `app-icon:${app.packageName}`,\n box: {x: x + APP_ICON_INSET, y: listTop + APP_ICON_INSET, w: APP_ICON, h: APP_ICON},\n text: (app.name.trim()[0] ?? \"?\").toUpperCase(),\n style: {font: \"mono\", fontSize: 40},\n })\n }\n if (selected) {\n elements.push({\n type: \"rect\",\n id: \"apps-selection\",\n box: {x, y: listTop, w: APP_CELL, h: APP_CELL},\n style: {border: 2, radius: 8},\n })\n }\n })\n\n // Grayscale mini icons are ambiguous on their own: caption the selection.\n const selectedApp = apps[this.appsIndex]\n if (selectedApp) {\n elements.push({\n type: \"text\",\n id: \"apps-selected-name\",\n box: {x: MARGIN_X, y: listTop + APP_CELL + 12, w: width, h: LINE_H},\n text: `${selectedApp.name}${selectedApp.running ? \" ●\" : \"\"}`,\n style: {font: \"builtin\", breakMode: \"strict-word\", overflow: \"ellipsis\"},\n })\n }\n return elements\n }\n}\n\nconst customDashboardService = new CustomDashboardService()\nexport default customDashboardService\nexport {CustomDashboardService}\n"]}
1
+ {"version":3,"file":"CustomDashboardService.js","sourceRoot":"","sources":["../../src/services/CustomDashboardService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAErC,OAAO,EAAC,YAAY,EAAC,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAC,gBAAgB,EAAqB,MAAM,kBAAkB,CAAA;AAErE,OAAO,EAAC,iBAAiB,EAAC,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAG7D,OAAO,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAA;AACvC,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,EACzB,oBAAoB,GAIrB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAC,uBAAuB,EAAE,kBAAkB,EAAC,MAAM,wBAAwB,CAAA;AAClF,OAAO,aAAa,MAAM,iBAAiB,CAAA;AAE3C,MAAM,gBAAgB,GAAG,yBAAyB,CAAA;AAClD,MAAM,gBAAgB,GAAG,MAAM,CAAA;AAC/B,MAAM,mBAAmB,GAAG,CAAC,GAAG,MAAM,CAAA;AAEtC,8EAA8E;AAC9E,uCAAuC;AACvC,MAAM,eAAe,GAAG,EAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAC,CAAA;AACjD,MAAM,QAAQ,GAAG,EAAE,CAAA;AACnB,MAAM,UAAU,GAAG,EAAE,CAAA;AACrB,MAAM,UAAU,GAAG,EAAE,CAAA;AACrB,4DAA4D;AAC5D,MAAM,MAAM,GAAG,EAAE,CAAA;AACjB,MAAM,QAAQ,GAAG,EAAE,CAAA;AACnB;;;;;GAKG;AACH,MAAM,aAAa,GAAG,CAAC,CAAA;AACvB,MAAM,aAAa,GAAG,CAAC,CAAA;AACvB,MAAM,aAAa,GAAG,aAAa,GAAG,aAAa,CAAA;AACnD,MAAM,QAAQ,GAAG,EAAE,CAAA;AACnB,MAAM,YAAY,GAAG,EAAE,CAAA;AACvB,MAAM,cAAc,GAAG,CAAC,CAAA;AACxB,MAAM,QAAQ,GAAG,QAAQ,GAAG,cAAc,GAAG,CAAC,CAAA;AAE9C,SAAS,SAAS,CAAC,KAAoB;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM;QACvB,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,EAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC7G,OAAO,GAAG,IAAI,KAAK,KAAK,CAAC,KAAK,EAAE,CAAA;AAClC,CAAC;AAED,MAAM,sBAAsB;IAClB,OAAO,GAAG,KAAK,CAAA;IACf,IAAI,GAAG,KAAK,CAAA;IACZ,IAAI,GAAoB,MAAM,CAAA;IAC9B,SAAS,GAAG,CAAC,CAAA;IACb,SAAS,GAAG,CAAC,CAAA;IACrB,+FAA+F;IACvF,cAAc,GAAmD,SAAS,CAAA;IAClF,kGAAkG;IAC1F,SAAS,GAAG,IAAI,GAAG,EAAyB,CAAA;IAC5C,SAAS,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,cAAc,GAAoB,EAAE,CAAA;IACpC,MAAM,GAAsB,EAAE,CAAA;IAC9B,UAAU,GAAkB,IAAI,CAAA;IAChC,aAAa,GAAkB,IAAI,CAAA;IAC3C,2DAA2D;IACnD,QAAQ,GAAG,KAAK,CAAA;IAExB,KAAK;QACH,IAAI,IAAI,CAAC,OAAO;YAAE,OAAM;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,eAAe,CAAC,SAAS,CACvB,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,eAAe,EAAE,EACpF,GAAG,EAAE;YACH,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAM;YACnC,KAAK,IAAI,CAAC,wBAAwB,EAAE,CAAA;QACtC,CAAC,CACF;QACD,sEAAsE;QACtE,mEAAmE;QACnE,gBAAgB,CAAC,SAAS,CACxB,CAAC,KAAK,EAAE,EAAE,CACR,IAAI,CAAC,SAAS,CAAC;YACb,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,GAAG,CAAC;YACvD,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,GAAG,CAAC;YACvD,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,4BAA4B,CAAC,GAAG,CAAC;YAC3D,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,gCAAgC,CAAC,GAAG,CAAC;YAC/D,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC;YAC/C,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,qCAAqC,CAAC,GAAG,CAAC;YACpE,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,oCAAoC,CAAC,GAAG,CAAC;YACnE,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC;SACzC,CAAC,EACJ,GAAG,EAAE;YACH,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAM;YACnC,KAAK,IAAI,CAAC,wBAAwB,EAAE,CAAA;QACtC,CAAC,CACF,EACD,iBAAiB,CAAC,SAAS,CAAC,GAAG,EAAE;YAC/B,IAAI,IAAI,CAAC,UAAU,EAAE;gBAAE,IAAI,CAAC,MAAM,EAAE,CAAA;QACtC,CAAC,CAAC,CACH,CAAA;QACD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE;YACzC,IAAI,IAAI,CAAC,UAAU,EAAE;gBAAE,IAAI,CAAC,MAAM,EAAE,CAAA;QACtC,CAAC,EAAE,gBAAgB,CAAC,CAAA;QACpB,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE;YAC5C,IAAI,IAAI,CAAC,UAAU,EAAE;gBAAE,KAAK,IAAI,CAAC,wBAAwB,EAAE,CAAA;QAC7D,CAAC,EAAE,mBAAmB,CAAC,CAAA;QACvB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,CAAA;QACjC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,yBAAyB,EAAE,CAAA;YAChC,KAAK,IAAI,CAAC,wBAAwB,EAAE,CAAA;QACtC,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACK,eAAe;QACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,CAAA;QAClC,IAAI,QAAQ,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO,QAAQ,CAAA;QAC/C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,yBAAyB,EAAE,CAAA;QAClC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,GAAG,KAAK,CAAA;YACjB,IAAI,CAAC,eAAe,EAAE,CAAA;YACtB,aAAa,CAAC,SAAS,CAAC,gBAAgB,EAAE,WAAW,EAAE,EAAE,CAAC,CAAA;YAC1D,aAAa,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAA;QAC1C,CAAC;QACD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,IAAI;QACF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM;YAAE,KAAK,EAAE,CAAA;QACxC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;QAChB,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI;YAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACpE,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI;YAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QAC1E,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACtB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAA;QACjB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAA;QACrB,IAAI,CAAC,eAAe,EAAE,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACpB,aAAa,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAA;IAC1C,CAAC;IAED,UAAU;QACR,IAAI,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,GAAG,CAAC,KAAK,KAAK;YAAE,OAAO,KAAK,CAAA;QACzG,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;QACxC,OAAO,CACL,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC;YACpC,yBAAyB,CAAC;gBACxB,KAAK,EAAE,KAAK,CAAC,WAAW;gBACxB,eAAe,EAAE,KAAK,CAAC,eAAe;aACvC,CAAC,CACH,CAAA;IACH,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,SAAS,CAAC,EAAW;QACnB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;YACvB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAA;YACjB,OAAM;QACR,CAAC;QACD,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,IAAI,CAAC,eAAe,EAAE,CAAA;QAC5C,IAAI,CAAC,IAAI,GAAG,EAAE,CAAA;QACd,IAAI,EAAE;YAAE,KAAK,IAAI,CAAC,wBAAwB,EAAE,CAAA;IAC9C,CAAC;IAED,0DAA0D;IAC1D,MAAM;QACJ,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YAAE,OAAO,KAAK,CAAA;QACpC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAA;QACtB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,IAAI,CAAC,eAAe,EAAE,CAAA;YACtB,KAAK,IAAI,CAAC,wBAAwB,EAAE,CAAA;QACtC,CAAC;QACD,KAAK,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YAChE,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAA;YACtB,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,KAAK,CAAC,CAAA;QAC9D,CAAC,CAAC,CAAA;QACF,OAAO,IAAI,CAAA;IACb,CAAC;IAED,uEAAuE;IACvE,aAAa,CAAC,WAAmB;QAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YAAE,OAAO,KAAK,CAAA;QAClD,IAAI,WAAW,KAAK,YAAY,EAAE,CAAC;YACjC,qEAAqE;YACrE,oEAAoE;YACpE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACzB,IAAI,CAAC,eAAe,EAAE,CAAA;gBACtB,IAAI,CAAC,MAAM,EAAE,CAAA;gBACb,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAO,IAAI,CAAC,MAAM,EAAE,CAAA;QACtB,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,oBAAoB,CAAC,WAAW,CAAC,CAAA;QACvE,OAAO,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;IAC5C,CAAC;IAEO,eAAe;QACrB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAA;QAClB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QAClB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;IACpB,CAAC;IAEO,iBAAiB,CAAC,WAAmB;QAC3C,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;QAChD,IAAI,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC,MAAM;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACtF,IAAI,WAAW,KAAK,YAAY,IAAI,WAAW,KAAK,UAAU,EAAE,CAAC;YAC/D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAA;YACrC,MAAM,KAAK,GAAG,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACnD,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAA;YAC3E,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,OAAO,IAAI,CAAA;QACb,CAAC;QACD,IAAI,WAAW,KAAK,YAAY,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YACtC,IAAI,MAAM,EAAE,IAAI,KAAK,WAAW,EAAE,CAAC;gBACjC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAA;gBAClB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;gBAClB,IAAI,CAAC,MAAM,EAAE,CAAA;YACf,CAAC;iBAAM,IAAI,MAAM,EAAE,IAAI,KAAK,eAAe,EAAE,CAAC;gBAC5C,KAAK,IAAI,CAAC,sBAAsB,EAAE,CAAA;YACpC,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAEO,oBAAoB,CAAC,WAAmB;QAC9C,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC5C,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QAChF,IAAI,WAAW,KAAK,YAAY,IAAI,WAAW,KAAK,UAAU,EAAE,CAAC;YAC/D,MAAM,KAAK,GAAG,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACnD,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAA;YACrE,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,OAAO,IAAI,CAAA;QACb,CAAC;QACD,IAAI,WAAW,KAAK,YAAY,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YAChC,IAAI,GAAG,EAAE,IAAI,KAAK,KAAK,EAAE,CAAC;gBACxB,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;YACtC,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAEO,QAAQ;QACd,MAAM,IAAI,GAAG,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAA4B,CAAA;QACtG,OAAO,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAA;IACtE,CAAC;IAEO,OAAO;QACb,OAAO,yBAAyB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAA;IACjH,CAAC;IAEO,KAAK,CAAC,KAAK;QACjB,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAM;QACtB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAA;QACjB,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACjE,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAA;QACvD,CAAC,CAAC,CAAA;IACJ,CAAC;IAEO,KAAK,CAAC,sBAAsB;QAClC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;QAClB,MAAM,YAAY,CAAC,2BAA2B,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YAC/D,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAA;QACpE,CAAC,CAAC,CAAA;IACJ,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,WAAmB;QACzC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;QAClB,MAAM,GAAG,GAAG,iBAAiB,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,KAAK,WAAW,CAAC,CAAA;QACxG,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,iBAAiB,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAC1D,CAAC;aAAM,CAAC;YACN,MAAM,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAC,cAAc,EAAE,IAAI,EAAC,CAAC,CAAA;QACvE,CAAC;IACH,CAAC;IAEO,qBAAqB;QAC3B,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,UAAU,IAAI,MAAM,CAAC,OAAO,CAAC,CAAA;IACpF,CAAC;IAEO,yBAAyB;QAC/B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,EAAE,CAAA;QAC5C,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,oBAAoB,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YACpE,wEAAwE;YACxE,uEAAuE;YACvE,KAAK,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,oBAAoB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;QAC1E,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,wBAAwB;QACpC,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAA;YAC/B,IAAI,CAAC,cAAc,GAAG,EAAE,CAAA;YACxB,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,OAAM;QACR,CAAC;QACD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,EAAE,CAAA;QAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,qCAAqC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;QAClG,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,oCAAoC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;QAChG,MAAM,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAA;QAC3B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;QACrF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC;gBAC3C,QAAQ,EAAE,QAAQ,CAAC,WAAW,EAAE;gBAChC,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;gBAC5B,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;aACvC,CAAC,CAAA;YACF,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;YAC1B,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,MAAM,CAAA;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,qEAAqE;YACrE,+DAA+D;YAC/D,oEAAoE;YACpE,WAAW;YACX,MAAM,MAAM,GAAG,KAAK,YAAY,kBAAkB,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAC,iBAAiB,CAAA;YACvG,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAA;YAC1D,IAAI,CAAC,cAAc,GAAG,EAAE,CAAA;YACxB,IAAI,CAAC,MAAM;gBAAE,OAAO,CAAC,IAAI,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAA;QAC5E,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAA;IACf,CAAC;IAEO,MAAM;QACZ,MAAM,EAAC,gBAAgB,EAAE,iBAAiB,EAAC,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;QACxE,IAAI,gBAAgB,IAAI,gBAAgB,GAAG,CAAC,IAAI,iBAAiB,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;YAC3F,OAAO,EAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,iBAAiB,EAAC,CAAA;QAC7D,CAAC;QACD,OAAO,eAAe,CAAA;IACxB,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YAAE,OAAM;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,CAAA;QACpF,aAAa,CAAC,SAAS,CAAC,gBAAgB,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAA;IAClE,CAAC;IAEO,YAAY;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;QAC5B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,EAAE,CAAA;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACjE,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;QACzC,IAAI,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC,MAAM;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACtF,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAExC,MAAM,UAAU,GACb,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,4BAA4B,CAAC,GAAG,CAA+B,KAAK,OAAO;YACvG,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,SAAS,CAAA;QACf,MAAM,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,UAAU,CAAC,CAAA;QAC1E,uEAAuE;QACvE,qDAAqD;QACrD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAA;QAClF,MAAM,KAAK,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAA;QACnE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAA;QAExE,MAAM,QAAQ,GAAwB,EAAE,CAAA;QACxC,IAAI,CAAC,GAAG,UAAU,CAAA;QAClB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,EAAE,KAAK,OAAO,EAAE,CAAC;gBAC1B,MAAM,QAAQ,GAAG,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;gBACjD,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,IAAI,EAAE,EAAE;oBAC7C,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,IAAI;oBACvE,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,gCAAgC,CAAC,GAAG,CAAC,KAAK,KAAK;iBACvF,CAAC,CAAA;gBACF,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,EAAE,EAAE,OAAO;oBACX,GAAG,EAAE,EAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,GAAG,CAAC,EAAC;oBAChD,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,KAAK,EAAE,EAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAC;iBAC1D,CAAC,CAAA;gBACF,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAA;gBACjB,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;oBACf,QAAQ,CAAC,IAAI,CAAC;wBACZ,IAAI,EAAE,MAAM;wBACZ,EAAE,EAAE,YAAY;wBAChB,GAAG,EAAE,EAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAC;wBAC1C,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,KAAK,EAAE,EAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAC;qBACnD,CAAC,CAAA;oBACF,CAAC,IAAI,MAAM,CAAA;gBACb,CAAC;gBACD,CAAC,IAAI,UAAU,CAAA;YACjB,CAAC;iBAAM,IAAI,MAAM,CAAC,EAAE,KAAK,UAAU,EAAE,CAAC;gBACpC,oEAAoE;gBACpE,oEAAoE;gBACpE,uEAAuE;gBACvE,MAAM,KAAK,GACT,IAAI,CAAC,cAAc,KAAK,IAAI;oBAC1B,CAAC,CAAC;wBACE,UAAU;wBACV,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM;4BAC5B,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC;4BAChD,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;qBAC5B;oBACH,CAAC,CAAC,IAAI,CAAC,cAAc,KAAK,SAAS;wBACjC,CAAC,CAAC,CAAC,UAAU,CAAC;wBACd,CAAC,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,cAAc,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAA;gBACvF,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,EAAE,EAAE,UAAU;oBACd,GAAG,EAAE,EAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,EAAC;oBACnE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;oBACtB,KAAK,EAAE,EAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAC;iBACzE,CAAC,CAAA;YACJ,CAAC;iBAAM,IAAI,MAAM,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;gBAChC,MAAM,UAAU,GAAG,QAAQ,EAAE,IAAI,KAAK,WAAW,CAAA;gBACjD,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,EAAE,EAAE,aAAa;oBACjB,GAAG,EAAE,EAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAC;oBAC5C,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,EAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAC;iBAChE,CAAC,CAAA;gBACF,CAAC,IAAI,QAAQ,GAAG,UAAU,CAAA;YAC5B,CAAC;iBAAM,IAAI,MAAM,CAAC,EAAE,KAAK,eAAe,EAAE,CAAC;gBACzC,MAAM,UAAU,GAAG,QAAQ,EAAE,IAAI,KAAK,eAAe,CAAA;gBACrD,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,EAAE,EAAE,eAAe;oBACnB,GAAG,EAAE,EAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAC;oBAC5C,IAAI,EAAE,eAAe;oBACrB,KAAK,EAAE,EAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAC;iBAChE,CAAC,CAAA;gBACF,CAAC,IAAI,QAAQ,GAAG,UAAU,CAAA;YAC5B,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED;;;;;OAKG;IACK,QAAQ,CAAC,GAAc;QAC7B,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC;YAAE,OAAM;QACtF,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;QAC9D,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;YACzC,OAAM;QACR,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACnC,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,IAAI,CAAC;gBACH,IAAI,MAAc,CAAA;gBAClB,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC5B,MAAM,GAAG,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAA;gBACvC,CAAC;qBAAM,CAAC;oBACN,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;oBACjC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;oBAClC,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;wBACrD,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAA;wBAC/B,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;wBACxE,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;wBAC3C,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;oBAC5B,CAAC,CAAC,CAAA;gBACJ,CAAC;gBACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,IAAI,IAAI,CAAC,CAAA;YACrD,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;YAC3C,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;gBACtC,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;oBAAE,IAAI,CAAC,MAAM,EAAE,CAAA;YACtD,CAAC;QACH,CAAC,CAAC,EAAE,CAAA;IACN,CAAC;IAEO,eAAe;QACrB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;QAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAA;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;QAC5B,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QAEhF,MAAM,QAAQ,GAAwB;YACpC;gBACE,IAAI,EAAE,MAAM;gBACZ,EAAE,EAAE,YAAY;gBAChB,GAAG,EAAE,EAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAC;gBACtD,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,EAAC,IAAI,EAAE,SAAS,EAAC;aACzB;SACF,CAAA;QACD,MAAM,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,EAAE,CAAA;QACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,MAAM;gBACZ,EAAE,EAAE,YAAY;gBAChB,GAAG,EAAE,EAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAC;gBACnD,IAAI,EAAE,yBAAyB;gBAC/B,KAAK,EAAE,EAAC,IAAI,EAAE,SAAS,EAAC;aACzB,CAAC,CAAA;YACF,OAAO,QAAQ,CAAA;QACjB,CAAC;QAED,+DAA+D;QAC/D,MAAM,OAAO,GAAG,aAAa,CAAA;QAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,OAAO,CAAA;QAChE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,CAAA;QACvD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;QACjD,MAAM,MAAM,GAAG,IAAI,GAAG,QAAQ,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,CAAA;QAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAC1E,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YACzB,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,GAAG,YAAY,CAAC,CAAA;YACrE,MAAM,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,GAAG,YAAY,CAAC,CAAA;YAChF,MAAM,QAAQ,GAAG,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC,SAAS,CAAA;YACpD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;YAChD,IAAI,IAAI,KAAK,SAAS;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;YAC1C,IAAI,IAAI,EAAE,CAAC;gBACT,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,OAAO;oBACb,EAAE,EAAE,YAAY,GAAG,CAAC,WAAW,EAAE;oBACjC,GAAG,EAAE,EAAC,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAC;oBAC7E,IAAI,EAAE,IAAI;iBACX,CAAC,CAAA;YACJ,CAAC;iBAAM,CAAC;gBACN,qEAAqE;gBACrE,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,EAAE,EAAE,YAAY,GAAG,CAAC,WAAW,EAAE;oBACjC,GAAG,EAAE,EAAC,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAC;oBAC7E,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE;oBAC/C,KAAK,EAAE,EAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAC;iBACpC,CAAC,CAAA;YACJ,CAAC;YACD,IAAI,QAAQ,EAAE,CAAC;gBACb,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,EAAE,EAAE,gBAAgB;oBACpB,GAAG,EAAE,EAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAC;oBACrC,KAAK,EAAE,EAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAC;iBAC9B,CAAC,CAAA;YACJ,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,0EAA0E;QAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,CAAA;QACvD,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,YAAY,CAAA;QACjE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACxC,IAAI,WAAW,EAAE,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,MAAM;gBACZ,EAAE,EAAE,oBAAoB;gBACxB,GAAG,EAAE,EAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAC;gBAChE,IAAI,EAAE,GAAG,WAAW,CAAC,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC9D,KAAK,EAAE,EAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAC;aACzE,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF;AAED,MAAM,sBAAsB,GAAG,IAAI,sBAAsB,EAAE,CAAA;AAC3D,eAAe,sBAAsB,CAAA;AACrC,OAAO,EAAC,sBAAsB,EAAC,CAAA","sourcesContent":["/**\n * Scene API dashboard for G2 custom firmware.\n *\n * G2.swift marks the published firmware version after it detects the CFW\n * capability advertisement. The service retains a dashboard frame, owns\n * dashboard gestures while head-up is active, and leaves every other device\n * on its native behavior.\n *\n * The dashboard is composed of user-orderable widgets (clock, calendar, apps\n * button, notifications button) stacked vertically on the CFW scene canvas.\n * The apps button switches to a second, dashboard-rendered app menu view.\n */\nimport {File} from \"expo-file-system\"\n\nimport {BluetoothSdk} from \"@glassly/bluetooth-sdk\"\nimport {MiniappErrorCode, type CalendarEvent} from \"@glassly/miniapp\"\n\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport type {ClientApp} from \"../types/applet\"\nimport type {SceneElementInput} from \"../utils/display/scene\"\nimport {BgTimer} from \"../utils/timers\"\nimport {\n appMenuActions,\n dashboardActions,\n formatDashboardClock,\n isCustomDashboardEligible,\n normalizeDashboardWidgets,\n orderedDashboardApps,\n type CustomDashboardClockStyle,\n type DashboardWidgetSetting,\n type MenuAppSetting,\n} from \"./CustomDashboardModel\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {listPhoneCalendarEvents, PhoneCalendarError} from \"./PhoneCalendarService\"\nimport sceneRenderer from \"./SceneRenderer\"\n\nconst DASHBOARD_APP_ID = \"system.custom-dashboard\"\nconst CLOCK_REFRESH_MS = 30_000\nconst CALENDAR_REFRESH_MS = 5 * 60_000\n\n// CFW blits the stock compositor's 576×288 canvas; used when the firmware has\n// not reported scene capabilities yet.\nconst FALLBACK_CANVAS = {width: 576, height: 288}\nconst MARGIN_X = 16\nconst MARGIN_TOP = 10\nconst WIDGET_GAP = 10\n/** Row height for the glasses' builtin 20px font engine. */\nconst LINE_H = 24\nconst BUTTON_H = 34\n/**\n * App-menu icon grid: 2 rows × 4 columns per page. CFW rasterizes scenes\n * phone-side, so the stock firmware's 4-image container pool does not bound\n * the page (getEffectiveCapabilities lifts the element caps while the CFW\n * canvas is active).\n */\nconst APP_GRID_COLS = 4\nconst APP_GRID_ROWS = 2\nconst APPS_PER_PAGE = APP_GRID_COLS * APP_GRID_ROWS\nconst APP_CELL = 36\nconst APP_CELL_GAP = 12\nconst APP_ICON_INSET = 2\nconst APP_ICON = APP_CELL - APP_ICON_INSET * 2\n\nfunction eventLine(event: CalendarEvent): string {\n const when = event.allDay\n ? \"All day\"\n : new Intl.DateTimeFormat(undefined, {hour: \"numeric\", minute: \"2-digit\"}).format(new Date(event.startsAt))\n return `${when} ${event.title}`\n}\n\nclass CustomDashboardService {\n private started = false\n private open = false\n private view: \"main\" | \"apps\" = \"main\"\n private mainIndex = 0\n private appsIndex = 0\n /** \"loading\" until the first fetch resolves; \"no-access\" only for a real permission denial. */\n private calendarStatus: \"loading\" | \"ok\" | \"no-access\" | \"unavailable\" = \"loading\"\n /** packageName → base64 icon bytes, or null when the icon can't be loaded (monogram fallback). */\n private iconCache = new Map<string, string | null>()\n private iconLoads = new Set<string>()\n private calendarEvents: CalendarEvent[] = []\n private unsubs: Array<() => void> = []\n private clockTimer: number | null = null\n private calendarTimer: number | null = null\n /** Last observed eligibility, for transition detection. */\n private eligible = false\n\n start(): void {\n if (this.started) return\n this.started = true\n this.unsubs.push(\n useGlassesStore.subscribe(\n (state) => `${state.connection.state}:${state.deviceModel}:${state.firmwareVersion}`,\n () => {\n if (!this.syncEligibility()) return\n void this.refreshCalendarAndRender()\n },\n ),\n // Selector: only dashboard-relevant settings retrigger a refresh. The\n // whole-store form fired a calendar query on every settings write.\n useSettingsStore.subscribe(\n (state) =>\n JSON.stringify([\n state.getSetting(SETTINGS.custom_dashboard_enabled.key),\n state.getSetting(SETTINGS.custom_dashboard_widgets.key),\n state.getSetting(SETTINGS.custom_dashboard_clock_style.key),\n state.getSetting(SETTINGS.custom_dashboard_clock_show_date.key),\n state.getSetting(SETTINGS.twelve_hour_time.key),\n state.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key),\n state.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key),\n state.getSetting(SETTINGS.menu_apps.key),\n ]),\n () => {\n if (!this.syncEligibility()) return\n void this.refreshCalendarAndRender()\n },\n ),\n useAppStatusStore.subscribe(() => {\n if (this.isEligible()) this.render()\n }),\n )\n this.clockTimer = BgTimer.setInterval(() => {\n if (this.isEligible()) this.render()\n }, CLOCK_REFRESH_MS)\n this.calendarTimer = BgTimer.setInterval(() => {\n if (this.isEligible()) void this.refreshCalendarAndRender()\n }, CALENDAR_REFRESH_MS)\n this.eligible = this.isEligible()\n if (this.eligible) {\n this.ensureContextualDashboard()\n void this.refreshCalendarAndRender()\n }\n }\n\n /**\n * Track eligible⇄ineligible transitions from any trigger (setting toggle,\n * connect/disconnect, firmware change). Entering enables the contextual\n * dashboard once — not on every settings write, so a user's manual\n * contextual-dashboard choice is no longer ratcheted back. Leaving closes\n * the dashboard, blanks the glasses view, and drops the retained scene so a\n * stale frame can't be replayed after the feature was turned off.\n */\n private syncEligibility(): boolean {\n const eligible = this.isEligible()\n if (eligible === this.eligible) return eligible\n this.eligible = eligible\n if (eligible) {\n this.ensureContextualDashboard()\n } else {\n this.open = false\n this.resetNavigation()\n sceneRenderer.emitScene(DASHBOARD_APP_ID, \"dashboard\", [])\n sceneRenderer.clearApp(DASHBOARD_APP_ID)\n }\n return eligible\n }\n\n stop(): void {\n for (const unsub of this.unsubs) unsub()\n this.unsubs = []\n if (this.clockTimer !== null) BgTimer.clearInterval(this.clockTimer)\n if (this.calendarTimer !== null) BgTimer.clearInterval(this.calendarTimer)\n this.clockTimer = null\n this.calendarTimer = null\n this.open = false\n this.eligible = false\n this.resetNavigation()\n this.started = false\n sceneRenderer.clearApp(DASHBOARD_APP_ID)\n }\n\n isEligible(): boolean {\n if (useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_enabled.key) === false) return false\n const state = useGlassesStore.getState()\n return (\n isGlassesConnected(state.connection) &&\n isCustomDashboardEligible({\n model: state.deviceModel,\n firmwareVersion: state.firmwareVersion,\n })\n )\n }\n\n get isOpen(): boolean {\n return this.open\n }\n\n setHeadUp(up: boolean): void {\n if (!this.isEligible()) {\n this.open = false\n return\n }\n if (up && !this.open) this.resetNavigation()\n this.open = up\n if (up) void this.refreshCalendarAndRender()\n }\n\n /** Toggle the custom dashboard for an eligible device. */\n toggle(): boolean {\n if (!this.isEligible()) return false\n this.open = !this.open\n if (this.open) {\n this.resetNavigation()\n void this.refreshCalendarAndRender()\n }\n void Promise.resolve(BluetoothSdk.toggleHeadUp()).catch((error) => {\n this.open = !this.open\n console.error(\"CustomDashboard: toggleHeadUp failed\", error)\n })\n return true\n }\n\n /** Returns true when an open custom dashboard consumed the gesture. */\n handleGesture(gestureName: string): boolean {\n if (!this.open || !this.isEligible()) return false\n if (gestureName === \"double_tap\") {\n // In the app menu a double tap backs out to the top of the dashboard\n // (there is no Back row); on the main view it closes the dashboard.\n if (this.view === \"apps\") {\n this.resetNavigation()\n this.render()\n return true\n }\n return this.toggle()\n }\n if (this.view === \"apps\") return this.handleAppMenuGesture(gestureName)\n return this.handleMainGesture(gestureName)\n }\n\n private resetNavigation(): void {\n this.view = \"main\"\n this.mainIndex = 0\n this.appsIndex = 0\n }\n\n private handleMainGesture(gestureName: string): boolean {\n const actions = dashboardActions(this.widgets())\n if (this.mainIndex >= actions.length) this.mainIndex = Math.max(0, actions.length - 1)\n if (gestureName === \"swipe_down\" || gestureName === \"swipe_up\") {\n if (actions.length === 0) return true\n const delta = gestureName === \"swipe_down\" ? 1 : -1\n this.mainIndex = (this.mainIndex + delta + actions.length) % actions.length\n this.render()\n return true\n }\n if (gestureName === \"single_tap\") {\n const action = actions[this.mainIndex]\n if (action?.kind === \"apps-menu\") {\n this.view = \"apps\"\n this.appsIndex = 0\n this.render()\n } else if (action?.kind === \"notifications\") {\n void this.openNotificationsPanel()\n }\n return true\n }\n return false\n }\n\n private handleAppMenuGesture(gestureName: string): boolean {\n const rows = appMenuActions(this.menuApps())\n if (this.appsIndex >= rows.length) this.appsIndex = Math.max(0, rows.length - 1)\n if (gestureName === \"swipe_down\" || gestureName === \"swipe_up\") {\n const delta = gestureName === \"swipe_down\" ? 1 : -1\n this.appsIndex = (this.appsIndex + delta + rows.length) % rows.length\n this.render()\n return true\n }\n if (gestureName === \"single_tap\") {\n const row = rows[this.appsIndex]\n if (row?.kind === \"app\") {\n void this.toggleApp(row.packageName)\n }\n return true\n }\n return false\n }\n\n private menuApps(): ClientApp[] {\n const menu = useSettingsStore.getState().getSetting(SETTINGS.menu_apps.key) as MenuAppSetting[] | null\n return orderedDashboardApps(menu, useAppStatusStore.getState().apps)\n }\n\n private widgets(): DashboardWidgetSetting[] {\n return normalizeDashboardWidgets(useSettingsStore.getState().getSetting(SETTINGS.custom_dashboard_widgets.key))\n }\n\n private async close(): Promise<void> {\n if (!this.open) return\n this.open = false\n await Promise.resolve(BluetoothSdk.toggleHeadUp()).catch((error) => {\n console.error(\"CustomDashboard: close failed\", error)\n })\n }\n\n private async openNotificationsPanel(): Promise<void> {\n await this.close()\n await BluetoothSdk.showNativeNotificationPanel().catch((error) => {\n console.error(\"CustomDashboard: notification panel failed\", error)\n })\n }\n\n private async toggleApp(packageName: string): Promise<void> {\n await this.close()\n const app = useAppStatusStore.getState().apps.find((candidate) => candidate.packageName === packageName)\n if (!app) return\n if (app.running) {\n await useAppStatusStore.getState().stop(app.packageName)\n } else {\n await useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }\n\n private calendarWidgetEnabled(): boolean {\n return this.widgets().some((widget) => widget.id === \"calendar\" && widget.enabled)\n }\n\n private ensureContextualDashboard(): void {\n const settings = useSettingsStore.getState()\n if (settings.getSetting(SETTINGS.contextual_dashboard.key) !== true) {\n // DeviceManager selects its retained dashboard Scene only while this is\n // enabled. Custom firmware eligibility makes that dashboard automatic.\n void settings.setSetting(SETTINGS.contextual_dashboard.key, true, false)\n }\n }\n\n private async refreshCalendarAndRender(): Promise<void> {\n if (!this.calendarWidgetEnabled()) {\n this.calendarStatus = \"loading\"\n this.calendarEvents = []\n this.render()\n return\n }\n const settings = useSettingsStore.getState()\n const count = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_event_count.key) ?? 2)\n const days = Number(settings.getSetting(SETTINGS.custom_dashboard_calendar_days_ahead.key) ?? 3)\n const startsAt = new Date()\n const endsAt = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000)\n try {\n const result = await listPhoneCalendarEvents({\n startsAt: startsAt.toISOString(),\n endsAt: endsAt.toISOString(),\n limit: Math.max(1, Math.min(3, count)),\n })\n this.calendarStatus = \"ok\"\n this.calendarEvents = result.events\n } catch (error) {\n // Permission is requested from the Custom Dashboard settings screen;\n // only a real denial should read as one — any other failure is\n // \"Unavailable\" (and logged) so it can't masquerade as a permission\n // problem.\n const denied = error instanceof PhoneCalendarError && error.code === MiniappErrorCode.PERMISSION_DENIED\n this.calendarStatus = denied ? \"no-access\" : \"unavailable\"\n this.calendarEvents = []\n if (!denied) console.warn(\"CustomDashboard: calendar fetch failed\", error)\n }\n this.render()\n }\n\n private canvas(): {width: number; height: number} {\n const {sceneCanvasWidth, sceneCanvasHeight} = useGlassesStore.getState()\n if (sceneCanvasWidth && sceneCanvasWidth > 0 && sceneCanvasHeight && sceneCanvasHeight > 0) {\n return {width: sceneCanvasWidth, height: sceneCanvasHeight}\n }\n return FALLBACK_CANVAS\n }\n\n private render(): void {\n if (!this.isEligible()) return\n const elements = this.view === \"apps\" ? this.appMenuElements() : this.mainElements()\n sceneRenderer.emitScene(DASHBOARD_APP_ID, \"dashboard\", elements)\n }\n\n private mainElements(): SceneElementInput[] {\n const canvas = this.canvas()\n const settings = useSettingsStore.getState()\n const widgets = this.widgets().filter((widget) => widget.enabled)\n const actions = dashboardActions(widgets)\n if (this.mainIndex >= actions.length) this.mainIndex = Math.max(0, actions.length - 1)\n const selected = actions[this.mainIndex]\n\n const clockStyle =\n (settings.getSetting(SETTINGS.custom_dashboard_clock_style.key) as CustomDashboardClockStyle) === \"large\"\n ? \"large\"\n : \"compact\"\n const calendarEnabled = widgets.some((widget) => widget.id === \"calendar\")\n // Calendar lives in the right half; everything else stacks in the left\n // half (full width when the calendar widget is off).\n const halfW = Math.floor(canvas.width / 2) - MARGIN_X - Math.floor(WIDGET_GAP / 2)\n const width = calendarEnabled ? halfW : canvas.width - MARGIN_X * 2\n const rightX = Math.floor(canvas.width / 2) + Math.floor(WIDGET_GAP / 2)\n\n const elements: SceneElementInput[] = []\n let y = MARGIN_TOP\n for (const widget of widgets) {\n if (widget.id === \"clock\") {\n const fontSize = clockStyle === \"large\" ? 48 : 32\n const clock = formatDashboardClock(new Date(), {\n twelveHour: settings.getSetting(SETTINGS.twelve_hour_time.key) === true,\n showDate: settings.getSetting(SETTINGS.custom_dashboard_clock_show_date.key) !== false,\n })\n elements.push({\n type: \"text\",\n id: \"clock\",\n box: {x: MARGIN_X, y, w: width, h: fontSize + 6},\n text: clock.time,\n style: {font: \"mono\", fontSize, breakMode: \"strict-word\"},\n })\n y += fontSize + 6\n if (clock.date) {\n elements.push({\n type: \"text\",\n id: \"clock-date\",\n box: {x: MARGIN_X, y, w: width, h: LINE_H},\n text: clock.date,\n style: {font: \"builtin\", breakMode: \"strict-word\"},\n })\n y += LINE_H\n }\n y += WIDGET_GAP\n } else if (widget.id === \"calendar\") {\n // Right half, top-aligned, independent of the left stack. A missing\n // calendar shows a hint instead of silently vanishing — \"No access\"\n // strictly for permission denial, \"Unavailable\" for any other failure.\n const lines =\n this.calendarStatus === \"ok\"\n ? [\n \"Calendar\",\n ...(this.calendarEvents.length\n ? this.calendarEvents.slice(0, 3).map(eventLine)\n : [\"No upcoming events\"]),\n ]\n : this.calendarStatus === \"loading\"\n ? [\"Calendar\"]\n : [\"Calendar\", this.calendarStatus === \"no-access\" ? \"No access\" : \"Unavailable\"]\n elements.push({\n type: \"text\",\n id: \"calendar\",\n box: {x: rightX, y: MARGIN_TOP, w: halfW, h: lines.length * LINE_H},\n text: lines.join(\"\\n\"),\n style: {font: \"builtin\", breakMode: \"strict-word\", overflow: \"ellipsis\"},\n })\n } else if (widget.id === \"apps\") {\n const isSelected = selected?.kind === \"apps-menu\"\n elements.push({\n type: \"text\",\n id: \"apps-button\",\n box: {x: MARGIN_X, y, w: width, h: BUTTON_H},\n text: \"Apps\",\n style: {font: \"builtin\", border: isSelected ? 2 : 0, radius: 8},\n })\n y += BUTTON_H + WIDGET_GAP\n } else if (widget.id === \"notifications\") {\n const isSelected = selected?.kind === \"notifications\"\n elements.push({\n type: \"text\",\n id: \"notifications\",\n box: {x: MARGIN_X, y, w: width, h: BUTTON_H},\n text: \"Notifications\",\n style: {font: \"builtin\", border: isSelected ? 2 : 0, radius: 8},\n })\n y += BUTTON_H + WIDGET_GAP\n }\n }\n return elements\n }\n\n /**\n * Resolve a miniapp icon to base64 for the glasses. Local/bundled miniapps\n * carry a file:// icon.png; store apps an https URL; the offline system\n * tiles a bundler asset id (unsupported here — monogram fallback). Loads\n * are async and cached; a finished load repaints the open app menu.\n */\n private loadIcon(app: ClientApp): void {\n if (this.iconCache.has(app.packageName) || this.iconLoads.has(app.packageName)) return\n const url = typeof app.logoUrl === \"string\" ? app.logoUrl : \"\"\n if (!/^(file|https?):/.test(url)) {\n this.iconCache.set(app.packageName, null)\n return\n }\n this.iconLoads.add(app.packageName)\n void (async () => {\n try {\n let base64: string\n if (url.startsWith(\"file:\")) {\n base64 = await new File(url).base64()\n } else {\n const response = await fetch(url)\n const blob = await response.blob()\n base64 = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(String(reader.result).split(\",\")[1] ?? \"\")\n reader.onerror = () => reject(reader.error)\n reader.readAsDataURL(blob)\n })\n }\n this.iconCache.set(app.packageName, base64 || null)\n } catch {\n this.iconCache.set(app.packageName, null)\n } finally {\n this.iconLoads.delete(app.packageName)\n if (this.open && this.view === \"apps\") this.render()\n }\n })()\n }\n\n private appMenuElements(): SceneElementInput[] {\n const canvas = this.canvas()\n const width = canvas.width - MARGIN_X * 2\n const apps = this.menuApps()\n const rows = appMenuActions(apps)\n if (this.appsIndex >= rows.length) this.appsIndex = Math.max(0, rows.length - 1)\n\n const elements: SceneElementInput[] = [\n {\n type: \"text\",\n id: \"apps-title\",\n box: {x: MARGIN_X, y: MARGIN_TOP, w: width, h: LINE_H},\n text: \"Apps\",\n style: {font: \"builtin\"},\n },\n ]\n const listTop = MARGIN_TOP + LINE_H + 16\n if (apps.length === 0) {\n elements.push({\n type: \"text\",\n id: \"apps-empty\",\n box: {x: MARGIN_X, y: listTop, w: width, h: LINE_H},\n text: \"No apps in Glasses Menu\",\n style: {font: \"builtin\"},\n })\n return elements\n }\n\n // Icon grid, paged, row-major. Selection is the bordered cell.\n const perPage = APPS_PER_PAGE\n const pageStart = Math.floor(this.appsIndex / perPage) * perPage\n const page = apps.slice(pageStart, pageStart + perPage)\n const cols = Math.min(page.length, APP_GRID_COLS)\n const totalW = cols * APP_CELL + (cols - 1) * APP_CELL_GAP\n const startX = Math.max(MARGIN_X, Math.round((canvas.width - totalW) / 2))\n page.forEach((app, cell) => {\n const x = startX + (cell % APP_GRID_COLS) * (APP_CELL + APP_CELL_GAP)\n const y = listTop + Math.floor(cell / APP_GRID_COLS) * (APP_CELL + APP_CELL_GAP)\n const selected = pageStart + cell === this.appsIndex\n const icon = this.iconCache.get(app.packageName)\n if (icon === undefined) this.loadIcon(app)\n if (icon) {\n elements.push({\n type: \"image\",\n id: `app-icon:${app.packageName}`,\n box: {x: x + APP_ICON_INSET, y: y + APP_ICON_INSET, w: APP_ICON, h: APP_ICON},\n data: icon,\n })\n } else {\n // Monogram fallback for icons that can't load (bundler-asset tiles).\n elements.push({\n type: \"text\",\n id: `app-icon:${app.packageName}`,\n box: {x: x + APP_ICON_INSET, y: y + APP_ICON_INSET, w: APP_ICON, h: APP_ICON},\n text: (app.name.trim()[0] ?? \"?\").toUpperCase(),\n style: {font: \"mono\", fontSize: 20},\n })\n }\n if (selected) {\n elements.push({\n type: \"rect\",\n id: \"apps-selection\",\n box: {x, y, w: APP_CELL, h: APP_CELL},\n style: {border: 2, radius: 6},\n })\n }\n })\n\n // Grayscale mini icons are ambiguous on their own: caption the selection.\n const gridRows = Math.ceil(page.length / APP_GRID_COLS)\n const gridH = gridRows * APP_CELL + (gridRows - 1) * APP_CELL_GAP\n const selectedApp = apps[this.appsIndex]\n if (selectedApp) {\n elements.push({\n type: \"text\",\n id: \"apps-selected-name\",\n box: {x: MARGIN_X, y: listTop + gridH + 12, w: width, h: LINE_H},\n text: `${selectedApp.name}${selectedApp.running ? \" ●\" : \"\"}`,\n style: {font: \"builtin\", breakMode: \"strict-word\", overflow: \"ellipsis\"},\n })\n }\n return elements\n }\n}\n\nconst customDashboardService = new CustomDashboardService()\nexport default customDashboardService\nexport {CustomDashboardService}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"effectiveCapabilities.d.ts","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"AAQA,OAAO,EAAuB,KAAK,YAAY,EAAE,KAAK,WAAW,EAAC,MAAM,UAAU,CAAA;AAElF,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,IAAI,CAI3D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAKzE"}
1
+ {"version":3,"file":"effectiveCapabilities.d.ts","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"AAWA,OAAO,EAAuB,KAAK,YAAY,EAAE,KAAK,WAAW,EAAC,MAAM,UAAU,CAAA;AAKlF,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,IAAI,CAI3D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAczE"}
@@ -1,12 +1,17 @@
1
1
  /**
2
2
  * Effective device capabilities — the static model profile plus what the
3
- * connected firmware reports at runtime. Today the only runtime override is
4
- * the scene canvas: Glassly CFW on the G2 rasterizes scenes phone-side
5
- * straight into the 640×480 panel, so the drawable canvas grows past stock's
6
- * 576×288. Native publishes the canvas into the glasses status (0 = none).
3
+ * connected firmware reports at runtime. The runtime override is the scene
4
+ * canvas: Glassly CFW on the G2 rasterizes scenes phone-side straight into
5
+ * the 640×480 panel, so the drawable canvas grows past stock's 576×288.
6
+ * Native publishes the canvas into the glasses status (0 = none). Because
7
+ * that path is phone-side rasterization, the stock firmware's container
8
+ * pools (6 text / 4 image) don't bound a CFW frame either — element caps are
9
+ * lifted alongside the canvas, bounded only to keep frames sane.
7
10
  */
8
11
  import { useGlassesStore } from "../stores/glasses";
9
12
  import { getModelCapabilities } from "../types";
13
+ const CFW_MAX_TEXT_ELEMENTS = 24;
14
+ const CFW_MAX_IMAGE_ELEMENTS = 16;
10
15
  export function runtimeSceneCanvas() {
11
16
  const { sceneCanvasWidth, sceneCanvasHeight } = useGlassesStore.getState();
12
17
  if (!sceneCanvasWidth || !sceneCanvasHeight || sceneCanvasWidth <= 0 || sceneCanvasHeight <= 0)
@@ -18,6 +23,15 @@ export function getEffectiveCapabilities(model) {
18
23
  const canvas = runtimeSceneCanvas();
19
24
  if (!base.display || !canvas)
20
25
  return base;
21
- return { ...base, display: { ...base.display, width: canvas.width, height: canvas.height } };
26
+ return {
27
+ ...base,
28
+ display: {
29
+ ...base.display,
30
+ width: canvas.width,
31
+ height: canvas.height,
32
+ maxTextElements: Math.max(base.display.maxTextElements ?? 0, CFW_MAX_TEXT_ELEMENTS),
33
+ maxImageElements: Math.max(base.display.maxImageElements ?? 0, CFW_MAX_IMAGE_ELEMENTS),
34
+ },
35
+ };
22
36
  }
23
37
  //# sourceMappingURL=effectiveCapabilities.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"effectiveCapabilities.js","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,oBAAoB,EAAsC,MAAM,UAAU,CAAA;AAOlF,MAAM,UAAU,kBAAkB;IAChC,MAAM,EAAC,gBAAgB,EAAE,iBAAiB,EAAC,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;IACxE,IAAI,CAAC,gBAAgB,IAAI,CAAC,iBAAiB,IAAI,gBAAgB,IAAI,CAAC,IAAI,iBAAiB,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAC3G,OAAO,EAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,iBAAiB,EAAC,CAAA;AAC7D,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAAkB;IACzD,MAAM,IAAI,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAA;IACnC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO,EAAC,GAAG,IAAI,EAAE,OAAO,EAAE,EAAC,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAC,EAAC,CAAA;AAC1F,CAAC","sourcesContent":["/**\n * Effective device capabilities — the static model profile plus what the\n * connected firmware reports at runtime. Today the only runtime override is\n * the scene canvas: Glassly CFW on the G2 rasterizes scenes phone-side\n * straight into the 640×480 panel, so the drawable canvas grows past stock's\n * 576×288. Native publishes the canvas into the glasses status (0 = none).\n */\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {getModelCapabilities, type Capabilities, type DeviceTypes} from \"../types\"\n\nexport interface SceneCanvasSize {\n width: number\n height: number\n}\n\nexport function runtimeSceneCanvas(): SceneCanvasSize | null {\n const {sceneCanvasWidth, sceneCanvasHeight} = useGlassesStore.getState()\n if (!sceneCanvasWidth || !sceneCanvasHeight || sceneCanvasWidth <= 0 || sceneCanvasHeight <= 0) return null\n return {width: sceneCanvasWidth, height: sceneCanvasHeight}\n}\n\nexport function getEffectiveCapabilities(model: DeviceTypes): Capabilities {\n const base = getModelCapabilities(model)\n const canvas = runtimeSceneCanvas()\n if (!base.display || !canvas) return base\n return {...base, display: {...base.display, width: canvas.width, height: canvas.height}}\n}\n"]}
1
+ {"version":3,"file":"effectiveCapabilities.js","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,oBAAoB,EAAsC,MAAM,UAAU,CAAA;AAElF,MAAM,qBAAqB,GAAG,EAAE,CAAA;AAChC,MAAM,sBAAsB,GAAG,EAAE,CAAA;AAOjC,MAAM,UAAU,kBAAkB;IAChC,MAAM,EAAC,gBAAgB,EAAE,iBAAiB,EAAC,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;IACxE,IAAI,CAAC,gBAAgB,IAAI,CAAC,iBAAiB,IAAI,gBAAgB,IAAI,CAAC,IAAI,iBAAiB,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAC3G,OAAO,EAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,iBAAiB,EAAC,CAAA;AAC7D,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAAkB;IACzD,MAAM,IAAI,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAA;IACnC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO;QACL,GAAG,IAAI;QACP,OAAO,EAAE;YACP,GAAG,IAAI,CAAC,OAAO;YACf,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,eAAe,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,IAAI,CAAC,EAAE,qBAAqB,CAAC;YACnF,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,IAAI,CAAC,EAAE,sBAAsB,CAAC;SACvF;KACF,CAAA;AACH,CAAC","sourcesContent":["/**\n * Effective device capabilities — the static model profile plus what the\n * connected firmware reports at runtime. The runtime override is the scene\n * canvas: Glassly CFW on the G2 rasterizes scenes phone-side straight into\n * the 640×480 panel, so the drawable canvas grows past stock's 576×288.\n * Native publishes the canvas into the glasses status (0 = none). Because\n * that path is phone-side rasterization, the stock firmware's container\n * pools (6 text / 4 image) don't bound a CFW frame either — element caps are\n * lifted alongside the canvas, bounded only to keep frames sane.\n */\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {getModelCapabilities, type Capabilities, type DeviceTypes} from \"../types\"\n\nconst CFW_MAX_TEXT_ELEMENTS = 24\nconst CFW_MAX_IMAGE_ELEMENTS = 16\n\nexport interface SceneCanvasSize {\n width: number\n height: number\n}\n\nexport function runtimeSceneCanvas(): SceneCanvasSize | null {\n const {sceneCanvasWidth, sceneCanvasHeight} = useGlassesStore.getState()\n if (!sceneCanvasWidth || !sceneCanvasHeight || sceneCanvasWidth <= 0 || sceneCanvasHeight <= 0) return null\n return {width: sceneCanvasWidth, height: sceneCanvasHeight}\n}\n\nexport function getEffectiveCapabilities(model: DeviceTypes): Capabilities {\n const base = getModelCapabilities(model)\n const canvas = runtimeSceneCanvas()\n if (!base.display || !canvas) return base\n return {\n ...base,\n display: {\n ...base.display,\n width: canvas.width,\n height: canvas.height,\n maxTextElements: Math.max(base.display.maxTextElements ?? 0, CFW_MAX_TEXT_ELEMENTS),\n maxImageElements: Math.max(base.display.maxImageElements ?? 0, CFW_MAX_IMAGE_ELEMENTS),\n },\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassly/engine",
3
- "version": "0.1.0-dev.31",
3
+ "version": "0.1.0-dev.35",
4
4
  "description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -97,11 +97,11 @@
97
97
  "registry": "https://registry.npmjs.org/"
98
98
  },
99
99
  "dependencies": {
100
- "@glassly/bluetooth-sdk": "0.1.0-dev.31",
101
- "@glassly/cloud-client": "0.1.0-dev.31",
102
- "@glassly/cloud-protocol": "0.1.0-dev.31",
103
- "@glassly/crust": "0.1.0-dev.31",
104
- "@glassly/miniapp": "0.1.0-dev.31",
100
+ "@glassly/bluetooth-sdk": "0.1.0-dev.35",
101
+ "@glassly/cloud-client": "0.1.0-dev.35",
102
+ "@glassly/cloud-protocol": "0.1.0-dev.35",
103
+ "@glassly/crust": "0.1.0-dev.35",
104
+ "@glassly/miniapp": "0.1.0-dev.35",
105
105
  "buffer": "^6.0.3",
106
106
  "events": "^3.3.0",
107
107
  "semver": "^7.7.4",
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
12
12
  export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "0.1.0",
15
- "releaseIdentity": "0.1.0-dev.31",
16
- "releaseSetId": "glassly-0.1.0-dev.31",
17
- "sourceCommit": "f344cf647ef3cd00444902f1154a7f5ad7a0fe52",
15
+ "releaseIdentity": "0.1.0-dev.35",
16
+ "releaseSetId": "glassly-0.1.0-dev.35",
17
+ "sourceCommit": "6909e863991061a55c153d51bd2c4384a0f4c0a3",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
@@ -13,7 +13,7 @@
13
13
  import {File} from "expo-file-system"
14
14
 
15
15
  import {BluetoothSdk} from "@glassly/bluetooth-sdk"
16
- import type {CalendarEvent} from "@glassly/miniapp"
16
+ import {MiniappErrorCode, type CalendarEvent} from "@glassly/miniapp"
17
17
 
18
18
  import {useAppStatusStore} from "../stores/apps"
19
19
  import {useGlassesStore} from "../stores/glasses"
@@ -33,7 +33,7 @@ import {
33
33
  type MenuAppSetting,
34
34
  } from "./CustomDashboardModel"
35
35
  import {isGlassesConnected} from "./GlassesReadiness"
36
- import {listPhoneCalendarEvents} from "./PhoneCalendarService"
36
+ import {listPhoneCalendarEvents, PhoneCalendarError} from "./PhoneCalendarService"
37
37
  import sceneRenderer from "./SceneRenderer"
38
38
 
39
39
  const DASHBOARD_APP_ID = "system.custom-dashboard"
@@ -49,11 +49,18 @@ const WIDGET_GAP = 10
49
49
  /** Row height for the glasses' builtin 20px font engine. */
50
50
  const LINE_H = 24
51
51
  const BUTTON_H = 34
52
- /** App-menu icon grid: 4 per page (G2 firmware image-container pool). */
53
- const APPS_PER_PAGE = 4
54
- const APP_CELL = 72
55
- const APP_CELL_GAP = 16
56
- const APP_ICON_INSET = 4
52
+ /**
53
+ * App-menu icon grid: 2 rows × 4 columns per page. CFW rasterizes scenes
54
+ * phone-side, so the stock firmware's 4-image container pool does not bound
55
+ * the page (getEffectiveCapabilities lifts the element caps while the CFW
56
+ * canvas is active).
57
+ */
58
+ const APP_GRID_COLS = 4
59
+ const APP_GRID_ROWS = 2
60
+ const APPS_PER_PAGE = APP_GRID_COLS * APP_GRID_ROWS
61
+ const APP_CELL = 36
62
+ const APP_CELL_GAP = 12
63
+ const APP_ICON_INSET = 2
57
64
  const APP_ICON = APP_CELL - APP_ICON_INSET * 2
58
65
 
59
66
  function eventLine(event: CalendarEvent): string {
@@ -69,7 +76,8 @@ class CustomDashboardService {
69
76
  private view: "main" | "apps" = "main"
70
77
  private mainIndex = 0
71
78
  private appsIndex = 0
72
- private calendarAvailable = false
79
+ /** "loading" until the first fetch resolves; "no-access" only for a real permission denial. */
80
+ private calendarStatus: "loading" | "ok" | "no-access" | "unavailable" = "loading"
73
81
  /** packageName → base64 icon bytes, or null when the icon can't be loaded (monogram fallback). */
74
82
  private iconCache = new Map<string, string | null>()
75
83
  private iconLoads = new Set<string>()
@@ -321,7 +329,7 @@ class CustomDashboardService {
321
329
 
322
330
  private async refreshCalendarAndRender(): Promise<void> {
323
331
  if (!this.calendarWidgetEnabled()) {
324
- this.calendarAvailable = false
332
+ this.calendarStatus = "loading"
325
333
  this.calendarEvents = []
326
334
  this.render()
327
335
  return
@@ -332,14 +340,22 @@ class CustomDashboardService {
332
340
  const startsAt = new Date()
333
341
  const endsAt = new Date(startsAt.getTime() + Math.max(1, days) * 24 * 60 * 60 * 1000)
334
342
  try {
335
- const result = await listPhoneCalendarEvents({startsAt, endsAt, limit: Math.max(1, Math.min(3, count))})
336
- this.calendarAvailable = true
343
+ const result = await listPhoneCalendarEvents({
344
+ startsAt: startsAt.toISOString(),
345
+ endsAt: endsAt.toISOString(),
346
+ limit: Math.max(1, Math.min(3, count)),
347
+ })
348
+ this.calendarStatus = "ok"
337
349
  this.calendarEvents = result.events
338
- } catch {
350
+ } catch (error) {
339
351
  // Permission is requested from the Custom Dashboard settings screen;
340
- // without access the widget silently stays off the glasses.
341
- this.calendarAvailable = false
352
+ // only a real denial should read as one — any other failure is
353
+ // "Unavailable" (and logged) so it can't masquerade as a permission
354
+ // problem.
355
+ const denied = error instanceof PhoneCalendarError && error.code === MiniappErrorCode.PERMISSION_DENIED
356
+ this.calendarStatus = denied ? "no-access" : "unavailable"
342
357
  this.calendarEvents = []
358
+ if (!denied) console.warn("CustomDashboard: calendar fetch failed", error)
343
359
  }
344
360
  this.render()
345
361
  }
@@ -407,16 +423,19 @@ class CustomDashboardService {
407
423
  y += WIDGET_GAP
408
424
  } else if (widget.id === "calendar") {
409
425
  // Right half, top-aligned, independent of the left stack. A missing
410
- // calendar (permission not granted, fetch failed) shows a hint
411
- // instead of silently vanishing.
412
- const lines = this.calendarAvailable
413
- ? [
414
- "Calendar",
415
- ...(this.calendarEvents.length
416
- ? this.calendarEvents.slice(0, 3).map(eventLine)
417
- : ["No upcoming events"]),
418
- ]
419
- : ["Calendar", "No access"]
426
+ // calendar shows a hint instead of silently vanishing — "No access"
427
+ // strictly for permission denial, "Unavailable" for any other failure.
428
+ const lines =
429
+ this.calendarStatus === "ok"
430
+ ? [
431
+ "Calendar",
432
+ ...(this.calendarEvents.length
433
+ ? this.calendarEvents.slice(0, 3).map(eventLine)
434
+ : ["No upcoming events"]),
435
+ ]
436
+ : this.calendarStatus === "loading"
437
+ ? ["Calendar"]
438
+ : ["Calendar", this.calendarStatus === "no-access" ? "No access" : "Unavailable"]
420
439
  elements.push({
421
440
  type: "text",
422
441
  id: "calendar",
@@ -516,23 +535,24 @@ class CustomDashboardService {
516
535
  return elements
517
536
  }
518
537
 
519
- // Icon grid, paged: the G2 firmware image-container pool is 4, so at most
520
- // four icon images may be on a page. Selection is the bordered cell.
538
+ // Icon grid, paged, row-major. Selection is the bordered cell.
521
539
  const perPage = APPS_PER_PAGE
522
540
  const pageStart = Math.floor(this.appsIndex / perPage) * perPage
523
541
  const page = apps.slice(pageStart, pageStart + perPage)
524
- const totalW = page.length * APP_CELL + (page.length - 1) * APP_CELL_GAP
542
+ const cols = Math.min(page.length, APP_GRID_COLS)
543
+ const totalW = cols * APP_CELL + (cols - 1) * APP_CELL_GAP
525
544
  const startX = Math.max(MARGIN_X, Math.round((canvas.width - totalW) / 2))
526
- page.forEach((app, column) => {
527
- const x = startX + column * (APP_CELL + APP_CELL_GAP)
528
- const selected = pageStart + column === this.appsIndex
545
+ page.forEach((app, cell) => {
546
+ const x = startX + (cell % APP_GRID_COLS) * (APP_CELL + APP_CELL_GAP)
547
+ const y = listTop + Math.floor(cell / APP_GRID_COLS) * (APP_CELL + APP_CELL_GAP)
548
+ const selected = pageStart + cell === this.appsIndex
529
549
  const icon = this.iconCache.get(app.packageName)
530
550
  if (icon === undefined) this.loadIcon(app)
531
551
  if (icon) {
532
552
  elements.push({
533
553
  type: "image",
534
554
  id: `app-icon:${app.packageName}`,
535
- box: {x: x + APP_ICON_INSET, y: listTop + APP_ICON_INSET, w: APP_ICON, h: APP_ICON},
555
+ box: {x: x + APP_ICON_INSET, y: y + APP_ICON_INSET, w: APP_ICON, h: APP_ICON},
536
556
  data: icon,
537
557
  })
538
558
  } else {
@@ -540,28 +560,30 @@ class CustomDashboardService {
540
560
  elements.push({
541
561
  type: "text",
542
562
  id: `app-icon:${app.packageName}`,
543
- box: {x: x + APP_ICON_INSET, y: listTop + APP_ICON_INSET, w: APP_ICON, h: APP_ICON},
563
+ box: {x: x + APP_ICON_INSET, y: y + APP_ICON_INSET, w: APP_ICON, h: APP_ICON},
544
564
  text: (app.name.trim()[0] ?? "?").toUpperCase(),
545
- style: {font: "mono", fontSize: 40},
565
+ style: {font: "mono", fontSize: 20},
546
566
  })
547
567
  }
548
568
  if (selected) {
549
569
  elements.push({
550
570
  type: "rect",
551
571
  id: "apps-selection",
552
- box: {x, y: listTop, w: APP_CELL, h: APP_CELL},
553
- style: {border: 2, radius: 8},
572
+ box: {x, y, w: APP_CELL, h: APP_CELL},
573
+ style: {border: 2, radius: 6},
554
574
  })
555
575
  }
556
576
  })
557
577
 
558
578
  // Grayscale mini icons are ambiguous on their own: caption the selection.
579
+ const gridRows = Math.ceil(page.length / APP_GRID_COLS)
580
+ const gridH = gridRows * APP_CELL + (gridRows - 1) * APP_CELL_GAP
559
581
  const selectedApp = apps[this.appsIndex]
560
582
  if (selectedApp) {
561
583
  elements.push({
562
584
  type: "text",
563
585
  id: "apps-selected-name",
564
- box: {x: MARGIN_X, y: listTop + APP_CELL + 12, w: width, h: LINE_H},
586
+ box: {x: MARGIN_X, y: listTop + gridH + 12, w: width, h: LINE_H},
565
587
  text: `${selectedApp.name}${selectedApp.running ? " ●" : ""}`,
566
588
  style: {font: "builtin", breakMode: "strict-word", overflow: "ellipsis"},
567
589
  })
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * Effective device capabilities — the static model profile plus what the
3
- * connected firmware reports at runtime. Today the only runtime override is
4
- * the scene canvas: Glassly CFW on the G2 rasterizes scenes phone-side
5
- * straight into the 640×480 panel, so the drawable canvas grows past stock's
6
- * 576×288. Native publishes the canvas into the glasses status (0 = none).
3
+ * connected firmware reports at runtime. The runtime override is the scene
4
+ * canvas: Glassly CFW on the G2 rasterizes scenes phone-side straight into
5
+ * the 640×480 panel, so the drawable canvas grows past stock's 576×288.
6
+ * Native publishes the canvas into the glasses status (0 = none). Because
7
+ * that path is phone-side rasterization, the stock firmware's container
8
+ * pools (6 text / 4 image) don't bound a CFW frame either — element caps are
9
+ * lifted alongside the canvas, bounded only to keep frames sane.
7
10
  */
8
11
  import {useGlassesStore} from "../stores/glasses"
9
12
  import {getModelCapabilities, type Capabilities, type DeviceTypes} from "../types"
10
13
 
14
+ const CFW_MAX_TEXT_ELEMENTS = 24
15
+ const CFW_MAX_IMAGE_ELEMENTS = 16
16
+
11
17
  export interface SceneCanvasSize {
12
18
  width: number
13
19
  height: number
@@ -23,5 +29,14 @@ export function getEffectiveCapabilities(model: DeviceTypes): Capabilities {
23
29
  const base = getModelCapabilities(model)
24
30
  const canvas = runtimeSceneCanvas()
25
31
  if (!base.display || !canvas) return base
26
- return {...base, display: {...base.display, width: canvas.width, height: canvas.height}}
32
+ return {
33
+ ...base,
34
+ display: {
35
+ ...base.display,
36
+ width: canvas.width,
37
+ height: canvas.height,
38
+ maxTextElements: Math.max(base.display.maxTextElements ?? 0, CFW_MAX_TEXT_ELEMENTS),
39
+ maxImageElements: Math.max(base.display.maxImageElements ?? 0, CFW_MAX_IMAGE_ELEMENTS),
40
+ },
41
+ }
27
42
  }