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 .translation import async_get_translations
1819
1920from .api import PetLibroAPI
2021from .const import (
22+ SENTINEL ,
2123 DEFAULT_FEED ,
2224 DEFAULT_WATER ,
2325 DEFAULT_WEIGHT ,
26+ MANUAL_FEED_PORTIONS ,
2427 DOMAIN ,
25- CommonAPIKeys as API ,
28+ APIKey as API ,
2629 Gender ,
27- UnitTypes ,
30+ Unit ,
2831)
2932from .exceptions import PetLibroCannotConnect , PetLibroInvalidAuth
3033from .hub import PetLibroHub
@@ -159,8 +162,6 @@ def async_get_options_flow(config_entry: ConfigEntry) -> PetlibroOptionsFlow:
159162class PetlibroOptionsFlow (OptionsFlow ):
160163 """Handle an options flow for Petlibro."""
161164
162- _SENTINEL = object ()
163-
164165 def __init__ (self ):
165166 """Initialise Petlibro Options Flow."""
166167 self ._data : dict [str , Any ] = {} # For storing temporary data.
@@ -179,6 +180,8 @@ async def async_step_init(
179180 """Handle the initial options menu."""
180181
181182 _LOGGER .debug ("Starting Petlibro options flow." )
183+ self .translations = await async_get_translations (
184+ self .hass , self .hass .config .language , "common" )
182185 self .entry = self .config_entry
183186 self .hub = self .hass .data [DOMAIN ][self .handler ]
184187 self .api = self .hub .api
@@ -188,44 +191,116 @@ async def async_step_init(
188191 _LOGGER .debug (
189192 "Started Petlibro options flow for account %s" , self .entry .data [CONF_EMAIL ]
190193 )
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 )
221+
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 ()
191232
192- # Using a menu so more things can be added later.
193- return self .async_show_menu (menu_options = ["account_settings" ])
233+ # --- Done
234+ return self .async_abort (
235+ reason = "integration_settings_abort" ,
236+ description_placeholders = {"integration_settings_abort" : "\n " .join (abort_messages )},
237+ )
194238
239+ _LOGGER .debug ("Showing integration settings form." )
240+ return self ._show_integration_settings_form (user_input )
241+
195242 async def async_step_account_settings (
196243 self , user_input : dict [str , Any ] | None = None
197244 ) -> ConfigFlowResult :
198245 """Handle account settings."""
199246
200247 if not self .member :
201- return self .async_abort (reason = "account_update_nomember " )
248+ return self .async_abort (reason = "no_member_data " )
202249
203250 user_input = user_input or {}
204251 if user_input :
205- update_setting = self .collect_updates (
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 )
257+
258+ unit_updates = self .collect_updates (
206259 fields = (API .FEED_UNIT , API .WATER_UNIT , API .WEIGHT_UNIT ),
207- user_input = user_input . pop ( "measurement_unit" , {}) ,
208- enum_cls = UnitTypes ,
260+ user_input = measurement_units_raw ,
261+ enum_cls = Unit ,
209262 )
210263
211- update_info = self .collect_updates (
264+ info_updates = self .collect_updates (
212265 fields = (API .NICKNAME , API .GENDER ),
213- user_input = user_input ,
266+ user_input = account_info_raw ,
214267 special = {
215268 API .NICKNAME : lambda v : v or "" ,
216269 API .GENDER : lambda v : self .validate_enum (API .GENDER , v , Gender ),
217270 },
218271 )
219272
220- if not (update_info or update_setting ):
221- _LOGGER .debug ("No account settings were changed." )
222- return self .async_abort (reason = "account_update_nochanges" )
223-
224- no_error = await self .api .member_update_info (update_info , update_setting )
225- 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 )
226275
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
227301 return self .async_abort (
228- 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 )},
229304 )
230305
231306 _LOGGER .debug ("Showing account settings form." )
@@ -234,6 +309,23 @@ async def async_step_account_settings(
234309 # ------------------------------
235310 # Form Builders
236311 # ------------------------------
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+ )
237329
238330 def _show_account_settings_form (self , user_input : dict [str , Any ]) -> ConfigFlowResult :
239331 """Build and show the account settings form."""
@@ -252,12 +344,12 @@ def _show_account_settings_form(self, user_input: dict[str, Any]) -> ConfigFlowR
252344 vol .Required (
253345 str (API .GENDER ),
254346 default = user_input .get (
255- API .GENDER , getattr (self .member , API .GENDER , str ( Gender .NONE ))
347+ API .GENDER , getattr (self .member , API .GENDER , Gender .NONE ). lower
256348 ),
257349 ): selector (
258350 {
259351 "select" : {
260- "options" : [g .name . lower () for g in Gender ],
352+ "options" : [g .lower for g in Gender ],
261353 "mode" : "dropdown" ,
262354 "translation_key" : "member_gender" ,
263355 }
@@ -280,33 +372,32 @@ def _get_measurement_schema(self, user_input: dict[str, Any]) -> vol.Schema:
280372 vol .Required (
281373 str (API .FEED_UNIT ),
282374 default = user_input .get (
283- API .FEED_UNIT , getattr (self .member , API .FEED_UNIT , DEFAULT_FEED . name )
375+ API .FEED_UNIT , getattr (self .member , API .FEED_UNIT , DEFAULT_FEED ). lower
284376 ),
285- ): self ._unit_selector (
286- (UnitTypes .CUPS , UnitTypes .OUNCES , UnitTypes .GRAMS , UnitTypes .MILLILITERS )
287- ),
377+ ): self ._unit_selector ((Unit .CUPS , Unit .OUNCES , Unit .GRAMS , Unit .MILLILITERS )),
288378 vol .Required (
289379 str (API .WATER_UNIT ),
290380 default = user_input .get (
291- API .WATER_UNIT , getattr (self .member , API .WATER_UNIT , DEFAULT_WATER . name )
381+ API .WATER_UNIT , getattr (self .member , API .WATER_UNIT , DEFAULT_WATER ). lower
292382 ),
293- ): self ._unit_selector ((UnitTypes . OUNCES , UnitTypes . MILLILITERS )),
383+ ): self ._unit_selector ((Unit . WATER_OUNCES , Unit . WATER_MILLILITERS )),
294384 vol .Required (
295385 str (API .WEIGHT_UNIT ),
296386 default = user_input .get (
297387 API .WEIGHT_UNIT ,
298- getattr (self .member , API .WEIGHT_UNIT , DEFAULT_WEIGHT . name ) ,
388+ getattr (self .member , API .WEIGHT_UNIT , DEFAULT_WEIGHT ). lower ,
299389 ),
300- ): self ._unit_selector ((UnitTypes .POUNDS , UnitTypes .KILOGRAMS )),
390+ ): self ._unit_selector ((Unit .POUNDS , Unit .KILOGRAMS )),
391+ vol .Optional ("update_all_units" , default = user_input .get ("update_all_units" , False )): bool ,
301392 }
302393 )
303394
304- def _unit_selector (self , options : tuple [Enum , ...]) -> Any :
395+ def _unit_selector (self , options : tuple [Unit , ...]) -> Any :
305396 """Return a dropdown selector for measurement unit options."""
306397 return selector (
307398 {
308399 "select" : {
309- "options" : [o .name . lower () for o in options ],
400+ "options" : [o .lower for o in options ],
310401 "mode" : "dropdown" ,
311402 "translation_key" : "unit_type" ,
312403 }
@@ -324,7 +415,7 @@ def validate_enum(self, api_key: str, form_value: Any, enum_cls: type[Enum]) ->
324415
325416 form_value_str = str (form_value ).upper ()
326417 if form_value_str in enum_cls .__members__ :
327- return enum_cls [form_value_str ]. value
418+ return enum_cls [form_value_str ]
328419
329420 _LOGGER .error ("Invalid value: %s for API key: %s" , form_value , api_key )
330421 return None
@@ -341,9 +432,9 @@ def collect_updates(
341432
342433 for api_key in fields :
343434 form_value = user_input .get (api_key )
344- current_value = getattr (self .member , api_key , self . _SENTINEL )
435+ current_value = getattr (self .member , api_key , SENTINEL )
345436
346- if current_value is self . _SENTINEL :
437+ if current_value is SENTINEL :
347438 _LOGGER .error ("Unsupported API key: %s" , api_key )
348439 continue
349440 if form_value == current_value :
@@ -358,6 +449,14 @@ def collect_updates(
358449 else :
359450 api_value = form_value
360451
361- updates [api_key ] = api_value
362-
452+ if api_value != current_value :
453+ updates [api_key ] = api_value
454+
363455 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