Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions custom_components/choremander/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@
ATTR_POINTS,
ATTR_REASON,
ATTR_REWARD_ID,
ATTR_SOUND,
DOMAIN,
EVENT_PREVIEW_SOUND,
SERVICE_ADD_POINTS,
SERVICE_APPROVE_CHORE,
SERVICE_APPROVE_REWARD,
SERVICE_CLAIM_REWARD,
SERVICE_REJECT_REWARD,
SERVICE_COMPLETE_CHORE,
SERVICE_PREVIEW_SOUND,
SERVICE_REJECT_CHORE,
SERVICE_REMOVE_POINTS,
SERVICE_SET_CHORE_ORDER,
Expand Down Expand Up @@ -54,9 +58,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

hass.data[DOMAIN][entry.entry_id] = coordinator

# Register frontend static paths and Lovelace resources (only once)
# Register frontend static paths
await async_register_frontend(hass)
await async_register_cards(hass)

# Register Lovelace resources only on first install, not on every restart
# A new entry has no prior data stored — use that to detect first install
is_first_install = not entry.data.get("resources_registered", False)
await async_register_cards(hass, first_install=is_first_install)

# Mark resources as registered so future restarts skip auto-registration
if is_first_install:
new_data = dict(entry.data)
new_data["resources_registered"] = True
hass.config_entries.async_update_entry(entry, data=new_data)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

Expand All @@ -70,6 +84,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
coordinator = hass.data[DOMAIN].get(entry.entry_id)
if coordinator:
await coordinator.async_shutdown()

if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)

Expand Down Expand Up @@ -123,6 +141,15 @@ async def handle_reject_chore(call: ServiceCall) -> None:
completion_id = call.data["completion_id"]
await coordinator.async_reject_chore(completion_id)

async def handle_reject_reward(call: ServiceCall) -> None:
"""Handle the reject_reward service call."""
coordinator = _get_coordinator(hass)
if not coordinator:
_LOGGER.error("No Choremander coordinator available")
return
claim_id = call.data["claim_id"]
await coordinator.async_reject_reward(claim_id)

async def handle_claim_reward(call: ServiceCall) -> None:
"""Handle the claim_reward service call."""
coordinator = _get_coordinator(hass)
Expand Down Expand Up @@ -164,6 +191,11 @@ async def handle_remove_points(call: ServiceCall) -> None:
reason = call.data.get(ATTR_REASON, "")
await coordinator.async_remove_points(child_id, points, reason)

async def handle_preview_sound(call: ServiceCall) -> None:
"""Handle the preview_sound service call — fires a browser event for the config-sounds card."""
sound = call.data.get(ATTR_SOUND, "coin")
hass.bus.async_fire(EVENT_PREVIEW_SOUND, {"sound": sound})

async def handle_set_chore_order(call: ServiceCall) -> None:
"""Handle the set_chore_order service call."""
coordinator = _get_coordinator(hass)
Expand Down Expand Up @@ -221,6 +253,13 @@ async def handle_set_chore_order(call: ServiceCall) -> None:
),
)

hass.services.async_register(
DOMAIN,
SERVICE_REJECT_REWARD,
handle_reject_reward,
schema=vol.Schema({ vol.Required("claim_id"): cv.string }),
)

hass.services.async_register(
DOMAIN,
SERVICE_APPROVE_REWARD,
Expand Down Expand Up @@ -258,6 +297,21 @@ async def handle_set_chore_order(call: ServiceCall) -> None:
),
)

hass.services.async_register(
DOMAIN,
SERVICE_PREVIEW_SOUND,
handle_preview_sound,
schema=vol.Schema(
{
vol.Required(ATTR_SOUND): vol.In([
"none", "coin", "levelup", "fanfare", "chime", "powerup", "undo",
"fart1", "fart2", "fart3", "fart4", "fart5", "fart6", "fart7",
"fart8", "fart9", "fart10", "fart_random",
]),
}
),
)

hass.services.async_register(
DOMAIN,
SERVICE_SET_CHORE_ORDER,
Expand All @@ -279,9 +333,11 @@ def _async_unregister_services(hass: HomeAssistant) -> None:
SERVICE_REJECT_CHORE,
SERVICE_CLAIM_REWARD,
SERVICE_APPROVE_REWARD,
SERVICE_REJECT_REWARD,
SERVICE_ADD_POINTS,
SERVICE_REMOVE_POINTS,
SERVICE_SET_CHORE_ORDER,
SERVICE_PREVIEW_SOUND,
]
for service in services:
hass.services.async_remove(DOMAIN, service)
37 changes: 37 additions & 0 deletions custom_components/choremander/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,8 +714,22 @@ async def async_step_settings(
name=user_input.get("points_name", DEFAULT_POINTS_NAME),
icon=user_input.get("points_icon", DEFAULT_POINTS_ICON),
)
await self.coordinator.async_set_setting(
"streak_reset_mode",
user_input.get("streak_reset_mode", "reset"),
)
await self.coordinator.async_set_setting(
"history_days",
str(int(float(user_input.get("history_days", 90)))),
)
return await self.async_step_init()

current_streak_mode = self.coordinator.storage.get_setting("streak_reset_mode", "reset")
try:
current_history_days = float(self.coordinator.storage.get_setting("history_days", "90"))
except (ValueError, TypeError):
current_history_days = 90.0

return self.async_show_form(
step_id="settings",
data_schema=vol.Schema(
Expand All @@ -728,6 +742,29 @@ async def async_step_settings(
"points_icon",
default=self.coordinator.storage.get_points_icon(),
): selector.IconSelector(),
vol.Required(
"streak_reset_mode",
default=current_streak_mode,
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=[
selector.SelectOptionDict(value="reset", label="Reset — streak goes to 0 on missed day"),
selector.SelectOptionDict(value="pause", label="Pause — streak preserved until next completion"),
],
mode=selector.SelectSelectorMode.LIST,
)
),
vol.Required(
"history_days",
default=current_history_days,
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=30,
max=365,
step=1,
mode=selector.NumberSelectorMode.BOX,
)
),
}
),
)
Expand Down
1 change: 1 addition & 0 deletions custom_components/choremander/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
SERVICE_REJECT_CHORE: Final = "reject_chore"
SERVICE_CLAIM_REWARD: Final = "claim_reward"
SERVICE_APPROVE_REWARD: Final = "approve_reward"
SERVICE_REJECT_REWARD: Final = "reject_reward"
SERVICE_ADD_POINTS: Final = "add_points"
SERVICE_REMOVE_POINTS: Final = "remove_points"
SERVICE_RESET_DAILY: Final = "reset_daily"
Expand Down
Loading