From 0a2d2574211f4dbce497eec90e3fcd18d1b351fb Mon Sep 17 00:00:00 2001 From: Philip Abbey Date: Mon, 8 Jan 2024 00:08:12 +0000 Subject: [PATCH] Initial solution --- resources/settings/properties.xml | 6 ++ resources/settings/settings.xml | 14 +++ resources/strings/strings.xml | 3 + source/HomeAssistantApp.mc | 122 ++++++++++++++++---------- source/HomeAssistantToggleMenuItem.mc | 2 + source/HomeAssistantView.mc | 4 +- source/RezStrings.mc | 8 ++ source/Settings.mc | 19 +++- 8 files changed, 128 insertions(+), 50 deletions(-) diff --git a/resources/settings/properties.xml b/resources/settings/properties.xml index 2f928a2..7d88dff 100644 --- a/resources/settings/properties.xml +++ b/resources/settings/properties.xml @@ -24,6 +24,12 @@ + + false + + + false + @@ -43,6 +44,8 @@ Long-Lived Access Token. URL for HomeAssistant API. URL for menu configuration (JSON). + Should the application cache the menu configuration? + Should the application clear the existing cache next time it is started? Timeout in seconds. Exit the application after this period of inactivity to save the device battery. After this time (in seconds), a confirmation dialog for an action is automatically closed and the action is cancelled. Set to 0 to disable the timeout. Menu item style. diff --git a/source/HomeAssistantApp.mc b/source/HomeAssistantApp.mc index 23a5385..0d4b7b6 100644 --- a/source/HomeAssistantApp.mc +++ b/source/HomeAssistantApp.mc @@ -124,12 +124,16 @@ class HomeAssistantApp extends Application.AppBase { } return ErrorView.create(RezStrings.getNoInternet() + "."); } else { - fetchMenuConfig(); + var isCached = fetchMenuConfig(); fetchApiStatus(); if (WidgetApp.isWidget) { return [new RootView(self), new RootViewDelegate(self)] as Lang.Array; } else { - return [new WatchUi.View(), new WatchUi.BehaviorDelegate()] as Lang.Array; + if (isCached) { + return [mHaMenu, new HomeAssistantViewDelegate(true)] as Lang.Array; + } else { + return [new WatchUi.View(), new WatchUi.BehaviorDelegate()] as Lang.Array; + } } } } @@ -193,21 +197,11 @@ class HomeAssistantApp extends Application.AppBase { case 200: mMenuStatus = RezStrings.getAvailable(); + if (Settings.getCacheConfig()) { + Storage.setValue("menu", data as Lang.Dictionary); + } if (!mIsGlance) { - mHaMenu = new HomeAssistantView(data, null); - mQuitTimer.begin(); - if (Settings.getIsWidgetStartNoTap()) { - // As soon as the menu has been fetched start show the menu of items. - // This behaviour is inconsistent with the standard Garmin User Interface, but has been - // requested by users so has been made the non-default option. - pushHomeAssistantMenuView(); - } - mItemsToUpdate = mHaMenu.getItemsToUpdate(); - // Start the continuous update process that continues for as long as the application is running. - // The chain of functions from 'updateNextMenuItem()' calls 'updateNextMenuItem()' on completion. - if (mItemsToUpdate.size() > 0) { - updateNextMenuItem(); - } + buildMenu(data); if (!WidgetApp.isWidget) { WatchUi.switchToView(mHaMenu, new HomeAssistantViewDelegate(false), WatchUi.SLIDE_IMMEDIATE); } @@ -226,43 +220,77 @@ class HomeAssistantApp extends Application.AppBase { WatchUi.requestUpdate(); } + // Return true if the menu came from the cache, otherwise false. This is because fetching the menu when not in the cache is + // asynchronous and affects how the views are managed. (:glance) - function fetchMenuConfig() as Void { + function fetchMenuConfig() as Lang.Boolean { if (Settings.getConfigUrl().equals("")) { mMenuStatus = RezStrings.getUnconfigured(); WatchUi.requestUpdate(); } else { - if (! System.getDeviceSettings().phoneConnected) { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call."); - } - if (mIsGlance) { - WatchUi.requestUpdate(); - } else { - ErrorView.show(RezStrings.getNoPhone() + "."); - } - mMenuStatus = RezStrings.getUnavailable(); - } else if (! System.getDeviceSettings().connectionAvailable) { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call."); - } - if (mIsGlance) { - WatchUi.requestUpdate(); - } else { - ErrorView.show(RezStrings.getNoInternet() + "."); - } - mMenuStatus = RezStrings.getUnavailable(); - } else { - Communications.makeWebRequest( - Settings.getConfigUrl(), - null, - { - :method => Communications.HTTP_REQUEST_METHOD_GET, - :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON - }, - method(:onReturnFetchMenuConfig) - ); + var menu = Storage.getValue("menu") as Lang.Dictionary; + if (menu != null and Settings.getClearCache()) { + Storage.deleteValue("menu"); + menu = null; + Settings.unsetClearCache(); } + if (menu == null) { + if (! System.getDeviceSettings().phoneConnected) { + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call."); + } + if (mIsGlance) { + WatchUi.requestUpdate(); + } else { + ErrorView.show(RezStrings.getNoPhone() + "."); + } + mMenuStatus = RezStrings.getUnavailable(); + } else if (! System.getDeviceSettings().connectionAvailable) { + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call."); + } + if (mIsGlance) { + WatchUi.requestUpdate(); + } else { + ErrorView.show(RezStrings.getNoInternet() + "."); + } + mMenuStatus = RezStrings.getUnavailable(); + } else { + Communications.makeWebRequest( + Settings.getConfigUrl(), + null, + { + :method => Communications.HTTP_REQUEST_METHOD_GET, + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON + }, + method(:onReturnFetchMenuConfig) + ); + } + } else { + mMenuStatus = RezStrings.getCached(); + if (!mIsGlance) { + buildMenu(menu); + } + return true; + } + } + return false; + } + + private function buildMenu(menu as Lang.Dictionary) { + mHaMenu = new HomeAssistantView(menu, null); + mQuitTimer.begin(); + if (Settings.getIsWidgetStartNoTap()) { + // As soon as the menu has been fetched start show the menu of items. + // This behaviour is inconsistent with the standard Garmin User Interface, but has been + // requested by users so has been made the non-default option. + pushHomeAssistantMenuView(); + } + mItemsToUpdate = mHaMenu.getItemsToUpdate(); + // Start the continuous update process that continues for as long as the application is running. + // The chain of functions from 'updateNextMenuItem()' calls 'updateNextMenuItem()' on completion. + if (mItemsToUpdate.size() > 0) { + updateNextMenuItem(); } } diff --git a/source/HomeAssistantToggleMenuItem.mc b/source/HomeAssistantToggleMenuItem.mc index 3e9c068..1965562 100644 --- a/source/HomeAssistantToggleMenuItem.mc +++ b/source/HomeAssistantToggleMenuItem.mc @@ -102,6 +102,8 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { var myTimer = new Timer.Timer(); // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false); + // Revert status + status = getApp().getApiStatus(); break; case 404: diff --git a/source/HomeAssistantView.mc b/source/HomeAssistantView.mc index 78afe1b..37a23f8 100644 --- a/source/HomeAssistantView.mc +++ b/source/HomeAssistantView.mc @@ -26,8 +26,8 @@ using Toybox.WatchUi; class HomeAssistantView extends WatchUi.Menu2 { // List of items that need to have their status updated periodically - private var mListToggleItems = []; - private var mListMenuItems = []; + private var mListToggleItems = []; + private var mListMenuItems = []; function initialize( definition as Lang.Dictionary, diff --git a/source/RezStrings.mc b/source/RezStrings.mc index 3243d42..57781fe 100644 --- a/source/RezStrings.mc +++ b/source/RezStrings.mc @@ -55,6 +55,8 @@ class RezStrings { (:glance) private static var strUnconfigured as Lang.String or Null; (:glance) + private static var strCached as Lang.String or Null; + (:glance) private static var strGlanceMenu as Lang.String or Null; private static var strLabelToggle as Lang.Dictionary or Null; @@ -71,6 +73,7 @@ class RezStrings { strChecking = WatchUi.loadResource($.Rez.Strings.Checking); strUnavailable = WatchUi.loadResource($.Rez.Strings.Unavailable); strUnconfigured = WatchUi.loadResource($.Rez.Strings.Unconfigured); + strCached = WatchUi.loadResource($.Rez.Strings.Cached); strGlanceMenu = WatchUi.loadResource($.Rez.Strings.GlanceMenu); } @@ -97,6 +100,7 @@ class RezStrings { strChecking = WatchUi.loadResource($.Rez.Strings.Checking); strUnavailable = WatchUi.loadResource($.Rez.Strings.Unavailable); strUnconfigured = WatchUi.loadResource($.Rez.Strings.Unconfigured); + strCached = WatchUi.loadResource($.Rez.Strings.Cached); strGlanceMenu = WatchUi.loadResource($.Rez.Strings.GlanceMenu); strLabelToggle = { :enabled => WatchUi.loadResource($.Rez.Strings.MenuItemOn) as Lang.String, @@ -184,6 +188,10 @@ class RezStrings { return strUnconfigured; } + static function getCached() as Lang.String { + return strCached; + } + static function getGlanceMenu() as Lang.String { return strGlanceMenu; } diff --git a/source/Settings.mc b/source/Settings.mc index 8faa54f..3820927 100644 --- a/source/Settings.mc +++ b/source/Settings.mc @@ -34,6 +34,8 @@ class Settings { private static var mApiKey as Lang.String = ""; private static var mApiUrl as Lang.String = ""; private static var mConfigUrl as Lang.String = ""; + private static var mCacheConfig as Lang.Boolean = false; + private static var mClearCache as Lang.Boolean = false; private static var mAppTimeout as Lang.Number = 0; // seconds private static var mConfirmTimeout as Lang.Number = 3; // seconds private static var mMenuStyle as Lang.Number = MENU_STYLE_ICONS; @@ -49,6 +51,8 @@ class Settings { mApiKey = Properties.getValue("api_key"); mApiUrl = Properties.getValue("api_url"); mConfigUrl = Properties.getValue("config_url"); + mCacheConfig = Properties.getValue("cache_config"); + mClearCache = Properties.getValue("clear_cache"); mAppTimeout = Properties.getValue("app_timeout"); mConfirmTimeout = Properties.getValue("confirm_timeout"); mMenuStyle = Properties.getValue("menu_theme"); @@ -99,7 +103,20 @@ class Settings { static function getConfigUrl() as Lang.String { return mConfigUrl; } - + + static function getCacheConfig() as Lang.Boolean { + return mCacheConfig; + } + + static function getClearCache() as Lang.Boolean { + return mClearCache; + } + + static function unsetClearCache() { + mClearCache = false; + Properties.setValue("clear_cache", mClearCache); + } + static function getAppTimeout() as Lang.Number { return mAppTimeout * 1000; // Convert to milliseconds }