From e5716efa9ce508ff8c9e21cfc8123b612e80206b Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 25 Oct 2022 09:03:19 +0100 Subject: [PATCH] Add visual image preview during generic camera options flow (#80392) Co-authored-by: Dave T --- .../components/generic/config_flow.py | 47 ++++++++++++++----- homeassistant/components/generic/strings.json | 7 +++ tests/components/generic/test_config_flow.py | 38 +++++++++++++-- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/generic/config_flow.py b/homeassistant/components/generic/config_flow.py index 19ab7666b7c..09f52705734 100644 --- a/homeassistant/components/generic/config_flow.py +++ b/homeassistant/components/generic/config_flow.py @@ -394,8 +394,7 @@ class GenericOptionsFlowHandler(OptionsFlow): def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Generic IP Camera options flow.""" self.config_entry = config_entry - self.cached_user_input: dict[str, Any] = {} - self.cached_title = "" + self.user_input: dict[str, Any] = {} async def async_step_init( self, user_input: dict[str, Any] | None = None @@ -410,9 +409,7 @@ class GenericOptionsFlowHandler(OptionsFlow): ) errors = errors | await async_test_stream(hass, user_input) still_url = user_input.get(CONF_STILL_IMAGE_URL) - stream_url = user_input.get(CONF_STREAM_SOURCE) if not errors: - title = slug(hass, still_url) or slug(hass, stream_url) or DEFAULT_NAME if still_url is None: # If user didn't specify a still image URL, # The automatically generated still image that stream generates @@ -438,10 +435,10 @@ class GenericOptionsFlowHandler(OptionsFlow): ), ), } - return self.async_create_entry( - title=title, - data=data, - ) + self.user_input = data + # temporary preview for user to check the image + self.context["preview_cam"] = data + return await self.async_step_confirm_still() return self.async_show_form( step_id="init", data_schema=build_schema( @@ -452,6 +449,30 @@ class GenericOptionsFlowHandler(OptionsFlow): errors=errors, ) + async def async_step_confirm_still( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle user clicking confirm after still preview.""" + if user_input: + if not user_input.get(CONF_CONFIRMED_OK): + return await self.async_step_init() + return self.async_create_entry( + title=self.config_entry.title, + data=self.user_input, + ) + register_preview(self.hass) + preview_url = f"/api/generic/preview_flow_image/{self.flow_id}?t={datetime.now().isoformat()}" + return self.async_show_form( + step_id="confirm_still", + data_schema=vol.Schema( + { + vol.Required(CONF_CONFIRMED_OK, default=False): bool, + } + ), + description_placeholders={"preview_url": preview_url}, + errors=None, + ) + class CameraImagePreview(HomeAssistantView): """Camera view to temporarily serve an image.""" @@ -468,9 +489,13 @@ class CameraImagePreview(HomeAssistantView): """Start a GET request.""" _LOGGER.debug("processing GET request for flow_id=%s", flow_id) try: - flow: FlowResult = self.hass.config_entries.flow.async_get(flow_id) - except UnknownFlow as exc: - raise web.HTTPNotFound() from exc + flow = self.hass.config_entries.flow.async_get(flow_id) + except UnknownFlow: + try: + flow = self.hass.config_entries.options.async_get(flow_id) + except UnknownFlow as exc: + _LOGGER.warning("Unknown flow while getting image preview") + raise web.HTTPNotFound() from exc user_input = flow["context"]["preview_cam"] camera = GenericCamera(self.hass, user_input, flow_id, "preview") if not camera.is_on: diff --git a/homeassistant/components/generic/strings.json b/homeassistant/components/generic/strings.json index 7ada90e3c90..7c7e44a67e4 100644 --- a/homeassistant/components/generic/strings.json +++ b/homeassistant/components/generic/strings.json @@ -73,6 +73,13 @@ "data": { "content_type": "[%key:component::generic::config::step::content_type::data::content_type%]" } + }, + "confirm_still": { + "title": "[%key:component::generic::config::step::user_confirm_still::title%]", + "description": "[%key:component::generic::config::step::user_confirm_still::description%]", + "data": { + "confirmed_ok": "[%key:component::generic::config::step::user_confirm_still::data::confirmed_ok%]" + } } }, "error": { diff --git a/tests/components/generic/test_config_flow.py b/tests/components/generic/test_config_flow.py index 6ec1ce0c32b..ba4ff4dbd0c 100644 --- a/tests/components/generic/test_config_flow.py +++ b/tests/components/generic/test_config_flow.py @@ -596,7 +596,13 @@ async def test_options_template_error(hass, fakeimgbytes_png, mock_create_stream result["flow_id"], user_input=data, ) - assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result2["type"] == data_entry_flow.FlowResultType.FORM + assert result2["step_id"] == "confirm_still" + + result2a = await hass.config_entries.options.async_configure( + result2["flow_id"], user_input={CONF_CONFIRMED_OK: True} + ) + assert result2a["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY result3 = await hass.config_entries.options.async_init(mock_entry.entry_id) assert result3["type"] == data_entry_flow.FlowResultType.FORM @@ -681,10 +687,16 @@ async def test_options_only_stream(hass, fakeimgbytes_png, mock_create_stream): # try updating the config options with mock_create_stream: - result3 = await hass.config_entries.options.async_configure( + result2 = await hass.config_entries.options.async_configure( result["flow_id"], user_input=data, ) + assert result2["type"] == data_entry_flow.FlowResultType.FORM + assert result2["step_id"] == "confirm_still" + + result3 = await hass.config_entries.options.async_configure( + result2["flow_id"], user_input={CONF_CONFIRMED_OK: True} + ) assert result3["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result3["data"][CONF_CONTENT_TYPE] == "image/jpeg" @@ -809,4 +821,24 @@ async def test_use_wallclock_as_timestamps_option( result["flow_id"], user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA}, ) - assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result2["type"] == data_entry_flow.FlowResultType.FORM + # Test what happens if user rejects the preview + result3 = await hass.config_entries.options.async_configure( + result2["flow_id"], user_input={CONF_CONFIRMED_OK: False} + ) + assert result3["type"] == data_entry_flow.FlowResultType.FORM + assert result3["step_id"] == "init" + with patch( + "homeassistant.components.generic.async_setup_entry", return_value=True + ), mock_create_stream: + result4 = await hass.config_entries.options.async_configure( + result3["flow_id"], + user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA}, + ) + assert result4["type"] == data_entry_flow.FlowResultType.FORM + assert result4["step_id"] == "confirm_still" + result5 = await hass.config_entries.options.async_configure( + result4["flow_id"], + user_input={CONF_CONFIRMED_OK: True}, + ) + assert result5["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY