diff --git a/custom_components/nvidia_shield/config_flow.py b/custom_components/nvidia_shield/config_flow.py index 71125e1..339989f 100644 --- a/custom_components/nvidia_shield/config_flow.py +++ b/custom_components/nvidia_shield/config_flow.py @@ -18,6 +18,7 @@ from .const import ( CONF_ADB_PORT, CONF_ADB_STORE_APP_INFO, CONF_ANDROIDTV_ENTITY, + CONF_APP_NAME_MAP, CONF_JELLYFIN_ENTITY, CONF_PAIRING_CODE, CONF_POWER_ENTITY, @@ -75,6 +76,29 @@ 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_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: """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): """Single-page options.""" + errors = {} if user_input is not None: - self._options.update(user_input) - return self.async_create_entry(title="", data=self._options) + 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[CONF_APP_NAME_MAP] = app_map + 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. + 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 jf = self._options.get(CONF_JELLYFIN_ENTITY) jf_key = vol.Optional(CONF_JELLYFIN_ENTITY) @@ -532,4 +577,5 @@ class NvidiaShieldOptionsFlow(config_entries.OptionsFlow): return self.async_show_form( step_id="init", data_schema=vol.Schema(schema_dict), + errors=errors, ) diff --git a/custom_components/nvidia_shield/media_player.py b/custom_components/nvidia_shield/media_player.py index b07224a..3b4fab5 100644 --- a/custom_components/nvidia_shield/media_player.py +++ b/custom_components/nvidia_shield/media_player.py @@ -48,6 +48,7 @@ from .const import ( CHANNEL_DIGIT_DELAY, CONF_ADB_STORE_APP_INFO, CONF_ANDROIDTV_ENTITY, + CONF_APP_NAME_MAP, CONF_CHANNEL_DIGIT_DELAY, CONF_CHANNEL_SEND_ENTER, CONF_JELLYFIN_ENTITY, @@ -511,16 +512,28 @@ class NvidiaShieldMediaPlayer(MediaPlayerEntity): return self._manager.current_app 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 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 if atv is not None: app_name = atv.attributes.get("app_name") if app_name: return app_name if self._manager is not None: - # Name resolution is deferred app-map work; the package is truth. - return self._manager.current_app + package = self._manager.current_app + if package: + return self._app_name_map.get(package, package) return None @property