feat(config_flow,media_player): apps name/icon map editor in the options flow (package=name per line, invalid input error) + app_name resolves through CONF_APP_NAME_MAP after the entity/push tiers (P4-1b)
This commit is contained in:
1 parent
1dbb695cec
commit
61c7ac99a3
2 files changed
+61
-2
No files matched your search
@@ -18,6 +18,7 @@ from .const import (
|
|||||||
CONF_ADB_PORT,
|
CONF_ADB_PORT,
|
||||||
CONF_ADB_STORE_APP_INFO,
|
CONF_ADB_STORE_APP_INFO,
|
||||||
CONF_ANDROIDTV_ENTITY,
|
CONF_ANDROIDTV_ENTITY,
|
||||||
|
CONF_APP_NAME_MAP,
|
||||||
CONF_JELLYFIN_ENTITY,
|
CONF_JELLYFIN_ENTITY,
|
||||||
CONF_PAIRING_CODE,
|
CONF_PAIRING_CODE,
|
||||||
CONF_POWER_ENTITY,
|
CONF_POWER_ENTITY,
|
||||||
@@ -75,6 +76,29 @@ def _find_jellyfin_entity(hass, device_name: str) -> str | None:
|
|||||||
return 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_from_text(text: str) -> dict[str, str] | None:
|
||||||
|
"""Parse one ``package=name`` per line; None on malformed input."""
|
||||||
|
app_map: dict[str, str] = {}
|
||||||
|
for line in text.split("\n"):
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if "=" not in line:
|
||||||
|
return None
|
||||||
|
package, name = line.split("=", 1)
|
||||||
|
package = package.strip()
|
||||||
|
name = name.strip()
|
||||||
|
if not package or not name or " " in package:
|
||||||
|
return None
|
||||||
|
app_map[package] = name
|
||||||
|
return app_map
|
||||||
|
|
||||||
|
|
||||||
def _user_schema_dict(sel, defaults: dict | None = None) -> dict:
|
def _user_schema_dict(sel, defaults: dict | None = None) -> dict:
|
||||||
"""User-step schema — the androidtv entity is optional (standalone).
|
"""User-step schema — the androidtv entity is optional (standalone).
|
||||||
|
|
||||||
@@ -458,13 +482,34 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow):
|
|||||||
|
|
||||||
async def async_step_init(self, user_input=None):
|
async def async_step_init(self, user_input=None):
|
||||||
"""Single-page options."""
|
"""Single-page options."""
|
||||||
|
errors = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
|
app_map_text = user_input.pop(CONF_APP_NAME_MAP, None)
|
||||||
|
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)
|
||||||
|
if app_map is None:
|
||||||
|
errors["base"] = "invalid_app_map"
|
||||||
|
if not errors:
|
||||||
self._options.update(user_input)
|
self._options.update(user_input)
|
||||||
|
self._options[CONF_APP_NAME_MAP] = app_map
|
||||||
return self.async_create_entry(title="", data=self._options)
|
return self.async_create_entry(title="", data=self._options)
|
||||||
|
|
||||||
sel = _build_selectors()
|
sel = _build_selectors()
|
||||||
schema_dict = {}
|
schema_dict = {}
|
||||||
|
|
||||||
|
# Apps name/icon map editor (P4-1, core androidtv_remote options
|
||||||
|
# pattern, simplified): one package=name per line.
|
||||||
|
current_map = self._options.get(CONF_APP_NAME_MAP, {})
|
||||||
|
if current_map:
|
||||||
|
app_map_default = _app_map_to_text(current_map)
|
||||||
|
schema_dict[vol.Optional(CONF_APP_NAME_MAP, default=app_map_default)] = str
|
||||||
|
else:
|
||||||
|
schema_dict[vol.Optional(CONF_APP_NAME_MAP)] = str
|
||||||
|
|
||||||
# Jellyfin
|
# Jellyfin
|
||||||
jf = self._options.get(CONF_JELLYFIN_ENTITY)
|
jf = self._options.get(CONF_JELLYFIN_ENTITY)
|
||||||
jf_key = vol.Optional(CONF_JELLYFIN_ENTITY)
|
jf_key = vol.Optional(CONF_JELLYFIN_ENTITY)
|
||||||
@@ -532,4 +577,5 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow):
|
|||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="init",
|
step_id="init",
|
||||||
data_schema=vol.Schema(schema_dict),
|
data_schema=vol.Schema(schema_dict),
|
||||||
|
errors=errors,
|
||||||
)
|
)
|
||||||
@@ -48,6 +48,7 @@ from .const import (
|
|||||||
CHANNEL_DIGIT_DELAY,
|
CHANNEL_DIGIT_DELAY,
|
||||||
CONF_ADB_STORE_APP_INFO,
|
CONF_ADB_STORE_APP_INFO,
|
||||||
CONF_ANDROIDTV_ENTITY,
|
CONF_ANDROIDTV_ENTITY,
|
||||||
|
CONF_APP_NAME_MAP,
|
||||||
CONF_CHANNEL_DIGIT_DELAY,
|
CONF_CHANNEL_DIGIT_DELAY,
|
||||||
CONF_CHANNEL_SEND_ENTER,
|
CONF_CHANNEL_SEND_ENTER,
|
||||||
CONF_JELLYFIN_ENTITY,
|
CONF_JELLYFIN_ENTITY,
|
||||||
@@ -511,16 +512,28 @@ class NvidiaShieldMediaPlayer(MediaPlayerEntity):
|
|||||||
return self._manager.current_app
|
return self._manager.current_app
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _app_name_map(self) -> dict:
|
||||||
|
"""The user's apps name/icon map (P4-1 options editor)."""
|
||||||
|
return self._entry.options.get(CONF_APP_NAME_MAP) or {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def app_name(self):
|
def app_name(self):
|
||||||
|
"""Entity tier → protocol-push tier → the P4-1 apps map.
|
||||||
|
|
||||||
|
The map (package → friendly name) resolves after the entity and
|
||||||
|
push tiers; unmapped packages stay raw (icons follow HA's media
|
||||||
|
image lookup by name).
|
||||||
|
"""
|
||||||
atv = self._read(self._androidtv_entity) if self._androidtv_entity else None
|
atv = self._read(self._androidtv_entity) if self._androidtv_entity else None
|
||||||
if atv is not None:
|
if atv is not None:
|
||||||
app_name = atv.attributes.get("app_name")
|
app_name = atv.attributes.get("app_name")
|
||||||
if app_name:
|
if app_name:
|
||||||
return app_name
|
return app_name
|
||||||
if self._manager is not None:
|
if self._manager is not None:
|
||||||
# Name resolution is deferred app-map work; the package is truth.
|
package = self._manager.current_app
|
||||||
return self._manager.current_app
|
if package:
|
||||||
|
return self._app_name_map.get(package, package)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
Reference in new issue
Block a user