257 files
This commit is contained in:
@@ -1030,6 +1030,7 @@ def build_action_buttons(
|
||||
entry_id: str | None,
|
||||
task_id: str | None,
|
||||
skip_allowed: bool,
|
||||
complete_allowed: bool = True,
|
||||
) -> list[dict[str, str]]:
|
||||
"""The Companion-app action buttons a reminder carries, per the three
|
||||
action toggles — translated labels, at most three (Android's cap).
|
||||
@@ -1049,7 +1050,7 @@ def build_action_buttons(
|
||||
return action_id(verb, None, None) if is_test else action_id(verb, entry_id, task_id)
|
||||
|
||||
actions: list[dict[str, str]] = []
|
||||
if options.get(CONF_ACTION_COMPLETE_ENABLED, setting_default(CONF_ACTION_COMPLETE_ENABLED)):
|
||||
if options.get(CONF_ACTION_COMPLETE_ENABLED, setting_default(CONF_ACTION_COMPLETE_ENABLED)) and complete_allowed:
|
||||
actions.append({"action": _id("COMPLETE"), "title": f"✅ {_notif_t('action_complete', lang)}"})
|
||||
if options.get(CONF_ACTION_SKIP_ENABLED, setting_default(CONF_ACTION_SKIP_ENABLED)) and skip_allowed:
|
||||
actions.append({"action": _id("SKIP"), "title": f"⏭️ {_notif_t('action_skip', lang)}"})
|
||||
@@ -1341,6 +1342,31 @@ class NotificationManager:
|
||||
Unknown entries/tasks keep the button (nothing to check against)."""
|
||||
return self._task_config(entry_id, task_id).get("allow_skip") is not False
|
||||
|
||||
def _complete_allowed(self, entry_id: str, task_id: str) -> bool:
|
||||
"""A "Complete" button the action handler would refuse is not offered
|
||||
(audit 2026-09-29): the tap only logged the refusal. Same rules as
|
||||
``coordinator.complete_maintenance`` for an unattended surface — a
|
||||
task only a tag scan may complete, one that demands details (nobody
|
||||
is asked on a notification tap; phase-aware), and one whose completion
|
||||
window has not opened yet (a lead-time reminder). Unknown entries or
|
||||
tasks keep the button (nothing to check against)."""
|
||||
from ..models.maintenance_task import MaintenanceTask
|
||||
from .completion_requirements import required_completion_fields
|
||||
|
||||
entry = self.hass.config_entries.async_get_entry(entry_id)
|
||||
coordinator = getattr(getattr(entry, "runtime_data", None), "coordinator", None)
|
||||
if coordinator is None:
|
||||
return True
|
||||
task = coordinator._get_merged_tasks_data().get(task_id)
|
||||
if not task:
|
||||
return True
|
||||
if task.get("require_tag_scan") or required_completion_fields(task):
|
||||
return False
|
||||
try:
|
||||
return bool(MaintenanceTask.from_dict(task).can_complete_now)
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return True
|
||||
|
||||
def _task_config(self, entry_id: str, task_id: str) -> Mapping[str, Any]:
|
||||
"""The task's static config dict (``entry.data``) — carries the flags
|
||||
the computed coordinator payload drops (``notify_enabled``,
|
||||
@@ -1723,8 +1749,27 @@ class NotificationManager:
|
||||
per-task gate's view of the snooze state — notification_gates.)"""
|
||||
return self._is_snoozed(notification_key(entry_id, task_id, status))
|
||||
|
||||
def snooze_task(self, entry_id: str, task_id: str) -> None:
|
||||
"""Snooze all notifications for a task."""
|
||||
def task_snooze_state(self, entry_id: str, task_id: str) -> dict[str, datetime]:
|
||||
"""The task's active snoozes, per status — what a voice undo restores."""
|
||||
state: dict[str, datetime] = {}
|
||||
for status in NOTIFIABLE_STATUSES:
|
||||
until = self._snoozed_until.get(notification_key(entry_id, task_id, status))
|
||||
if until is not None:
|
||||
state[status] = until
|
||||
return state
|
||||
|
||||
def restore_task_snooze_state(self, entry_id: str, task_id: str, state: dict[str, datetime]) -> None:
|
||||
"""Put a task's snoozes back to *state* (from :meth:`task_snooze_state`)."""
|
||||
for status in NOTIFIABLE_STATUSES:
|
||||
key = notification_key(entry_id, task_id, status)
|
||||
if status in state:
|
||||
self._snoozed_until[key] = state[status]
|
||||
else:
|
||||
self._snoozed_until.pop(key, None)
|
||||
self._dirty()
|
||||
|
||||
def snooze_task(self, entry_id: str, task_id: str) -> datetime:
|
||||
"""Snooze all notifications for a task; returns when it ends."""
|
||||
hours = self._opt(CONF_SNOOZE_DURATION_HOURS)
|
||||
until = dt_util.now() + timedelta(hours=hours)
|
||||
# Snooze for all status types
|
||||
@@ -1732,6 +1777,7 @@ class NotificationManager:
|
||||
self._snoozed_until[notification_key(entry_id, task_id, status)] = until
|
||||
self._dirty()
|
||||
_LOGGER.debug("Snoozed task %s for %s hours (until %s)", task_id, hours, until)
|
||||
return until
|
||||
|
||||
async def async_task_status_changed(
|
||||
self,
|
||||
@@ -1996,6 +2042,7 @@ class NotificationManager:
|
||||
entry_id=entry_id,
|
||||
task_id=task_id,
|
||||
skip_allowed=self._skip_allowed(entry_id, task_id),
|
||||
complete_allowed=self._complete_allowed(entry_id, task_id),
|
||||
)
|
||||
|
||||
service_data = _service_payload(
|
||||
|
||||
Reference in New Issue
Block a user