From 45a5c1c23561e0f808f409121383e4b6929c93eb Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 25 Mar 2024 15:56:55 +0100 Subject: [PATCH] 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 --- src/components/ha-control-button.ts | 12 +- .../more-info/controls/more-info-lock.ts | 205 ++++++++++++------ .../lock/ha-state-control-lock-toggle.ts | 8 + src/translations/en.json | 5 +- 4 files changed, 156 insertions(+), 74 deletions(-) diff --git a/src/components/ha-control-button.ts b/src/components/ha-control-button.ts index 58ea4f4400b2..6da398498461 100644 --- a/src/components/ha-control-button.ts +++ b/src/components/ha-control-button.ts @@ -84,6 +84,7 @@ export class HaControlButton extends LitElement { --control-button-background-color: var(--disabled-color); --control-button-background-opacity: 0.2; --control-button-border-radius: 10px; + --control-button-padding: 8px; --mdc-icon-size: 20px; color: var(--primary-text-color); width: 40px; @@ -95,16 +96,20 @@ export class HaControlButton extends LitElement { position: relative; cursor: pointer; display: flex; + flex-direction: column; align-items: center; justify-content: center; + text-align: center; width: 100%; height: 100%; border-radius: var(--control-button-border-radius); border: none; margin: 0; - padding: 0; + padding: var(--control-button-padding); box-sizing: border-box; - line-height: 0; + line-height: inherit; + font-family: Roboto; + font-weight: 500; outline: none; overflow: hidden; background: none; @@ -126,6 +131,8 @@ export class HaControlButton extends LitElement { background-color 180ms ease-in-out, opacity 180ms ease-in-out; opacity: var(--control-button-background-opacity); + pointer-events: none; + white-space: normal; } .button { transition: color 180ms ease-in-out; @@ -133,6 +140,7 @@ export class HaControlButton extends LitElement { } .button ::slotted(*) { pointer-events: none; + opacity: 0.95; } .button:disabled { cursor: not-allowed; diff --git a/src/dialogs/more-info/controls/more-info-lock.ts b/src/dialogs/more-info/controls/more-info-lock.ts index 9af77fd128bf..fccc841c9014 100644 --- a/src/dialogs/more-info/controls/more-info-lock.ts +++ b/src/dialogs/more-info/controls/more-info-lock.ts @@ -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 { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import { stateColorCss } from "../../../common/entity/state_color"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; +import "../../../components/ha-control-button"; +import "../../../components/ha-control-button-group"; import "../../../components/ha-outlined-icon-button"; import "../../../components/ha-state-icon"; import { UNAVAILABLE } from "../../../data/entity"; @@ -18,14 +20,49 @@ import type { HomeAssistant } from "../../../types"; import "../components/ha-more-info-state-header"; 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") class MoreInfoLock extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @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() { + if (this._buttonState !== "confirm") { + this._setButtonState("confirm", CONFIRM_TIMEOUT_SECOND); + return; + } + 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() { @@ -45,7 +82,7 @@ class MoreInfoLock extends LitElement { const color = stateColorCss(this.stateObj); const style = { - "--icon-color": color, + "--state-color": color, }; const isJammed = this.stateObj.state === "jammed"; @@ -56,74 +93,70 @@ class MoreInfoLock extends LitElement { .stateObj=${this.stateObj} >
- ${ - this.stateObj.state === "jammed" - ? html` -
- -
- -
+ ${this.stateObj.state === "jammed" + ? html` +
+ +
+
- ` - : html` - - - ` - } - ${ - supportsOpen || isJammed - ? html` -
- ${supportsOpen - ? html` - - - - ` - : nothing} - ${isJammed - ? html` - - - - - - - ` - : nothing} -
- ` - : nothing - } -
+
+ ` + : html` + + + `} + ${supportsOpen + ? html` +
+ ${this._buttonState === "success" + ? html` +

+ + ${this.hass.localize("ui.card.lock.open_door_success")} +

+ ` + : html` + + ${this._buttonState === "confirm" + ? this.hass.localize("ui.card.lock.open_door_confirm") + : this.hass.localize("ui.card.lock.open_door")} + + `} +
+ ` + : nothing} +
+
+ ${isJammed + ? html` + + + ${this.hass.localize("ui.card.lock.unlock")} + + + ${this.hass.localize("ui.card.lock.lock")} + + + ` + : nothing} +
- `; } @@ -131,6 +164,36 @@ class MoreInfoLock extends LitElement { return [ moreInfoControlStyle, 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 { 0% { opacity: 1; @@ -155,7 +218,7 @@ class MoreInfoLock extends LitElement { position: relative; --mdc-icon-size: 80px; animation: pulse 1s infinite; - color: var(--icon-color); + color: var(--state-color); border-radius: 50%; width: 144px; height: 144px; @@ -171,7 +234,7 @@ class MoreInfoLock extends LitElement { height: 100%; width: 100%; border-radius: 50%; - background-color: var(--icon-color); + background-color: var(--state-color); transition: background-color 180ms ease-in-out; opacity: 0.2; } diff --git a/src/state-control/lock/ha-state-control-lock-toggle.ts b/src/state-control/lock/ha-state-control-lock-toggle.ts index d39c0ec574f0..d05eb393e2e6 100644 --- a/src/state-control/lock/ha-state-control-lock-toggle.ts +++ b/src/state-control/lock/ha-state-control-lock-toggle.ts @@ -17,6 +17,13 @@ import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { forwardHaptic } from "../../data/haptics"; import { callProtectedLockService, LockEntity } from "../../data/lock"; 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") export class HaStateControlLockToggle extends LitElement { @@ -67,6 +74,7 @@ export class HaStateControlLockToggle extends LitElement { return; } forwardHaptic("light"); + fireEvent(this, "lock-service-called"); callProtectedLockService( this, this.hass, diff --git a/src/translations/en.json b/src/translations/en.json index 0576924ef5d1..5b11bc6104c0 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -187,7 +187,10 @@ "code": "[%key:ui::card::alarm_control_panel::code%]", "lock": "Lock", "unlock": "Unlock", - "open": "Open" + "open": "Open", + "open_door": "Open door", + "open_door_confirm": "Really open?", + "open_door_success": "Door open" }, "media_player": { "source": "Source",