278 files
This commit is contained in:
1 parent
d7a0a08372
commit
1b4f5f68c6
278 files changed
+36892
-7150
No files matched your search
@@ -35,12 +35,14 @@ from .const import (
|
||||
CONF_BUDGET_CURRENCY,
|
||||
CONF_BUDGET_MONTHLY,
|
||||
CONF_BUDGET_YEARLY,
|
||||
CONF_CURRENCY_DECIMALS,
|
||||
CONF_DEFAULT_WARNING_DAYS,
|
||||
CONF_MAX_NOTIFICATIONS_PER_DAY,
|
||||
CONF_NOTIFICATION_BUNDLE_THRESHOLD,
|
||||
CONF_NOTIFICATION_BUNDLING_ENABLED,
|
||||
CONF_NOTIFICATION_TITLE_STYLE,
|
||||
CONF_NOTIFICATIONS_ENABLED,
|
||||
CONF_NOTIFY_COMPLETED,
|
||||
CONF_NOTIFY_DUE_SOON_ENABLED,
|
||||
CONF_NOTIFY_DUE_SOON_INTERVAL,
|
||||
CONF_NOTIFY_EVENT_ONLY,
|
||||
@@ -59,17 +61,15 @@ from .const import (
|
||||
CONF_SHOPPING_LIST_ENTITY,
|
||||
CONF_SNOOZE_DURATION_HOURS,
|
||||
DEFAULT_BUDGET_CURRENCY,
|
||||
DEFAULT_MAX_NOTIFICATIONS_PER_DAY,
|
||||
DEFAULT_PANEL_ENABLED,
|
||||
DEFAULT_SNOOZE_DURATION_HOURS,
|
||||
DEFAULT_WARNING_DAYS,
|
||||
MAX_NOTIFY_EXTRA_DATA_LENGTH,
|
||||
MAX_PANEL_TITLE_LENGTH,
|
||||
NOTIFY_COMPLETED_MODES,
|
||||
TIME_HHMMSS_PATTERN,
|
||||
)
|
||||
from .helpers.dates import normalize_hhmm
|
||||
from .helpers.i18n import normalize_language
|
||||
from .helpers.notify_targets import build_notify_targets
|
||||
from .helpers.settings_registry import float_range, int_range
|
||||
from .helpers.settings_registry import float_range, int_range, setting_default
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -376,29 +376,25 @@ async def _send_test_to(
|
||||
) -> str:
|
||||
"""Send the test payload to every resolved service; "success" if any went."""
|
||||
try:
|
||||
from .helpers.notify_hooks import KIND_TEST, async_emit_and_dispatch, notification_context
|
||||
from .helpers.notification_manager import build_action_buttons
|
||||
from .helpers.notify_hooks import async_emit_and_dispatch, sample_notification_context
|
||||
|
||||
push_msg = _get_test_result_text(hass, "push_message")
|
||||
service_data: dict[str, Any] = {
|
||||
"title": "Maintenance Supporter",
|
||||
"message": push_msg,
|
||||
}
|
||||
actions_enabled = options.get(CONF_ACTION_COMPLETE_ENABLED, False)
|
||||
skip_enabled = options.get(CONF_ACTION_SKIP_ENABLED, False)
|
||||
snooze_enabled = options.get(CONF_ACTION_SNOOZE_ENABLED, False)
|
||||
if actions_enabled or skip_enabled or snooze_enabled:
|
||||
test_actions: list[dict[str, str]] = []
|
||||
if actions_enabled:
|
||||
test_actions.append({"action": "MS_TEST_COMPLETE", "title": "\u2705 Complete"})
|
||||
if skip_enabled:
|
||||
test_actions.append({"action": "MS_TEST_SKIP", "title": "\u23ed\ufe0f Skip"})
|
||||
if snooze_enabled:
|
||||
test_actions.append({"action": "MS_TEST_SNOOZE", "title": "\U0001f4a4 Snooze"})
|
||||
# The same buttons (translated, per the action toggles) a real
|
||||
# reminder carries \u2014 test ids, which the action listener ignores.
|
||||
test_actions = build_action_buttons(hass, options, normalize_language(hass), entry_id=None, task_id=None, skip_allowed=True)
|
||||
if test_actions:
|
||||
service_data["data"] = {"actions": test_actions}
|
||||
# Dual-path: legacy notify service OR notify entity (send_message).
|
||||
# #165: the test walks the same hook as a real notification, so the
|
||||
# event and the extra-data template can be verified from Settings.
|
||||
context = notification_context(hass, KIND_TEST)
|
||||
# Sample values for EVERY field of the contract (#178), so a template
|
||||
# can be written and verified from Settings before a real reminder fires.
|
||||
context = sample_notification_context(hass)
|
||||
sent_any = False
|
||||
for service in services:
|
||||
if await async_emit_and_dispatch(hass, service, service_data, context, blocking=True):
|
||||
@@ -417,9 +413,35 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
"""Get current options."""
|
||||
return dict(self.config_entry.options or self.config_entry.data)
|
||||
|
||||
def _opt(self, key: str) -> Any:
|
||||
"""The stored value of a setting, or its registry default — every form
|
||||
``default=`` goes through here so the flow can't drift from the WS
|
||||
``settings`` echo (tests/test_settings_defaults.py)."""
|
||||
return self._current.get(key, setting_default(key))
|
||||
|
||||
def _save_and_return(self, user_input: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Merge user input into options and return to the menu."""
|
||||
from .helpers.settings_registry import ALLOWED_SETTING_KEYS
|
||||
|
||||
merged = self._current
|
||||
# HA's NumberSelector hands back floats; the settings registry (WS
|
||||
# writes, export/import) types these keys as int. Store what the
|
||||
# registry expects so a settings backup round-trips (bug audit
|
||||
# 2026-09-12).
|
||||
for key, value in list(user_input.items()):
|
||||
if ALLOWED_SETTING_KEYS.get(key) is int and isinstance(value, float) and value.is_integer():
|
||||
user_input[key] = int(value)
|
||||
# The TimeSelector hands back "HH:MM:SS"; the registry caps the quiet
|
||||
# hours at 5 chars, so an 8-char value was dropped on a settings
|
||||
# export/import. Store the canonical "HH:MM" (an unparseable value
|
||||
# keeps the previous setting — the form's default coerces it anyway).
|
||||
for key in (CONF_QUIET_HOURS_START, CONF_QUIET_HOURS_END):
|
||||
if key in user_input:
|
||||
normalized = normalize_hhmm(user_input[key])
|
||||
if normalized is None:
|
||||
user_input.pop(key)
|
||||
else:
|
||||
user_input[key] = normalized
|
||||
merged.update(user_input)
|
||||
self.hass.config_entries.async_update_entry(self.config_entry, options=merged)
|
||||
return self.async_show_menu(
|
||||
@@ -475,43 +497,41 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
if user_input is not None:
|
||||
return self._save_and_return(user_input)
|
||||
|
||||
current = self._current
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="advanced_features",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_ADAPTIVE,
|
||||
default=current.get(CONF_ADVANCED_ADAPTIVE, False),
|
||||
default=self._opt(CONF_ADVANCED_ADAPTIVE),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_PREDICTIONS,
|
||||
default=current.get(CONF_ADVANCED_PREDICTIONS, False),
|
||||
default=self._opt(CONF_ADVANCED_PREDICTIONS),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_SEASONAL,
|
||||
default=current.get(CONF_ADVANCED_SEASONAL, False),
|
||||
default=self._opt(CONF_ADVANCED_SEASONAL),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_ENVIRONMENTAL,
|
||||
default=current.get(CONF_ADVANCED_ENVIRONMENTAL, False),
|
||||
default=self._opt(CONF_ADVANCED_ENVIRONMENTAL),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_BUDGET,
|
||||
default=current.get(CONF_ADVANCED_BUDGET, False),
|
||||
default=self._opt(CONF_ADVANCED_BUDGET),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_GROUPS,
|
||||
default=current.get(CONF_ADVANCED_GROUPS, False),
|
||||
default=self._opt(CONF_ADVANCED_GROUPS),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_CHECKLISTS,
|
||||
default=current.get(CONF_ADVANCED_CHECKLISTS, False),
|
||||
default=self._opt(CONF_ADVANCED_CHECKLISTS),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_SCHEDULE_TIME,
|
||||
default=current.get(CONF_ADVANCED_SCHEDULE_TIME, False),
|
||||
default=self._opt(CONF_ADVANCED_SCHEDULE_TIME),
|
||||
): selector.BooleanSelector(),
|
||||
}
|
||||
),
|
||||
@@ -542,18 +562,17 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
for u in non_admin
|
||||
]
|
||||
|
||||
current = self._current
|
||||
return self.async_show_form(
|
||||
step_id="panel_access",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_OPERATOR_WRITE_ENABLED,
|
||||
default=current.get(CONF_OPERATOR_WRITE_ENABLED, False),
|
||||
default=self._opt(CONF_OPERATOR_WRITE_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADMIN_PANEL_USER_IDS,
|
||||
default=current.get(CONF_ADMIN_PANEL_USER_IDS, []),
|
||||
default=self._opt(CONF_ADMIN_PANEL_USER_IDS),
|
||||
): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=options,
|
||||
@@ -605,7 +624,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
return self._save_and_return(user_input)
|
||||
|
||||
current = self._current
|
||||
currency_code = current.get(CONF_BUDGET_CURRENCY, DEFAULT_BUDGET_CURRENCY)
|
||||
currency_code = self._opt(CONF_BUDGET_CURRENCY)
|
||||
currency_options = [
|
||||
selector.SelectOptionDict(value=code, label=f"{code} ({symbol})") for code, symbol in BUDGET_CURRENCIES.items()
|
||||
]
|
||||
@@ -623,7 +642,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_DEFAULT_WARNING_DAYS,
|
||||
default=current.get(CONF_DEFAULT_WARNING_DAYS, DEFAULT_WARNING_DAYS),
|
||||
default=self._opt(CONF_DEFAULT_WARNING_DAYS),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(min=_WARN_MIN, max=_WARN_MAX, step=1, mode=selector.NumberSelectorMode.BOX)
|
||||
),
|
||||
@@ -636,13 +655,25 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
mode=selector.SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
# Decimal places for every displayed amount (0 = whole numbers).
|
||||
vol.Optional(
|
||||
CONF_CURRENCY_DECIMALS,
|
||||
default=self._opt(CONF_CURRENCY_DECIMALS),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=int_range(CONF_CURRENCY_DECIMALS)[0],
|
||||
max=int_range(CONF_CURRENCY_DECIMALS)[1],
|
||||
step=1,
|
||||
mode=selector.NumberSelectorMode.BOX,
|
||||
)
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_NOTIFICATIONS_ENABLED,
|
||||
default=current.get(CONF_NOTIFICATIONS_ENABLED, False),
|
||||
default=self._opt(CONF_NOTIFICATIONS_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_SERVICE,
|
||||
default=current.get(CONF_NOTIFY_SERVICE, ""),
|
||||
default=self._opt(CONF_NOTIFY_SERVICE),
|
||||
): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=notify_services,
|
||||
@@ -658,7 +689,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
): selector.EntitySelector(selector.EntitySelectorConfig(domain="todo")),
|
||||
vol.Optional(
|
||||
CONF_PANEL_ENABLED,
|
||||
default=current.get(CONF_PANEL_ENABLED, DEFAULT_PANEL_ENABLED),
|
||||
default=self._opt(CONF_PANEL_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
# Blank clears the override → panel falls back to the default
|
||||
# title ("Maintenance"). suggested_value pre-fills the current
|
||||
@@ -692,11 +723,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Due Soon ---
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_DUE_SOON_ENABLED,
|
||||
default=current.get(CONF_NOTIFY_DUE_SOON_ENABLED, True),
|
||||
default=self._opt(CONF_NOTIFY_DUE_SOON_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_DUE_SOON_INTERVAL,
|
||||
default=current.get(CONF_NOTIFY_DUE_SOON_INTERVAL, 24),
|
||||
default=self._opt(CONF_NOTIFY_DUE_SOON_INTERVAL),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_NOTIFY_INTERVAL_MIN, max=_NOTIFY_INTERVAL_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -705,11 +736,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Overdue ---
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_OVERDUE_ENABLED,
|
||||
default=current.get(CONF_NOTIFY_OVERDUE_ENABLED, True),
|
||||
default=self._opt(CONF_NOTIFY_OVERDUE_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_OVERDUE_INTERVAL,
|
||||
default=current.get(CONF_NOTIFY_OVERDUE_INTERVAL, 12),
|
||||
default=self._opt(CONF_NOTIFY_OVERDUE_INTERVAL),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_NOTIFY_INTERVAL_MIN, max=_NOTIFY_INTERVAL_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -718,11 +749,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Triggered ---
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_TRIGGERED_ENABLED,
|
||||
default=current.get(CONF_NOTIFY_TRIGGERED_ENABLED, True),
|
||||
default=self._opt(CONF_NOTIFY_TRIGGERED_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_TRIGGERED_INTERVAL,
|
||||
default=current.get(CONF_NOTIFY_TRIGGERED_INTERVAL, 0),
|
||||
default=self._opt(CONF_NOTIFY_TRIGGERED_INTERVAL),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_NOTIFY_INTERVAL_MIN, max=_NOTIFY_INTERVAL_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -731,7 +762,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Quiet Hours ---
|
||||
vol.Optional(
|
||||
CONF_QUIET_HOURS_ENABLED,
|
||||
default=current.get(CONF_QUIET_HOURS_ENABLED, True),
|
||||
default=self._opt(CONF_QUIET_HOURS_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
# v1.4.6 (#44 follow-up): use `or` instead of dict-default so
|
||||
# empty-string / null / non-HH:MM values in storage don't
|
||||
@@ -742,16 +773,16 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# fallback whenever the persisted value isn't a usable time.
|
||||
vol.Optional(
|
||||
CONF_QUIET_HOURS_START,
|
||||
default=_safe_time(current.get(CONF_QUIET_HOURS_START), "22:00"),
|
||||
default=_safe_time(current.get(CONF_QUIET_HOURS_START), setting_default(CONF_QUIET_HOURS_START)),
|
||||
): selector.TimeSelector(),
|
||||
vol.Optional(
|
||||
CONF_QUIET_HOURS_END,
|
||||
default=_safe_time(current.get(CONF_QUIET_HOURS_END), "08:00"),
|
||||
default=_safe_time(current.get(CONF_QUIET_HOURS_END), setting_default(CONF_QUIET_HOURS_END)),
|
||||
): selector.TimeSelector(),
|
||||
# --- Daily Limit ---
|
||||
vol.Optional(
|
||||
CONF_MAX_NOTIFICATIONS_PER_DAY,
|
||||
default=current.get(CONF_MAX_NOTIFICATIONS_PER_DAY, DEFAULT_MAX_NOTIFICATIONS_PER_DAY),
|
||||
default=self._opt(CONF_MAX_NOTIFICATIONS_PER_DAY),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_MAX_PER_DAY_MIN, max=_MAX_PER_DAY_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -760,11 +791,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Bundling ---
|
||||
vol.Optional(
|
||||
CONF_NOTIFICATION_BUNDLING_ENABLED,
|
||||
default=current.get(CONF_NOTIFICATION_BUNDLING_ENABLED, False),
|
||||
default=self._opt(CONF_NOTIFICATION_BUNDLING_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFICATION_BUNDLE_THRESHOLD,
|
||||
default=current.get(CONF_NOTIFICATION_BUNDLE_THRESHOLD, 2),
|
||||
default=self._opt(CONF_NOTIFICATION_BUNDLE_THRESHOLD),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_BUNDLE_MIN, max=_BUNDLE_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -773,7 +804,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# v1.4.0 (#44): notification title style
|
||||
vol.Optional(
|
||||
CONF_NOTIFICATION_TITLE_STYLE,
|
||||
default=current.get(CONF_NOTIFICATION_TITLE_STYLE, "default"),
|
||||
default=self._opt(CONF_NOTIFICATION_TITLE_STYLE),
|
||||
): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=["default", "object_name", "task_name"],
|
||||
@@ -781,15 +812,29 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
translation_key="notification_title_style",
|
||||
)
|
||||
),
|
||||
# #173 follow-up: completion notifications (activity kind).
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_COMPLETED,
|
||||
default=self._opt(CONF_NOTIFY_COMPLETED),
|
||||
): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=list(NOTIFY_COMPLETED_MODES),
|
||||
mode=selector.SelectSelectorMode.DROPDOWN,
|
||||
translation_key="notify_completed",
|
||||
)
|
||||
),
|
||||
# 2.80 (#165) / #173: your own notification rule — the same
|
||||
# two settings the panel's Settings tab offers.
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_EVENT_ONLY,
|
||||
default=current.get(CONF_NOTIFY_EVENT_ONLY, False),
|
||||
default=self._opt(CONF_NOTIFY_EVENT_ONLY),
|
||||
): selector.BooleanSelector(),
|
||||
# default (not suggested_value): an emptied optional text
|
||||
# field is omitted from user_input and the old template
|
||||
# survived - a default round-trips "" (bug audit 2026-09-12).
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_EXTRA_DATA,
|
||||
description={"suggested_value": current.get(CONF_NOTIFY_EXTRA_DATA, "")},
|
||||
default=self._opt(CONF_NOTIFY_EXTRA_DATA),
|
||||
): selector.TextSelector(
|
||||
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT, multiline=True)
|
||||
),
|
||||
@@ -804,27 +849,25 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
if user_input is not None:
|
||||
return self._save_and_return(user_input)
|
||||
|
||||
current = self._current
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="notification_actions",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_ACTION_COMPLETE_ENABLED,
|
||||
default=current.get(CONF_ACTION_COMPLETE_ENABLED, False),
|
||||
default=self._opt(CONF_ACTION_COMPLETE_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ACTION_SKIP_ENABLED,
|
||||
default=current.get(CONF_ACTION_SKIP_ENABLED, False),
|
||||
default=self._opt(CONF_ACTION_SKIP_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ACTION_SNOOZE_ENABLED,
|
||||
default=current.get(CONF_ACTION_SNOOZE_ENABLED, False),
|
||||
default=self._opt(CONF_ACTION_SNOOZE_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_SNOOZE_DURATION_HOURS,
|
||||
default=current.get(CONF_SNOOZE_DURATION_HOURS, DEFAULT_SNOOZE_DURATION_HOURS),
|
||||
default=self._opt(CONF_SNOOZE_DURATION_HOURS),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_SNOOZE_MIN, max=_SNOOZE_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -863,9 +906,8 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
if user_input is not None:
|
||||
return self._save_and_return(user_input)
|
||||
|
||||
current = self._current
|
||||
currency_code = current.get(CONF_BUDGET_CURRENCY, DEFAULT_BUDGET_CURRENCY)
|
||||
currency_symbol = BUDGET_CURRENCIES.get(currency_code, "€")
|
||||
currency_code = self._opt(CONF_BUDGET_CURRENCY)
|
||||
currency_symbol = BUDGET_CURRENCIES.get(currency_code, BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY])
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="budget_settings",
|
||||
@@ -873,7 +915,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_BUDGET_MONTHLY,
|
||||
default=current.get(CONF_BUDGET_MONTHLY, 0.0),
|
||||
default=self._opt(CONF_BUDGET_MONTHLY),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_BUDGET_MONTHLY_MIN,
|
||||
@@ -885,7 +927,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_BUDGET_YEARLY,
|
||||
default=current.get(CONF_BUDGET_YEARLY, 0.0),
|
||||
default=self._opt(CONF_BUDGET_YEARLY),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_BUDGET_YEARLY_MIN,
|
||||
@@ -897,11 +939,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_BUDGET_ALERTS_ENABLED,
|
||||
default=current.get(CONF_BUDGET_ALERTS_ENABLED, False),
|
||||
default=self._opt(CONF_BUDGET_ALERTS_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_BUDGET_ALERT_THRESHOLD,
|
||||
default=current.get(CONF_BUDGET_ALERT_THRESHOLD, 80),
|
||||
default=self._opt(CONF_BUDGET_ALERT_THRESHOLD),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_ALERT_MIN,
|
||||
|
||||
Reference in new issue
Block a user