New apps Added
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
"""Button platform for TaskMate integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.components.button import ButtonEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import TaskMateCoordinator
|
||||
from .models import Child, Chore, Reward
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up TaskMate buttons."""
|
||||
coordinator: TaskMateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
entities: list[ButtonEntity] = []
|
||||
|
||||
# Add complete chore buttons for each child/chore combination
|
||||
children = coordinator.data.get("children", [])
|
||||
chores = coordinator.data.get("chores", [])
|
||||
rewards = coordinator.data.get("rewards", [])
|
||||
|
||||
for child in children:
|
||||
# Chore completion buttons
|
||||
for chore in chores:
|
||||
if getattr(chore, "assignment_mode", "everyone") == "unassigned":
|
||||
continue
|
||||
if not chore.assigned_to or child.id in chore.assigned_to:
|
||||
entities.append(
|
||||
CompleteChoreButton(coordinator, entry, child, chore)
|
||||
)
|
||||
|
||||
# Reward claim buttons
|
||||
for reward in rewards:
|
||||
entities.append(
|
||||
ClaimRewardButton(coordinator, entry, child, reward)
|
||||
)
|
||||
|
||||
# Track which entity combos already exist
|
||||
tracked_combos: set[str] = set()
|
||||
for child in children:
|
||||
for chore in chores:
|
||||
if getattr(chore, "assignment_mode", "everyone") == "unassigned":
|
||||
continue
|
||||
if not chore.assigned_to or child.id in chore.assigned_to:
|
||||
tracked_combos.add(f"{child.id}_{chore.id}_complete")
|
||||
for reward in rewards:
|
||||
tracked_combos.add(f"{child.id}_{reward.id}_claim")
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
# Set up listener to add buttons for new children/chores/rewards
|
||||
@callback
|
||||
def async_update_entities() -> None:
|
||||
"""Add button entities for newly created children, chores, or rewards."""
|
||||
new_entities: list[ButtonEntity] = []
|
||||
current_children = coordinator.data.get("children", [])
|
||||
current_chores = coordinator.data.get("chores", [])
|
||||
current_rewards = coordinator.data.get("rewards", [])
|
||||
|
||||
for child in current_children:
|
||||
for chore in current_chores:
|
||||
if getattr(chore, "assignment_mode", "everyone") == "unassigned":
|
||||
continue
|
||||
if not chore.assigned_to or child.id in chore.assigned_to:
|
||||
key = f"{child.id}_{chore.id}_complete"
|
||||
if key not in tracked_combos:
|
||||
new_entities.append(
|
||||
CompleteChoreButton(coordinator, entry, child, chore)
|
||||
)
|
||||
tracked_combos.add(key)
|
||||
for reward in current_rewards:
|
||||
key = f"{child.id}_{reward.id}_claim"
|
||||
if key not in tracked_combos:
|
||||
new_entities.append(
|
||||
ClaimRewardButton(coordinator, entry, child, reward)
|
||||
)
|
||||
tracked_combos.add(key)
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
|
||||
coordinator.async_add_listener(async_update_entities)
|
||||
|
||||
|
||||
class TaskMateBaseButton(CoordinatorEntity, ButtonEntity):
|
||||
"""Base class for TaskMate buttons."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: TaskMateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize the button."""
|
||||
super().__init__(coordinator)
|
||||
self._entry = entry
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device info."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self._entry.entry_id)},
|
||||
name="TaskMate",
|
||||
manufacturer="TaskMate",
|
||||
model="Family Chore Manager",
|
||||
)
|
||||
|
||||
|
||||
class CompleteChoreButton(TaskMateBaseButton):
|
||||
"""Button to mark a chore as completed."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: TaskMateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
child: Child,
|
||||
chore: Chore,
|
||||
) -> None:
|
||||
"""Initialize the button."""
|
||||
super().__init__(coordinator, entry)
|
||||
self.child_id = child.id
|
||||
self.chore_id = chore.id
|
||||
self._attr_unique_id = f"{entry.entry_id}_{child.id}_{chore.id}_complete"
|
||||
self._attr_name = f"{child.name}: Complete {chore.name}"
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return the icon."""
|
||||
chore = self.coordinator.get_chore(self.chore_id)
|
||||
return getattr(chore, 'icon', "mdi:check-circle") if chore else "mdi:check-circle"
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict:
|
||||
"""Return additional attributes."""
|
||||
child = self.coordinator.get_child(self.child_id)
|
||||
chore = self.coordinator.get_chore(self.chore_id)
|
||||
|
||||
if not child or not chore:
|
||||
return {}
|
||||
|
||||
return {
|
||||
"child_id": child.id,
|
||||
"child_name": child.name,
|
||||
"chore_id": chore.id,
|
||||
"chore_name": chore.name,
|
||||
"points": chore.points,
|
||||
"requires_approval": chore.requires_approval,
|
||||
}
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Handle the button press."""
|
||||
try:
|
||||
await self.coordinator.async_complete_chore(self.chore_id, self.child_id)
|
||||
except ValueError as err:
|
||||
_LOGGER.error("Failed to complete chore: %s", err)
|
||||
|
||||
|
||||
class ClaimRewardButton(TaskMateBaseButton):
|
||||
"""Button to claim a reward."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: TaskMateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
child: Child,
|
||||
reward: Reward,
|
||||
) -> None:
|
||||
"""Initialize the button."""
|
||||
super().__init__(coordinator, entry)
|
||||
self.child_id = child.id
|
||||
self.reward_id = reward.id
|
||||
self._attr_unique_id = f"{entry.entry_id}_{child.id}_{reward.id}_claim"
|
||||
self._attr_name = f"{child.name}: Claim {reward.name}"
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return the icon."""
|
||||
reward = self.coordinator.get_reward(self.reward_id)
|
||||
return reward.icon if reward else "mdi:gift"
|
||||
|
||||
def _get_committed_points(self, child_id: str) -> int:
|
||||
"""Calculate points committed by pending reward claims for a child.
|
||||
|
||||
Pool-mode pending claims are skipped because their cost was already
|
||||
deducted from child.points at allocation time.
|
||||
"""
|
||||
pending_claims = self.coordinator.data.get("pending_reward_claims", [])
|
||||
committed = 0
|
||||
for c in pending_claims:
|
||||
if c.child_id == child_id and not self.coordinator.is_pool_mode_claim(c):
|
||||
reward = self.coordinator.get_reward(c.reward_id)
|
||||
if reward:
|
||||
committed += reward.cost
|
||||
return committed
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if button is available (accounting for committed points)."""
|
||||
child = self.coordinator.get_child(self.child_id)
|
||||
reward = self.coordinator.get_reward(self.reward_id)
|
||||
|
||||
if not child or not reward:
|
||||
return False
|
||||
|
||||
available_points = child.points - self._get_committed_points(child.id)
|
||||
return available_points >= reward.cost
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict:
|
||||
"""Return additional attributes."""
|
||||
child = self.coordinator.get_child(self.child_id)
|
||||
reward = self.coordinator.get_reward(self.reward_id)
|
||||
|
||||
if not child or not reward:
|
||||
return {}
|
||||
|
||||
committed = self._get_committed_points(child.id)
|
||||
available_points = child.points - committed
|
||||
can_afford = available_points >= reward.cost
|
||||
|
||||
return {
|
||||
"child_id": child.id,
|
||||
"child_name": child.name,
|
||||
"reward_id": reward.id,
|
||||
"reward_name": reward.name,
|
||||
"cost": reward.cost,
|
||||
"child_points": child.points,
|
||||
"committed_points": committed,
|
||||
"available_points": available_points,
|
||||
"can_afford": can_afford,
|
||||
"points_needed": max(0, reward.cost - available_points),
|
||||
}
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Handle the button press."""
|
||||
try:
|
||||
await self.coordinator.async_claim_reward(self.reward_id, self.child_id)
|
||||
except ValueError as err:
|
||||
_LOGGER.error("Failed to claim reward: %s", err)
|
||||
Reference in New Issue
Block a user