Add confirmation button in lock more info (#20093)

* Add confirmation button in more info lock

* Reset confirm open on service call

* Use text instead of toast for success
This commit is contained in:
Paul Bottein 2024-03-25 15:56:55 +01:00 committed by GitHub
parent db3709952c
commit 45a5c1c235
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 156 additions and 74 deletions

View File

@ -84,6 +84,7 @@ export class HaControlButton extends LitElement {
--control-button-background-color: var(--disabled-color); --control-button-background-color: var(--disabled-color);
--control-button-background-opacity: 0.2; --control-button-background-opacity: 0.2;
--control-button-border-radius: 10px; --control-button-border-radius: 10px;
--control-button-padding: 8px;
--mdc-icon-size: 20px; --mdc-icon-size: 20px;
color: var(--primary-text-color); color: var(--primary-text-color);
width: 40px; width: 40px;
@ -95,16 +96,20 @@ export class HaControlButton extends LitElement {
position: relative; position: relative;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
text-align: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: var(--control-button-border-radius); border-radius: var(--control-button-border-radius);
border: none; border: none;
margin: 0; margin: 0;
padding: 0; padding: var(--control-button-padding);
box-sizing: border-box; box-sizing: border-box;
line-height: 0; line-height: inherit;
font-family: Roboto;
font-weight: 500;
outline: none; outline: none;
overflow: hidden; overflow: hidden;
background: none; background: none;
@ -126,6 +131,8 @@ export class HaControlButton extends LitElement {
background-color 180ms ease-in-out, background-color 180ms ease-in-out,
opacity 180ms ease-in-out; opacity 180ms ease-in-out;
opacity: var(--control-button-background-opacity); opacity: var(--control-button-background-opacity);
pointer-events: none;
white-space: normal;
} }
.button { .button {
transition: color 180ms ease-in-out; transition: color 180ms ease-in-out;
@ -133,6 +140,7 @@ export class HaControlButton extends LitElement {
} }
.button ::slotted(*) { .button ::slotted(*) {
pointer-events: none; pointer-events: none;
opacity: 0.95;
} }
.button:disabled { .button:disabled {
cursor: not-allowed; cursor: not-allowed;

View File

@ -1,10 +1,12 @@
import { mdiDoorOpen, mdiLock, mdiLockOff } from "@mdi/js"; import { mdiCheck } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { stateColorCss } from "../../../common/entity/state_color"; import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes"; import "../../../components/ha-attributes";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-outlined-icon-button"; import "../../../components/ha-outlined-icon-button";
import "../../../components/ha-state-icon"; import "../../../components/ha-state-icon";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
@ -18,14 +20,49 @@ import type { HomeAssistant } from "../../../types";
import "../components/ha-more-info-state-header"; import "../components/ha-more-info-state-header";
import { moreInfoControlStyle } from "../components/more-info-control-style"; import { moreInfoControlStyle } from "../components/more-info-control-style";
const CONFIRM_TIMEOUT_SECOND = 5;
const OPENED_TIMEOUT_SECOND = 3;
type ButtonState = "normal" | "confirm" | "success";
@customElement("more-info-lock") @customElement("more-info-lock")
class MoreInfoLock extends LitElement { class MoreInfoLock extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj?: LockEntity; @property({ attribute: false }) public stateObj?: LockEntity;
@state() public _buttonState: ButtonState = "normal";
private _buttonTimeout?: number;
private _setButtonState(buttonState: ButtonState, timeoutSecond?: number) {
clearTimeout(this._buttonTimeout);
this._buttonState = buttonState;
if (timeoutSecond) {
this._buttonTimeout = window.setTimeout(() => {
this._buttonState = "normal";
}, timeoutSecond * 1000);
}
}
private async _open() { private async _open() {
if (this._buttonState !== "confirm") {
this._setButtonState("confirm", CONFIRM_TIMEOUT_SECOND);
return;
}
callProtectedLockService(this, this.hass, this.stateObj!, "open"); callProtectedLockService(this, this.hass, this.stateObj!, "open");
this._setButtonState("success", OPENED_TIMEOUT_SECOND);
}
private _resetButtonState() {
this._setButtonState("normal");
}
disconnectedCallback(): void {
super.disconnectedCallback();
this._resetButtonState();
} }
private async _lock() { private async _lock() {
@ -45,7 +82,7 @@ class MoreInfoLock extends LitElement {
const color = stateColorCss(this.stateObj); const color = stateColorCss(this.stateObj);
const style = { const style = {
"--icon-color": color, "--state-color": color,
}; };
const isJammed = this.stateObj.state === "jammed"; const isJammed = this.stateObj.state === "jammed";
@ -56,74 +93,70 @@ class MoreInfoLock extends LitElement {
.stateObj=${this.stateObj} .stateObj=${this.stateObj}
></ha-more-info-state-header> ></ha-more-info-state-header>
<div class="controls" style=${styleMap(style)}> <div class="controls" style=${styleMap(style)}>
${ ${this.stateObj.state === "jammed"
this.stateObj.state === "jammed" ? html`
? html` <div class="status">
<div class="status"> <span></span>
<span></span> <div class="icon">
<div class="icon"> <ha-state-icon
<ha-state-icon .hass=${this.hass}
.hass=${this.hass} .stateObj=${this.stateObj}
.stateObj=${this.stateObj} ></ha-state-icon>
></ha-state-icon>
</div>
</div> </div>
` </div>
: html` `
<ha-state-control-lock-toggle : html`
.stateObj=${this.stateObj} <ha-state-control-lock-toggle
.hass=${this.hass} @lock-service-called=${this._resetButtonState}
> .stateObj=${this.stateObj}
</ha-state-control-lock-toggle> .hass=${this.hass}
` >
} </ha-state-control-lock-toggle>
${ `}
supportsOpen || isJammed ${supportsOpen
? html` ? html`
<div class="buttons"> <div class="buttons">
${supportsOpen ${this._buttonState === "success"
? html` ? html`
<ha-outlined-icon-button <p class="open-success">
.disabled=${this.stateObj.state === UNAVAILABLE} <ha-svg-icon path=${mdiCheck}></ha-svg-icon>
.title=${this.hass.localize("ui.card.lock.open")} ${this.hass.localize("ui.card.lock.open_door_success")}
.ariaLabel=${this.hass.localize("ui.card.lock.open")} </p>
@click=${this._open} `
> : html`
<ha-svg-icon .path=${mdiDoorOpen}></ha-svg-icon> <ha-control-button
</ha-outlined-icon-button> .disabled=${this.stateObj.state === UNAVAILABLE}
` class="open-button ${this._buttonState}"
: nothing} @click=${this._open}
${isJammed >
? html` ${this._buttonState === "confirm"
<ha-outlined-icon-button ? this.hass.localize("ui.card.lock.open_door_confirm")
.title=${this.hass.localize("ui.card.lock.lock")} : this.hass.localize("ui.card.lock.open_door")}
.ariaLabel=${this.hass.localize("ui.card.lock.lock")} </ha-control-button>
@click=${this._lock} `}
> </div>
<ha-svg-icon .path=${mdiLock}></ha-svg-icon> `
</ha-outlined-icon-button> : nothing}
<ha-outlined-icon-button </div>
.title=${this.hass.localize("ui.card.lock.unlock")} <div>
.ariaLabel=${this.hass.localize( ${isJammed
"ui.card.lock.unlock" ? html`
)} <ha-control-button-group class="jammed">
@click=${this._unlock} <ha-control-button @click=${this._unlock}>
> ${this.hass.localize("ui.card.lock.unlock")}
<ha-svg-icon .path=${mdiLockOff}></ha-svg-icon> </ha-control-button>
</ha-outlined-icon-button> <ha-control-button @click=${this._lock}>
` ${this.hass.localize("ui.card.lock.lock")}
: nothing} </ha-control-button>
</div> </ha-control-button-group>
` `
: nothing : nothing}
} <ha-attributes
</div> .hass=${this.hass}
.stateObj=${this.stateObj}
extra-filters="code_format"
></ha-attributes>
</div> </div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
extra-filters="code_format"
></ha-attributes>
`; `;
} }
@ -131,6 +164,36 @@ class MoreInfoLock extends LitElement {
return [ return [
moreInfoControlStyle, moreInfoControlStyle,
css` css`
ha-control-button {
font-size: 14px;
height: 60px;
--control-button-border-radius: 24px;
}
.open-button {
width: 100px;
--control-button-background-color: var(--state-color);
}
.open-button.confirm {
--control-button-background-color: var(--warning-color);
}
.open-success {
line-height: 60px;
display: flex;
align-items: center;
flex-direction: row;
gap: 8px;
font-weight: 500;
color: var(--success-color);
}
ha-control-button-group.jammed {
--control-button-group-thickness: 60px;
width: 100%;
max-width: 400px;
margin: 0 auto;
}
ha-control-button-group + ha-attributes:not([empty]) {
margin-top: 16px;
}
@keyframes pulse { @keyframes pulse {
0% { 0% {
opacity: 1; opacity: 1;
@ -155,7 +218,7 @@ class MoreInfoLock extends LitElement {
position: relative; position: relative;
--mdc-icon-size: 80px; --mdc-icon-size: 80px;
animation: pulse 1s infinite; animation: pulse 1s infinite;
color: var(--icon-color); color: var(--state-color);
border-radius: 50%; border-radius: 50%;
width: 144px; width: 144px;
height: 144px; height: 144px;
@ -171,7 +234,7 @@ class MoreInfoLock extends LitElement {
height: 100%; height: 100%;
width: 100%; width: 100%;
border-radius: 50%; border-radius: 50%;
background-color: var(--icon-color); background-color: var(--state-color);
transition: background-color 180ms ease-in-out; transition: background-color 180ms ease-in-out;
opacity: 0.2; opacity: 0.2;
} }

View File

@ -17,6 +17,13 @@ import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { forwardHaptic } from "../../data/haptics"; import { forwardHaptic } from "../../data/haptics";
import { callProtectedLockService, LockEntity } from "../../data/lock"; import { callProtectedLockService, LockEntity } from "../../data/lock";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
declare global {
interface HASSDomEvents {
"lock-service-called": undefined;
}
}
@customElement("ha-state-control-lock-toggle") @customElement("ha-state-control-lock-toggle")
export class HaStateControlLockToggle extends LitElement { export class HaStateControlLockToggle extends LitElement {
@ -67,6 +74,7 @@ export class HaStateControlLockToggle extends LitElement {
return; return;
} }
forwardHaptic("light"); forwardHaptic("light");
fireEvent(this, "lock-service-called");
callProtectedLockService( callProtectedLockService(
this, this,
this.hass, this.hass,

View File

@ -187,7 +187,10 @@
"code": "[%key:ui::card::alarm_control_panel::code%]", "code": "[%key:ui::card::alarm_control_panel::code%]",
"lock": "Lock", "lock": "Lock",
"unlock": "Unlock", "unlock": "Unlock",
"open": "Open" "open": "Open",
"open_door": "Open door",
"open_door_confirm": "Really open?",
"open_door_success": "Door open"
}, },
"media_player": { "media_player": {
"source": "Source", "source": "Source",