diff --git a/public/static/images/appstore.svg b/public/static/images/appstore.svg new file mode 100644 index 0000000000..da50ed4ce7 --- /dev/null +++ b/public/static/images/appstore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/static/images/logo_apple_home.png b/public/static/images/logo_apple_home.png new file mode 100644 index 0000000000..b37416b56b Binary files /dev/null and b/public/static/images/logo_apple_home.png differ diff --git a/public/static/images/logo_google_home.png b/public/static/images/logo_google_home.png new file mode 100644 index 0000000000..1658b336f4 Binary files /dev/null and b/public/static/images/logo_google_home.png differ diff --git a/public/static/images/playstore.svg b/public/static/images/playstore.svg new file mode 100644 index 0000000000..7528a3d650 --- /dev/null +++ b/public/static/images/playstore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/static/images/qr-appstore.svg b/public/static/images/qr-appstore.svg new file mode 100644 index 0000000000..3c6605bb8d --- /dev/null +++ b/public/static/images/qr-appstore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/static/images/qr-playstore.svg b/public/static/images/qr-playstore.svg new file mode 100644 index 0000000000..af088506ae --- /dev/null +++ b/public/static/images/qr-playstore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/onboarding/dialogs/app-dialog.ts b/src/onboarding/dialogs/app-dialog.ts index 3f66189792..1dd5675cc1 100644 --- a/src/onboarding/dialogs/app-dialog.ts +++ b/src/onboarding/dialogs/app-dialog.ts @@ -27,6696 +27,47 @@ class DialogApp extends LitElement { @closed=${this.closeDialog} .heading=${createCloseHeading( undefined, - this.localize("ui.panel.page-onboarding.welcome.download_app") + this.localize("ui.panel.page-onboarding.welcome.download_app") || + "Click here to download the app" )} >
- - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
`; } @@ -6725,31 +76,19 @@ class DialogApp extends LitElement { ha-dialog { --mdc-dialog-min-width: min(500px, 90vw); } - div { + .app-qr { + margin: 24px auto 0 auto; display: flex; justify-content: space-between; - } - a:first-child { - margin-right: 16px; - margin-inline-end: 16px; - margin-inline-start: initial; - } - svg { + padding: 0 24px; + box-sizing: border-box; + gap: 16px; width: 100%; + max-width: 400px; } - .light { - fill: var(--card-background-color); - } - .dark { - fill: var(--primary-text-color); - } - @media (prefers-color-scheme: dark) { - .mask { - fill: var(--primary-text-color); - } - .logo { - filter: invert(100%); - } + .app-qr a, + .app-qr img { + flex: 1; } `; } diff --git a/src/panels/config/integrations/integration-panels/matter/dialog-matter-add-device.ts b/src/panels/config/integrations/integration-panels/matter/dialog-matter-add-device.ts index ed14994f2c..55a5165ffd 100644 --- a/src/panels/config/integrations/integration-panels/matter/dialog-matter-add-device.ts +++ b/src/panels/config/integrations/integration-panels/matter/dialog-matter-add-device.ts @@ -1,17 +1,56 @@ -import "@material/mwc-button/mwc-button"; +import { mdiClose } from "@mdi/js"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import "../../../../../components/ha-circular-progress"; -import { createCloseHeading } from "../../../../../components/ha-dialog"; +import "../../../../../components/ha-dialog-header"; +import "../../../../../components/ha-icon-button"; +import "../../../../../components/ha-icon-button-arrow-prev"; import { - addMatterDevice, - canCommissionMatterExternal, + commissionMatterDevice, redirectOnNewMatterDevice, } from "../../../../../data/matter"; import { haStyleDialog } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; +import "./matter-add-device/matter-add-device-apple-home"; +import "./matter-add-device/matter-add-device-existing"; +import "./matter-add-device/matter-add-device-generic"; +import "./matter-add-device/matter-add-device-google-home"; +import "./matter-add-device/matter-add-device-google-home-fallback"; +import "./matter-add-device/matter-add-device-main"; +import "./matter-add-device/matter-add-device-new"; +import "./matter-add-device/matter-add-device-commissioning"; +import { showToast } from "../../../../../util/toast"; + +export type MatterAddDeviceStep = + | "main" + | "new" + | "existing" + | "google_home" + | "google_home_fallback" + | "apple_home" + | "generic" + | "commissioning"; + +declare global { + interface HASSDomEvents { + "step-selected": { step: MatterAddDeviceStep }; + "pairing-code-changed": { code: string }; + } +} + +const BACK_STEP: Record = + { + main: undefined, + new: "main", + existing: "main", + google_home: "existing", + google_home_fallback: "google_home", + apple_home: "existing", + generic: "existing", + commissioning: undefined, + }; @customElement("dialog-matter-add-device") class DialogMatterAddDevice extends LitElement { @@ -19,50 +58,145 @@ class DialogMatterAddDevice extends LitElement { @state() private _open = false; + @state() _pairingCode = ""; + + @state() _step: MatterAddDeviceStep = "main"; + private _unsub?: UnsubscribeFunc; public showDialog(): void { this._open = true; - if (!canCommissionMatterExternal(this.hass)) { - return; - } this._unsub = redirectOnNewMatterDevice(this.hass, () => this.closeDialog() ); - addMatterDevice(this.hass); } public closeDialog(): void { this._open = false; + this._step = "main"; + this._pairingCode = ""; this._unsub?.(); this._unsub = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } + private _handleStepSelected(ev: CustomEvent) { + this._step = ev.detail.step; + this._pairingCode = ""; + } + + private _handlePairingCodeChanged(ev: CustomEvent) { + this._pairingCode = ev.detail.code; + } + + private _back() { + const backStep = BACK_STEP[this._step]; + if (!backStep) return; + this._step = backStep; + } + + private _renderStep() { + return html` +
+ ${dynamicElement( + `matter-add-device-${this._step.replaceAll("_", "-")}`, + { + hass: this.hass, + } + )} +
+ `; + } + + private async _addDevice() { + const code = this._pairingCode; + const savedStep = this._step; + try { + this._step = "commissioning"; + await commissionMatterDevice(this.hass, code); + } catch (err) { + showToast(this, { + message: this.hass.localize( + "ui.dialogs.matter-add-device.add_device_failed" + ), + duration: 2000, + }); + } + this._step = savedStep; + } + + private _renderActions() { + if ( + this._step === "apple_home" || + this._step === "google_home_fallback" || + this._step === "generic" + ) { + return html` + + ${this.hass.localize("ui.dialogs.matter-add-device.add_device")} + + `; + } + if (this._step === "new") { + return html` + + ${this.hass.localize("ui.common.ok")} + + `; + } + return nothing; + } + protected render() { if (!this._open) { return nothing; } + const title = this.hass.localize( + `ui.dialogs.matter-add-device.${this._step}.header` + ); + + const hasBackStep = BACK_STEP[this._step]; + + const actions = this._renderActions(); + return html` -
- ${!canCommissionMatterExternal(this.hass) - ? this.hass.localize( - "ui.panel.config.integrations.config_flow.matter_mobile_app" - ) - : html``} -
- - ${this.hass.localize("ui.common.cancel")} - + + ${hasBackStep + ? html` + + ` + : html` + + `} + ${title} + + ${this._renderStep()} ${actions}
`; } @@ -70,11 +204,34 @@ class DialogMatterAddDevice extends LitElement { static styles = [ haStyleDialog, css` - div { - display: grid; + :host { + --horizontal-padding: 24px; } - ha-circular-progress { - justify-self: center; + ha-dialog { + --dialog-content-padding: 0; + } + ha-dialog { + --mdc-dialog-min-width: 450px; + --mdc-dialog-max-width: 450px; + } + @media all and (max-width: 450px), all and (max-height: 500px) { + :host { + --horizontal-padding: 16px; + } + ha-dialog { + --mdc-dialog-min-width: calc( + 100vw - env(safe-area-inset-right) - env(safe-area-inset-left) + ); + --mdc-dialog-max-width: calc( + 100vw - env(safe-area-inset-right) - env(safe-area-inset-left) + ); + } + } + .loading { + padding: 24px; + display: flex; + align-items: center; + justify-content: center; } `, ]; diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-apple-home.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-apple-home.ts new file mode 100644 index 0000000000..f8dbef454c --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-apple-home.ts @@ -0,0 +1,80 @@ +import { LitElement, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../../../common/dom/fire_event"; +import "../../../../../../components/ha-icon-next"; +import "../../../../../../components/ha-list-item-new"; +import "../../../../../../components/ha-list-new"; +import { HomeAssistant } from "../../../../../../types"; +import { sharedStyles } from "./matter-add-device-shared-styles"; + +@customElement("matter-add-device-apple-home") +class MatterAddDeviceAppleHome extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _code: string = ""; + + render() { + return html` +
+
    +
  1. + ${this.hass.localize( + "ui.dialogs.matter-add-device.apple_home.step_1", + { + accessory_details: html`${this.hass.localize( + "ui.dialogs.matter-add-device.apple_home.accessory_details" + )}`, + } + )} +
  2. +
  3. + ${this.hass.localize( + "ui.dialogs.matter-add-device.apple_home.step_2", + { + turn_on_pairing_mode: html`${this.hass.localize( + "ui.dialogs.matter-add-device.apple_home.turn_on_pairing_mode" + )}`, + } + )} +
  4. +
  5. + ${this.hass.localize( + "ui.dialogs.matter-add-device.apple_home.step_3" + )} +
  6. +
+
+

+ ${this.hass.localize( + "ui.dialogs.matter-add-device.apple_home.code_instructions" + )} +

+ +
+ `; + } + + private _onCodeChanged(ev: any) { + const value = ev.currentTarget.value; + this._code = value; + fireEvent(this, "pairing-code-changed", { code: value }); + } + + static styles = [sharedStyles]; +} + +declare global { + interface HTMLElementTagNameMap { + "matter-add-device-apple-home": MatterAddDeviceAppleHome; + } +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-commissioning.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-commissioning.ts new file mode 100644 index 0000000000..b7825e1174 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-commissioning.ts @@ -0,0 +1,47 @@ +import { LitElement, css, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import { HomeAssistant } from "../../../../../../types"; +import { sharedStyles } from "./matter-add-device-shared-styles"; +import "../../../../../../components/ha-circular-progress"; + +@customElement("matter-add-device-commissioning") +class MatterAddDeviceCommissioning extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + render() { + return html` +
+ +

+ ${this.hass.localize( + "ui.dialogs.matter-add-device.commissioning.note" + )} +

+
+ `; + } + + static styles = [ + sharedStyles, + css` + .content { + display: flex; + align-items: center; + flex-direction: column; + text-align: center; + } + ha-circular-progress { + margin-bottom: 24px; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "matter-add-device-commissioning": MatterAddDeviceCommissioning; + } +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-existing.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-existing.ts new file mode 100644 index 0000000000..bbef0c0e94 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-existing.ts @@ -0,0 +1,123 @@ +import { mdiHomeAutomation } from "@mdi/js"; +import { LitElement, css, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../../../../common/dom/fire_event"; +import "../../../../../../components/ha-icon-next"; +import "../../../../../../components/ha-list-item-new"; +import "../../../../../../components/ha-list-new"; +import { HomeAssistant } from "../../../../../../types"; +import { MatterAddDeviceStep } from "../dialog-matter-add-device"; +import { sharedStyles } from "./matter-add-device-shared-styles"; + +@customElement("matter-add-device-existing") +class MatterAddDeviceExisting extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + render() { + return html` +
+

+ ${this.hass.localize( + `ui.dialogs.matter-add-device.existing.question` + )} +

+
+ + + + + + ${this.hass.localize( + `ui.dialogs.matter-add-device.existing.answer_google_home` + )} + + + + + + + ${this.hass.localize( + `ui.dialogs.matter-add-device.existing.answer_apple_home` + )} + + + + + + + ${this.hass.localize( + `ui.dialogs.matter-add-device.existing.answer_generic` + )} + + + + + `; + } + + private _onItemClick(ev) { + if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") { + return; + } + const item = ev.currentTarget as any; + const step = item.step as MatterAddDeviceStep; + fireEvent(this, "step-selected", { step }); + } + + static styles = [ + sharedStyles, + css` + .logo { + width: 48px; + height: 48px; + border-radius: 12px; + border: 1px solid var(--divider-color); + padding: 10px; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + object-fit: contain; + } + .logo ha-svg-icon { + --mdc-icon-size: 36px; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "matter-add-device-existing": MatterAddDeviceExisting; + } +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-generic.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-generic.ts new file mode 100644 index 0000000000..c48c149823 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-generic.ts @@ -0,0 +1,48 @@ +import { LitElement, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../../../common/dom/fire_event"; +import "../../../../../../components/ha-icon-next"; +import "../../../../../../components/ha-list-item-new"; +import "../../../../../../components/ha-list-new"; +import { HomeAssistant } from "../../../../../../types"; +import { sharedStyles } from "./matter-add-device-shared-styles"; + +@customElement("matter-add-device-generic") +class MatterAddDeviceGeneric extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _code: string = ""; + + render() { + return html` +
+

+ ${this.hass.localize( + "ui.dialogs.matter-add-device.generic.code_instructions" + )} +

+ +
+ `; + } + + private _onCodeChanged(ev: any) { + const value = ev.currentTarget.value; + this._code = value; + fireEvent(this, "pairing-code-changed", { code: value }); + } + + static styles = [sharedStyles]; +} + +declare global { + interface HTMLElementTagNameMap { + "matter-add-device-generic": MatterAddDeviceGeneric; + } +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-google-home-fallback.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-google-home-fallback.ts new file mode 100644 index 0000000000..4009f30da3 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-google-home-fallback.ts @@ -0,0 +1,85 @@ +import { LitElement, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../../../common/dom/fire_event"; +import "../../../../../../components/ha-icon-next"; +import "../../../../../../components/ha-list-item-new"; +import "../../../../../../components/ha-list-new"; +import { HomeAssistant } from "../../../../../../types"; +import { sharedStyles } from "./matter-add-device-shared-styles"; + +@customElement("matter-add-device-google-home-fallback") +class MatterAddDeviceGoogleHomeFallback extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _code: string = ""; + + render() { + return html` +
+
    +
  1. + ${this.hass.localize( + `ui.dialogs.matter-add-device.google_home_fallback.step_1` + )} +
  2. +
  3. + ${this.hass.localize( + `ui.dialogs.matter-add-device.google_home_fallback.step_2`, + { + linked_matter_apps_services: html`${this.hass.localize( + `ui.dialogs.matter-add-device.google_home_fallback.linked_matter_apps_services` + )}`, + } + )} +
  4. +
  5. + ${this.hass.localize( + `ui.dialogs.matter-add-device.google_home_fallback.step_3`, + { + link_apps_services: html`${this.hass.localize( + `ui.dialogs.matter-add-device.google_home_fallback.link_apps_services` + )}`, + use_pairing_code: html`${this.hass.localize( + `ui.dialogs.matter-add-device.google_home_fallback.use_pairing_code` + )}`, + } + )} +
  6. +
+
+

+ ${this.hass.localize( + `ui.dialogs.matter-add-device.google_home_fallback.code_instructions` + )} +

+ +
+ `; + } + + private _onCodeChanged(ev: any) { + const value = ev.currentTarget.value; + this._code = value; + fireEvent(this, "pairing-code-changed", { code: value }); + } + + static styles = [sharedStyles]; +} + +declare global { + interface HTMLElementTagNameMap { + "matter-add-device-google-home-fallback": MatterAddDeviceGoogleHomeFallback; + } +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-google-home.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-google-home.ts new file mode 100644 index 0000000000..1c528cf524 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-google-home.ts @@ -0,0 +1,82 @@ +import { LitElement, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../../../../common/dom/fire_event"; +import "../../../../../../components/ha-icon-next"; +import "../../../../../../components/ha-list-item-new"; +import "../../../../../../components/ha-list-new"; +import { HomeAssistant } from "../../../../../../types"; +import { sharedStyles } from "./matter-add-device-shared-styles"; + +@customElement("matter-add-device-google-home") +class MatterAddDeviceGoogleHome extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + render() { + return html` +
+
    +
  1. + ${this.hass.localize( + `ui.dialogs.matter-add-device.google_home.step_1` + )} +
  2. +
  3. + ${this.hass.localize( + `ui.dialogs.matter-add-device.google_home.step_2`, + { + linked_matter_apps_services: html`${this.hass.localize( + `ui.dialogs.matter-add-device.google_home.linked_matter_apps_services` + )}`, + } + )} +
  4. +
  5. + ${this.hass.localize( + `ui.dialogs.matter-add-device.google_home.step_3`, + { + link_apps_services: html`${this.hass.localize( + `ui.dialogs.matter-add-device.google_home.link_apps_services` + )}`, + home_assistant: html`Home Assistant`, + } + )} +
    + + ${this.hass.localize( + `ui.dialogs.matter-add-device.google_home.no_home_assistant` + )} + +
  6. +
+
+

+ ${this.hass.localize( + `ui.dialogs.matter-add-device.google_home.redirect` + )} +

+
+ `; + } + + private _nextStep() { + fireEvent(this, "step-selected", { step: "google_home_fallback" }); + } + + static styles = [sharedStyles]; +} + +declare global { + interface HTMLElementTagNameMap { + "matter-add-device-google-home": MatterAddDeviceGoogleHome; + } +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-main.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-main.ts new file mode 100644 index 0000000000..dc5b699034 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-main.ts @@ -0,0 +1,80 @@ +import { LitElement, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../../../../common/dom/fire_event"; +import "../../../../../../components/ha-icon-next"; +import "../../../../../../components/ha-list-item-new"; +import "../../../../../../components/ha-list-new"; +import { HomeAssistant } from "../../../../../../types"; +import { sharedStyles } from "./matter-add-device-shared-styles"; + +@customElement("matter-add-device-main") +class MatterAddDeviceMain extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + render() { + return html` +
+

+ ${this.hass.localize(`ui.dialogs.matter-add-device.main.question`)} +

+
+ + + + ${this.hass.localize( + `ui.dialogs.matter-add-device.main.answer_new` + )} + + + ${this.hass.localize( + `ui.dialogs.matter-add-device.main.answer_new_description` + )} + + + + + + ${this.hass.localize( + `ui.dialogs.matter-add-device.main.answer_existing` + )} + + + ${this.hass.localize( + `ui.dialogs.matter-add-device.main.answer_existing_description` + )} + + + + + `; + } + + private _onItemClick(ev) { + if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") { + return; + } + const item = ev.currentTarget as any; + const step = item.step as "new" | "existing"; + fireEvent(this, "step-selected", { step }); + } + + static styles = [sharedStyles]; +} + +declare global { + interface HTMLElementTagNameMap { + "matter-add-device-main": MatterAddDeviceMain; + } +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-new.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-new.ts new file mode 100644 index 0000000000..f273c4f685 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-new.ts @@ -0,0 +1,113 @@ +import { LitElement, css, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../../../../../../components/ha-circular-progress"; +import { + canCommissionMatterExternal, + startExternalCommissioning, +} from "../../../../../../data/matter"; +import { HomeAssistant } from "../../../../../../types"; +import { sharedStyles } from "./matter-add-device-shared-styles"; + +@customElement("matter-add-device-new") +class MatterAddDeviceNew extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + protected firstUpdated(): void { + if (!canCommissionMatterExternal(this.hass)) { + return; + } + startExternalCommissioning(this.hass); + } + + render() { + if (canCommissionMatterExternal(this.hass)) { + return html` +
+ +
+ `; + } + + return html` +
+

${this.hass.localize("ui.dialogs.matter-add-device.new.note")}

+

+ ${this.hass.localize("ui.dialogs.matter-add-device.new.download_app")} +

+ +
+ `; + } + + static styles = [ + sharedStyles, + css` + .app-qr { + margin: 24px auto 0 auto; + display: flex; + justify-content: space-between; + padding: 0 24px; + box-sizing: border-box; + gap: 16px; + width: 100%; + max-width: 400px; + } + .app-qr a, + .app-qr img { + flex: 1; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "matter-add-device-new": MatterAddDeviceNew; + } +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-shared-styles.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-shared-styles.ts new file mode 100644 index 0000000000..ca36a5f36b --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device/matter-add-device-shared-styles.ts @@ -0,0 +1,35 @@ +import { css } from "lit"; + +export const sharedStyles = css` + .content { + padding: 16px var(--horizontal-padding, 16px); + } + p { + margin: 0; + } + p:not(:last-child) { + margin-bottom: 8px; + } + ol { + padding-inline-start: 20px; + margin-block-start: 0; + margin-block-end: 8px; + } + li { + margin-bottom: 8px; + } + .link { + color: var(--primary-color); + cursor: pointer; + text-decoration: underline; + } + ha-list-new { + padding: 0; + --md-list-item-leading-space: var(--horizontal-padding, 16px); + --md-list-item-trailing-space: var(--horizontal-padding, 16px); + margin-bottom: 16px; + } + ha-textfield { + width: 100%; + } +`; diff --git a/src/translations/en.json b/src/translations/en.json index 68edd0d0f3..cd82c08792 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1657,6 +1657,72 @@ "title": "Create backup?", "text": "This will create a backup before installing.", "create": "Create" + }, + "matter-add-device": { + "add_device": "Add device", + "add_device_failed": "Failed to add the device", + "commissioning": { + "header": "Add Matter device", + "note": "Adding the device, this may take a minute or two." + }, + "main": { + "header": "Add Matter device", + "question": "Is your device already added to another Matter controller?", + "answer_new": "No. It’s new.", + "answer_new_description": "My device is brand new or factory reset.", + "answer_existing": "Yes. It’s already in use.", + "answer_existing_description": "My device is connected to another controller." + }, + "new": { + "header": "[%key:ui::dialogs::matter-add-device::main::header%]", + "note": "You need to use the Home Assistant Companion app on your mobile phone to add Matter devices.", + "download_app": "Install it from the Google Play Store or the App Store if you don't have it.", + "playstore": "[%key:ui::panel::page-onboarding::welcome::playstore%]", + "appstore": "[%key:ui::panel::page-onboarding::welcome::appstore%]" + }, + "existing": { + "header": "[%key:ui::dialogs::matter-add-device::main::header%]", + "question": "Which controller is it connected to?", + "answer_google_home": "Google Home", + "answer_apple_home": "Apple Home", + "answer_generic": "Other controllers" + }, + "google_home": { + "header": "Link Matter app", + "step_1": "Find your device in Google Home. Tap the gear icon to open the device settings.", + "step_2": "Tap {linked_matter_apps_services}.", + "step_3": "Tap {link_apps_services} and choose {home_assistant} from the list.", + "linked_matter_apps_services": "Linked Matter apps & services", + "link_apps_services": "Link apps & services", + "no_home_assistant": "I can’t find Home Assistant on the list", + "redirect": "You are redirected to the Home Assistant app. Please follow the instructions." + }, + "google_home_fallback": { + "header": "Copy pairing code", + "step_1": "[%key:ui::dialogs::matter-add-device::google_home::step_1%]", + "step_2": "[%key:ui::dialogs::matter-add-device::google_home::step_2%]", + "step_3": "Tap {link_apps_services} and choose {use_pairing_code} form the list", + "linked_matter_apps_services": "[%key:ui::dialogs::matter-add-device::google_home::linked_matter_apps_services%]", + "link_apps_services": "[%key:ui::dialogs::matter-add-device::google_home::link_apps_services%]", + "use_pairing_code": "Use Pairing Code", + "pairing_code": "Pairing code", + "code_instructions": "Paste the code you just received from the other controller." + }, + "apple_home": { + "header": "Copy setup code", + "step_1": "Find your device in Apple Home and open {accessory_details}.", + "step_2": "Scroll all the way down and tap {turn_on_pairing_mode}.", + "step_3": "You now see the setup code.", + "accessory_details": "Accessory Details", + "turn_on_pairing_mode": "Turn On Pairing Mode", + "setup_code": "Setup code", + "code_instructions": "Paste the code you just received from the other controller." + }, + "generic": { + "header": "Copy setup code", + "code_instructions": "Search for the sharing mode in the app of your controller, and activate it. You will get a sharing code, enter that below.", + "setup_code": "Setup code" + } } }, "weekdays": { @@ -4182,7 +4248,6 @@ "missing_zwave_zigbee_title": "{integration} is not setup", "missing_zwave_zigbee": "To add a {brand} device, you first need {supported_hardware_link} and the {integration} integration set up. If you already have the hardware then you can proceed with the setup of {integration}.", "missing_matter": "To add a {brand} device, you first need the {integration} integration and {supported_hardware_link}. Do you want to proceed with the setup of {integration}?", - "matter_mobile_app": "You need to use the Home Assistant Companion app on your mobile phone to commission Matter devices.", "supported_hardware": "supported hardware", "proceed": "Proceed", "single_config_entry_title": "This integration allows only one configuration", @@ -6623,7 +6688,9 @@ "forums": "Home Assistant forums", "open_home_newsletter": "Building the Open Home newsletter", "discord": "Discord chat", - "twitter": "Twitter" + "twitter": "Twitter", + "playstore": "Get it on Google Play", + "appstore": "Download on the App Store" }, "user": { "header": "Create user",