1010import voluptuous as vol
1111
1212from homeassistant .config_entries import ConfigEntry , ConfigFlow , ConfigFlowResult , OptionsFlow
13- from homeassistant .const import CONF_API_TOKEN , CONF_EMAIL , CONF_PASSWORD , CONF_REGION , Platform
13+ from homeassistant .const import CONF_API_TOKEN , CONF_EMAIL , CONF_PASSWORD , CONF_REGION
1414from homeassistant .core import callback
1515from homeassistant .data_entry_flow import section
1616from homeassistant .helpers .aiohttp_client import async_get_clientsession
1717from homeassistant .helpers .selector import selector
18- from homeassistant .helpers .entity_registry import async_get as get_entity_registry
18+ from homeassistant .helpers .translation import async_get_translations
1919
2020from .api import PetLibroAPI
2121from .const import (
22+ SENTINEL ,
2223 DEFAULT_FEED ,
2324 DEFAULT_WATER ,
2425 DEFAULT_WEIGHT ,
25- ROUNDING_RULES ,
26+ MANUAL_FEED_PORTIONS ,
2627 DOMAIN ,
2728 APIKey as API ,
2829 Gender ,
@@ -161,8 +162,6 @@ def async_get_options_flow(config_entry: ConfigEntry) -> PetlibroOptionsFlow:
161162class PetlibroOptionsFlow (OptionsFlow ):
162163 """Handle an options flow for Petlibro."""
163164
164- _SENTINEL = object ()
165-
166165 def __init__ (self ):
167166 """Initialise Petlibro Options Flow."""
168167 self ._data : dict [str , Any ] = {} # For storing temporary data.
@@ -181,6 +180,8 @@ async def async_step_init(
181180 """Handle the initial options menu."""
182181
183182 _LOGGER .debug ("Starting Petlibro options flow." )
183+ self .translations = await async_get_translations (
184+ self .hass , self .hass .config .language , "common" )
184185 self .entry = self .config_entry
185186 self .hub = self .hass .data [DOMAIN ][self .handler ]
186187 self .api = self .hub .api
@@ -190,73 +191,116 @@ async def async_step_init(
190191 _LOGGER .debug (
191192 "Started Petlibro options flow for account %s" , self .entry .data [CONF_EMAIL ]
192193 )
194+ return self .async_show_menu (menu_options = ["integration_settings" , "account_settings" ])
195+
196+ async def async_step_integration_settings (
197+ self , user_input : dict [str , Any ] | None = None
198+ ) -> ConfigFlowResult :
199+ """Handle integration-level settings for the Petlibro integration."""
200+
201+ user_input = user_input or {}
202+
203+ if user_input :
204+ manual_feed_portions = user_input .get (MANUAL_FEED_PORTIONS )
205+ current_value = self .entry .options .get (MANUAL_FEED_PORTIONS , False )
206+
207+ # --- Nothing changed
208+ if manual_feed_portions == current_value :
209+ _LOGGER .debug ("No integration settings changed." )
210+ return self .async_abort (reason = self .get_common_translation ("no_settings_changed" , "No settings were changed" ))
211+
212+ # --- Update option
213+ self .hub .update_options ({MANUAL_FEED_PORTIONS : manual_feed_portions })
214+ _LOGGER .debug ("Updated %s to %s" , MANUAL_FEED_PORTIONS , manual_feed_portions )
215+
216+ abort_messages = [self .get_common_translation ("settings_updated" , "Settings updated" )]
217+
218+ # --- Reload integration if feed unit is cups
219+ if self .member .feedUnitType == Unit .CUPS :
220+ reload_integration = await self .hub .unit_entities .sync_manual_feed_entity_visibility (Unit .CUPS )
193221
194- # Using a menu so more things can be added later.
195- return self .async_show_menu (menu_options = ["account_settings" ])
222+ if reload_integration :
223+ abort_messages .append (
224+ self .get_common_translation ("reloading_integration" , "The integration is being reloaded" )
225+ )
226+ _LOGGER .debug ("'manual_feed_portions' value changed while feed unit is 'cups', reloading integration." )
227+ self .hass .config_entries .async_schedule_reload (self .handler )
228+ else :
229+ _LOGGER .debug ("No Manual Feed entities found — nothing to reload." )
230+ else :
231+ await self .hub .async_refresh ()
232+
233+ # --- Done
234+ return self .async_abort (
235+ reason = "integration_settings_abort" ,
236+ description_placeholders = {"integration_settings_abort" : "\n " .join (abort_messages )},
237+ )
196238
239+ _LOGGER .debug ("Showing integration settings form." )
240+ return self ._show_integration_settings_form (user_input )
241+
197242 async def async_step_account_settings (
198243 self , user_input : dict [str , Any ] | None = None
199244 ) -> ConfigFlowResult :
200245 """Handle account settings."""
201246
202247 if not self .member :
203- return self .async_abort (reason = "account_update_nomember " )
248+ return self .async_abort (reason = "no_member_data " )
204249
205250 user_input = user_input or {}
206- if user_input :
207- update_setting_temp = user_input .pop ("measurement_unit" , {})
208- update_info_temp = user_input .copy ()
209- user_input .update (** update_setting_temp )
210- update_all_units = update_setting_temp .pop ("update_all_units" , False )
251+ if user_input :
252+ # --- Extract updates
253+ measurement_units_raw : dict = user_input .pop ("measurement_unit" , {})
254+ account_info_raw = user_input .copy ()
255+ user_input .update (** measurement_units_raw )
256+ update_all_units = measurement_units_raw .pop ("update_all_units" , False )
211257
212- update_setting = self .collect_updates (
258+ unit_updates = self .collect_updates (
213259 fields = (API .FEED_UNIT , API .WATER_UNIT , API .WEIGHT_UNIT ),
214- user_input = update_setting_temp ,
260+ user_input = measurement_units_raw ,
215261 enum_cls = Unit ,
216262 )
217263
218- update_info = self .collect_updates (
264+ info_updates = self .collect_updates (
219265 fields = (API .NICKNAME , API .GENDER ),
220- user_input = update_info_temp ,
266+ user_input = account_info_raw ,
221267 special = {
222268 API .NICKNAME : lambda v : v or "" ,
223269 API .GENDER : lambda v : self .validate_enum (API .GENDER , v , Gender ),
224270 },
225271 )
226-
227- if update_setting or update_all_units :
228- registry = get_entity_registry (self .hass )
229- for unit_type in self .hub .unit_sensor_unique_ids :
230- unit = (input if isinstance (input := update_setting .get (unit_type ), Unit )
231- else Unit (input ) if input else getattr (self .member , unit_type , None ))
232- if (unit_type not in update_setting or not unit or not unit .device_class ) and not update_all_units :
233- continue
234- _LOGGER .debug ("Updating %s sensor entities" , unit_type )
235- if update_all_units and unit_type == API .FEED_UNIT :
236- target_units = {"weight" : unit if unit .device_class == "weight" else Unit .GRAMS ,
237- "volume" : unit if unit .device_class == "volume" else Unit .MILLILITERS }
238- else :
239- target_units = {unit .device_class : unit }
240- for device_class , target_unit in target_units .items ():
241- display_precision = ROUNDING_RULES .get (target_unit , 0 )
242- options = { "unit_of_measurement" : target_unit .symbol ,
243- "display_precision" : display_precision ,
244- "suggested_display_precision" : display_precision }
245- for unique_id in self .hub .unit_sensor_unique_ids .get (unit_type , {}).get (device_class , []):
246- entity_id = registry .async_get_entity_id (Platform .SENSOR , DOMAIN , unique_id )
247- _LOGGER .debug ("Setting %s to %s with display precision %s" , entity_id , unit .symbol , display_precision )
248- registry .async_update_entity_options (entity_id , Platform .SENSOR , options )
249-
250- if not (update_info or update_setting ):
251- _LOGGER .debug ("No account settings were changed." )
252- reason = "account_update_nochanges" + ("_update_sensors" if update_all_units else "" )
253- return self .async_abort (reason = reason )
254272
255- no_error = await self . api . member_update_info ( update_info , update_setting )
256- await self .hub .async_refresh ( force_member = True )
273+ # --- Update entity options if units changed or update_all_units
274+ reload_integration = await self .hub .unit_entities . update_sensor_entity_units ( unit_updates , update_all_units )
257275
276+ # --- Apply account-level changes through API
277+ abort_messages = []
278+ if not (info_updates or unit_updates ):
279+ _LOGGER .debug ("No account settings were changed." )
280+ abort_messages .append (self .get_common_translation ("no_settings_changed" , "No settings were changed" ))
281+ else :
282+ success = await self .api .member_update_info (update_info = info_updates , update_setting = unit_updates )
283+ if success :
284+ abort_messages .append (self .get_common_translation ("account_updated" , "Account update successful" ))
285+ else :
286+ _LOGGER .error ("Error updating account info via API." )
287+ return self .async_abort (reason = "error_check_logs" )
288+
289+ if update_all_units :
290+ abort_messages .append (self .get_common_translation ("sensors_updated" , "Sensor entities were updated" ))
291+
292+ # --- Reload or refresh
293+ if reload_integration :
294+ _LOGGER .debug ("Reloading integration due to feed unit change to/from cups, or update_all_units chosen." )
295+ abort_messages .append (self .get_common_translation ("reloading_integration" , "The integration is being reloaded" ))
296+ self .hass .config_entries .async_schedule_reload (self .handler )
297+ elif info_updates or unit_updates :
298+ await self .hub .async_refresh (force_member = True )
299+
300+ # --- Done
258301 return self .async_abort (
259- reason = "account_update_success" if no_error else "error_check_logs"
302+ reason = "account_settings_abort" ,
303+ description_placeholders = {"account_settings_abort" : "\n " .join (abort_messages )},
260304 )
261305
262306 _LOGGER .debug ("Showing account settings form." )
@@ -265,6 +309,23 @@ async def async_step_account_settings(
265309 # ------------------------------
266310 # Form Builders
267311 # ------------------------------
312+
313+ def _show_integration_settings_form (self , user_input : dict [str , Any ]) -> ConfigFlowResult :
314+ """Build and show the integration settings form."""
315+
316+ return self .async_show_form (
317+ data_schema = vol .Schema (
318+ {
319+ vol .Optional (
320+ MANUAL_FEED_PORTIONS ,
321+ default = self .entry .options .get (MANUAL_FEED_PORTIONS , False ),
322+ ): selector ({"boolean" : {}})
323+ }
324+ ),
325+ description_placeholders = {
326+ "uom" : getattr (self .member , API .FEED_UNIT , DEFAULT_FEED ).symbol
327+ },
328+ )
268329
269330 def _show_account_settings_form (self , user_input : dict [str , Any ]) -> ConfigFlowResult :
270331 """Build and show the account settings form."""
@@ -371,9 +432,9 @@ def collect_updates(
371432
372433 for api_key in fields :
373434 form_value = user_input .get (api_key )
374- current_value = getattr (self .member , api_key , self . _SENTINEL )
435+ current_value = getattr (self .member , api_key , SENTINEL )
375436
376- if current_value is self . _SENTINEL :
437+ if current_value is SENTINEL :
377438 _LOGGER .error ("Unsupported API key: %s" , api_key )
378439 continue
379440 if form_value == current_value :
@@ -392,3 +453,10 @@ def collect_updates(
392453 updates [api_key ] = api_value
393454
394455 return updates
456+
457+ def get_common_translation (self , translation_key : str , fallback : str = "" ) -> str :
458+ """Get a translated string under the 'common' key from the user's chosen language."""
459+ translation_path = f"component.{ DOMAIN } .common.{ translation_key } "
460+ if translation_path not in self .translations :
461+ _LOGGER .warning ("Translation key %s not found in translation file." , translation_key )
462+ return self .translations .get (translation_path , fallback )
0 commit comments