test(config_flow,media_player,remote): digit-delay/send-enter options (store + window/error validation), IME-first text entry with ADB fallback tier, icon column parse/prefill/store (P5-2)

This commit is contained in:
James Mackay committed 2026-10-09 06:00:17 +01:00
1 parent 3d026ae60e
commit a3b397920e
7 files changed
+320 -18

No files matched your search

+38 -9
View File
@@ -20,6 +20,7 @@ from .const import (
CONF_ADB_PORT,
CONF_ADB_STORE_APP_INFO,
CONF_ANDROIDTV_ENTITY,
CONF_APP_ICON_MAP,
CONF_APP_NAME_MAP,
CONF_CHANNEL_DIGIT_DELAY,
CONF_CHANNEL_SEND_ENTER,
@@ -80,13 +81,27 @@ def _find_jellyfin_entity(hass, device_name: str) -> str | None:
return None
def _app_map_to_text(app_map: dict) -> str:
"""Serialize the apps map to one ``package=name`` per line."""
return "\n".join(f"{pkg}={name}" for pkg, name in app_map.items())
def _app_map_to_text(app_map: dict, icon_map: dict | None = None) -> str:
"""Serialize the apps map to one ``package=name[|icon]`` per line."""
icon_map = icon_map or {}
lines = []
for pkg, name in app_map.items():
icon = icon_map.get(pkg)
lines.append(f"{pkg}={name}|{icon}" if icon else f"{pkg}={name}")
return "\n".join(lines)
def _app_map_from_text(text: str) -> dict[str, str] | None:
"""Parse one ``package=name`` per line; None on malformed input."""
def _app_map_from_text(
text: str, icons: dict[str, str] | None = None
) -> dict[str, str] | None:
"""Parse one ``package=name[|icon]`` per line; None on malformed input.
The optional second column (a picture URL or entity id) feeds
entity_picture through CONF_APP_ICON_MAP; single-column maps stay
valid. Malformed icon text (spaces) invalidates the row the same way
a malformed name does.
"""
icons = icons if icons is not None else {}
app_map: dict[str, str] = {}
for line in text.split("\n"):
line = line.strip()
@@ -94,11 +109,18 @@ def _app_map_from_text(text: str) -> dict[str, str] | None:
continue
if "=" not in line:
return None
package, name = line.split("=", 1)
package, value = line.split("=", 1)
parts = value.strip().split("|", 1)
name = parts[0].strip()
icon = parts[1].strip() if len(parts) > 1 else ""
package = package.strip()
name = name.strip()
if not package or not name or " " in package:
return None
if icon:
if " " in icon:
return None
icons[package] = icon
app_map[package] = name
return app_map
@@ -511,12 +533,13 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow):
if delay is not None and not (0.05 <= delay <= 1.0):
errors["base"] = "invalid_digit_delay"
app_map_text = user_input.pop(CONF_APP_NAME_MAP, None)
icons: dict[str, str] = {}
if app_map_text is None:
app_map = self._options.get(CONF_APP_NAME_MAP, {})
elif app_map_text.strip() == "":
app_map = {}
else:
app_map = _app_map_from_text(app_map_text)
app_map = _app_map_from_text(app_map_text, icons)
if app_map is None:
errors["base"] = "invalid_app_map"
if not errors:
@@ -524,16 +547,22 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow):
self._options[CONF_CHANNEL_DIGIT_DELAY] = delay
self._options.update(user_input)
self._options[CONF_APP_NAME_MAP] = app_map
# P5-2: the icon column — blank/sparse keeps prior entries?
# No: the editor is the source of truth, so a save without
# icons clears them (single-column stays valid).
self._options[CONF_APP_ICON_MAP] = icons
return self.async_create_entry(title="", data=self._options)
sel = _build_selectors()
schema_dict = {}
# Apps name/icon map editor (P4-1, core androidtv_remote options
# pattern, simplified): one package=name per line.
# pattern, simplified): one package=name[|icon] per line (P5-2 adds
# the optional icon column).
current_map = self._options.get(CONF_APP_NAME_MAP, {})
current_icons = self._options.get(CONF_APP_ICON_MAP, {})
if current_map:
app_map_default = _app_map_to_text(current_map)
app_map_default = _app_map_to_text(current_map, current_icons)
schema_dict[vol.Optional(CONF_APP_NAME_MAP, default=app_map_default)] = str
else:
schema_dict[vol.Optional(CONF_APP_NAME_MAP)] = str