Add question based matter commissioning flow (#20188)

* Create add device dialog

* Add translations and shared design

* Disable add buttons when no code

* Use right endpoint

* Add loading state

* Update logos

* Add native flow

* Add store links to download app

* Always display qr code and link

* Update translations

* Share assets and translations with app dialog
This commit is contained in:
Paul Bottein 2024-03-26 20:53:05 +01:00 committed by GitHub
parent eb4ae926b7
commit 5547bc7356
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 991 additions and 6731 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 69 KiB

File diff suppressed because one or more lines are too long

View File

@ -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<MatterAddDeviceStep, MatterAddDeviceStep | undefined> =
{
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`
<div
@pairing-code-changed=${this._handlePairingCodeChanged}
@step-selected=${this._handleStepSelected}
.hass=${this.hass}
>
${dynamicElement(
`matter-add-device-${this._step.replaceAll("_", "-")}`,
{
hass: this.hass,
}
)}
</div>
`;
}
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`
<ha-button
slot="primaryAction"
@click=${this._addDevice}
.disabled=${!this._pairingCode}
>
${this.hass.localize("ui.dialogs.matter-add-device.add_device")}
</ha-button>
`;
}
if (this._step === "new") {
return html`
<ha-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.common.ok")}
</ha-button>
`;
}
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`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${createCloseHeading(this.hass, "Add Matter device")}
.heading=${title}
?hideActions=${actions === nothing}
scrimClickAction
escapeKeyAction
>
<div>
${!canCommissionMatterExternal(this.hass)
? this.hass.localize(
"ui.panel.config.integrations.config_flow.matter_mobile_app"
)
: html`<ha-circular-progress
size="large"
indeterminate
></ha-circular-progress>`}
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.common.cancel")}
</mwc-button>
<ha-dialog-header slot="heading">
${hasBackStep
? html`
<ha-icon-button-arrow-prev
slot="navigationIcon"
.hass=${this.hass}
@click=${this._back}
></ha-icon-button-arrow-prev>
`
: html`
<ha-icon-button
slot="navigationIcon"
dialogAction="cancel"
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
`}
<span slot="title">${title}</span>
</ha-dialog-header>
${this._renderStep()} ${actions}
</ha-dialog>
`;
}
@ -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;
}
`,
];

View File

@ -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`
<div class="content">
<ol>
<li>
${this.hass.localize(
"ui.dialogs.matter-add-device.apple_home.step_1",
{
accessory_details: html`<b
>${this.hass.localize(
"ui.dialogs.matter-add-device.apple_home.accessory_details"
)}</b
>`,
}
)}
</li>
<li>
${this.hass.localize(
"ui.dialogs.matter-add-device.apple_home.step_2",
{
turn_on_pairing_mode: html`<b
>${this.hass.localize(
"ui.dialogs.matter-add-device.apple_home.turn_on_pairing_mode"
)}</b
>`,
}
)}
</li>
<li>
${this.hass.localize(
"ui.dialogs.matter-add-device.apple_home.step_3"
)}
</li>
</ol>
<br />
<p>
${this.hass.localize(
"ui.dialogs.matter-add-device.apple_home.code_instructions"
)}
</p>
<ha-textfield
label=${this.hass.localize(
"ui.dialogs.matter-add-device.apple_home.setup_code"
)}
.value=${this._code}
@input=${this._onCodeChanged}
></ha-textfield>
</div>
`;
}
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;
}
}

View File

@ -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`
<div class="content">
<ha-circular-progress
size="medium"
indeterminate
></ha-circular-progress>
<p>
${this.hass.localize(
"ui.dialogs.matter-add-device.commissioning.note"
)}
</p>
</div>
`;
}
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;
}
}

View File

@ -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`
<div class="content">
<p>
${this.hass.localize(
`ui.dialogs.matter-add-device.existing.question`
)}
</p>
</div>
<ha-list-new>
<ha-list-item-new
interactive
type="button"
.step=${"google_home"}
@click=${this._onItemClick}
@keydown=${this._onItemClick}
>
<img
src="/static/images/logo_google_home.png"
alt=""
class="logo"
slot="start"
/>
<span slot="headline">
${this.hass.localize(
`ui.dialogs.matter-add-device.existing.answer_google_home`
)}
</span>
<ha-icon-next slot="end"></ha-icon-next>
</ha-list-item-new>
<ha-list-item-new
interactive
type="button"
.step=${"apple_home"}
@click=${this._onItemClick}
@keydown=${this._onItemClick}
>
<img
src="/static/images/logo_apple_home.png"
alt=""
class="logo"
slot="start"
/>
<span slot="headline">
${this.hass.localize(
`ui.dialogs.matter-add-device.existing.answer_apple_home`
)}
</span>
<ha-icon-next slot="end"></ha-icon-next>
</ha-list-item-new>
<ha-list-item-new
interactive
type="button"
.step=${"generic"}
@click=${this._onItemClick}
@keydown=${this._onItemClick}
>
<div class="logo" slot="start">
<ha-svg-icon path=${mdiHomeAutomation}></ha-svg-icon>
</div>
<span slot="headline">
${this.hass.localize(
`ui.dialogs.matter-add-device.existing.answer_generic`
)}
</span>
<ha-icon-next slot="end"></ha-icon-next>
</ha-list-item-new>
</ha-list-new>
`;
}
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;
}
}

View File

@ -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`
<div class="content">
<p>
${this.hass.localize(
"ui.dialogs.matter-add-device.generic.code_instructions"
)}
</p>
<ha-textfield
label=${this.hass.localize(
"ui.dialogs.matter-add-device.generic.setup_code"
)}
.value=${this._code}
@input=${this._onCodeChanged}
></ha-textfield>
</div>
`;
}
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;
}
}

View File

@ -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`
<div class="content">
<ol>
<li>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home_fallback.step_1`
)}
</li>
<li>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home_fallback.step_2`,
{
linked_matter_apps_services: html`<b
>${this.hass.localize(
`ui.dialogs.matter-add-device.google_home_fallback.linked_matter_apps_services`
)}</b
>`,
}
)}
</li>
<li>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home_fallback.step_3`,
{
link_apps_services: html`<b
>${this.hass.localize(
`ui.dialogs.matter-add-device.google_home_fallback.link_apps_services`
)}</b
>`,
use_pairing_code: html`<b
>${this.hass.localize(
`ui.dialogs.matter-add-device.google_home_fallback.use_pairing_code`
)}</b
>`,
}
)}
</li>
</ol>
<br />
<p>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home_fallback.code_instructions`
)}
</p>
<ha-textfield
label=${this.hass.localize(
`ui.dialogs.matter-add-device.google_home_fallback.pairing_code`
)}
.value=${this._code}
@input=${this._onCodeChanged}
></ha-textfield>
</div>
`;
}
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;
}
}

View File

@ -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`
<div class="content">
<ol>
<li>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home.step_1`
)}
</li>
<li>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home.step_2`,
{
linked_matter_apps_services: html`<b
>${this.hass.localize(
`ui.dialogs.matter-add-device.google_home.linked_matter_apps_services`
)}</b
>`,
}
)}
</li>
<li>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home.step_3`,
{
link_apps_services: html`<b
>${this.hass.localize(
`ui.dialogs.matter-add-device.google_home.link_apps_services`
)}</b
>`,
home_assistant: html`<b>Home Assistant</b>`,
}
)}
<br />
<span
class="link"
type="button"
tabindex="0"
@keydown=${this._nextStep}
@click=${this._nextStep}
>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home.no_home_assistant`
)}
</span>
</li>
</ol>
<br />
<p>
${this.hass.localize(
`ui.dialogs.matter-add-device.google_home.redirect`
)}
</p>
</div>
`;
}
private _nextStep() {
fireEvent(this, "step-selected", { step: "google_home_fallback" });
}
static styles = [sharedStyles];
}
declare global {
interface HTMLElementTagNameMap {
"matter-add-device-google-home": MatterAddDeviceGoogleHome;
}
}

View File

@ -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`
<div class="content">
<p class="text">
${this.hass.localize(`ui.dialogs.matter-add-device.main.question`)}
</p>
</div>
<ha-list-new>
<ha-list-item-new
interactive
type="button"
.step=${"new"}
@click=${this._onItemClick}
@keydown=${this._onItemClick}
>
<span slot="headline">
${this.hass.localize(
`ui.dialogs.matter-add-device.main.answer_new`
)}
</span>
<span slot="supporting-text">
${this.hass.localize(
`ui.dialogs.matter-add-device.main.answer_new_description`
)}
</span>
<ha-icon-next slot="end"></ha-icon-next>
</ha-list-item-new>
<ha-list-item-new
interactive
type="button"
.step=${"existing"}
@click=${this._onItemClick}
@keydown=${this._onItemClick}
>
<span slot="headline">
${this.hass.localize(
`ui.dialogs.matter-add-device.main.answer_existing`
)}
</span>
<span slot="supporting-text">
${this.hass.localize(
`ui.dialogs.matter-add-device.main.answer_existing_description`
)}
</span>
<ha-icon-next slot="end"></ha-icon-next>
</ha-list-item-new>
</ha-list-new>
`;
}
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;
}
}

View File

@ -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`
<div class="content">
<ha-circular-progress
size="medium"
indeterminate
></ha-circular-progress>
</div>
`;
}
return html`
<div class="content">
<p>${this.hass.localize("ui.dialogs.matter-add-device.new.note")}</p>
<p>
${this.hass.localize("ui.dialogs.matter-add-device.new.download_app")}
</p>
<div class="app-qr">
<a
target="_blank"
rel="noreferrer noopener"
href="https://apps.apple.com/app/home-assistant/id1099568401?mt=8"
>
<img
loading="lazy"
src="/static/images/appstore.svg"
alt=${this.hass.localize(
"ui.dialogs.matter-add-device.new.appstore"
)}
class="icon"
/>
<img
loading="lazy"
src="/static/images/qr-appstore.svg"
alt=${this.hass.localize(
"ui.dialogs.matter-add-device.new.appstore"
)}
/>
</a>
<a
target="_blank"
rel="noreferrer noopener"
href="https://play.google.com/store/apps/details?id=io.homeassistant.companion.android"
>
<img
loading="lazy"
src="/static/images/playstore.svg"
alt=${this.hass.localize(
"ui.dialogs.matter-add-device.new.playstore"
)}
class="icon"
/>
<img
loading="lazy"
src="/static/images/qr-playstore.svg"
alt=${this.hass.localize(
"ui.dialogs.matter-add-device.new.playstore"
)}
/>
</a>
</div>
</div>
`;
}
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;
}
}

View File

@ -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%;
}
`;

View File

@ -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. Its new.",
"answer_new_description": "My device is brand new or factory reset.",
"answer_existing": "Yes. Its 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 cant 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",