From 876496953700f4c3b9733345539ea7c344331093 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Thu, 16 Nov 2023 19:51:10 +0100 Subject: [PATCH] Leaner UI spike --- resources/settings/properties.xml | 2 + resources/settings/settings.xml | 9 ++ source/HomeAssistantIconMenuItem.mc | 165 ++++++++++++++++++++++++ source/HomeAssistantView.mc | 96 +++++++++++--- source/HomeAssistantViewIconMenuItem.mc | 54 ++++++++ 5 files changed, 305 insertions(+), 21 deletions(-) create mode 100644 source/HomeAssistantIconMenuItem.mc create mode 100644 source/HomeAssistantViewIconMenuItem.mc diff --git a/resources/settings/properties.xml b/resources/settings/properties.xml index f2e3a27..0829069 100644 --- a/resources/settings/properties.xml +++ b/resources/settings/properties.xml @@ -23,4 +23,6 @@ + + diff --git a/resources/settings/settings.xml b/resources/settings/settings.xml index 1ea3ca6..1b0ef4a 100644 --- a/resources/settings/settings.xml +++ b/resources/settings/settings.xml @@ -42,4 +42,13 @@ type="alphaNumeric" /> + + + + diff --git a/source/HomeAssistantIconMenuItem.mc b/source/HomeAssistantIconMenuItem.mc new file mode 100644 index 0000000..853cd1b --- /dev/null +++ b/source/HomeAssistantIconMenuItem.mc @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------------- +// +// Distributed under MIT Licence +// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE. +// +//----------------------------------------------------------------------------------- +// +// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely +// tested on a Venu 2 device. The source code is provided at: +// https://github.com/house-of-abbey/GarminHomeAssistant. +// +// P A Abbey & J D Abbey, 31 October 2023 +// +// +// Description: +// +// Menu button that triggers a service. +// +//----------------------------------------------------------------------------------- + +using Toybox.Lang; +using Toybox.WatchUi; +using Toybox.Graphics; +using Toybox.Application.Properties; + +class HomeAssistantIconMenuItem extends WatchUi.IconMenuItem { + hidden var mApiKey as Lang.String; + hidden var strNoInternet as Lang.String; + hidden var strApiFlood as Lang.String; + hidden var strApiUrlNotFound as Lang.String; + hidden var strUnhandledHttpErr as Lang.String; + hidden var mService as Lang.String; + + function initialize( + label as Lang.String or Lang.Symbol, + subLabel as Lang.String or Lang.Symbol or Null, + identifier as Lang.Object or Null, + service as Lang.String or Null, + icon as Graphics.BitmapType or WatchUi.Drawable, + options as { + :alignment as WatchUi.MenuItem.Alignment + } or Null + ) { + strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet); + strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood); + strApiUrlNotFound = WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound); + strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr); + mApiKey = Properties.getValue("api_key"); + mService = service; + WatchUi.IconMenuItem.initialize( + label, + subLabel, + identifier, + icon, + options + ); + } + + // Callback function after completing the POST request to call a script. + // + function onReturnExecScript(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void { + if (Globals.scDebug) { + System.println("HomeAssistantIconMenuItem onReturnExecScript() Response Code: " + responseCode); + System.println("HomeAssistantIconMenuItem onReturnExecScript() Response Data: " + data); + } + if (responseCode == Communications.BLE_QUEUE_FULL) { + if (Globals.scDebug) { + System.println("HomeAssistantIconMenuItem onReturnExecScript() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + } + var cw = WatchUi.getCurrentView(); + if (!(cw[0] instanceof ErrorView)) { + // Avoid pushing multiple ErrorViews + WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP); + } + } else if (responseCode == 404) { + if (Globals.scDebug) { + System.println("HomeAssistantIconMenuItem onReturnExecScript() Response Code: 404, page not found. Check API URL setting."); + } + WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP); + } else if (responseCode == 200) { + if (Globals.scDebug) { + System.println("HomeAssistantIconMenuItem onReturnExecScript(): Service executed."); + } + var d = data as Lang.Array; + var toast = "Executed"; + for(var i = 0; i < d.size(); i++) { + if ((d[i].get("entity_id") as Lang.String).equals(mIdentifier)) { + toast = (d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String; + } + } + if (WatchUi has :showToast) { + WatchUi.showToast(toast, null); + } else { + new Alert({ + :timeout => Globals.scAlertTimeout, + :font => Graphics.FONT_MEDIUM, + :text => toast, + :fgcolor => Graphics.COLOR_WHITE, + :bgcolor => Graphics.COLOR_BLACK + }).pushView(WatchUi.SLIDE_IMMEDIATE); + } + } else { + if (Globals.scDebug) { + System.println("HomeAssistantIconMenuItem onReturnExecScript(): Unhandled HTTP response code = " + responseCode); + } + WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP); + } + } + + function execScript() as Void { + var options = { + :method => Communications.HTTP_REQUEST_METHOD_POST, + :headers => { + "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON, + "Authorization" => "Bearer " + mApiKey + }, + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON + }; + if (System.getDeviceSettings().phoneConnected && System.getDeviceSettings().connectionAvailable) { + // Updated SDK and got a new error + // ERROR: venu: Cannot find symbol ':substring' on type 'PolyType'. + var id = mIdentifier as Lang.String; + if (mService == null) { + var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + id.substring(0, id.find(".")) + "/" + id.substring(id.find(".")+1, null); + if (Globals.scDebug) { + System.println("HomeAssistantIconMenuItem execScript() URL=" + url); + System.println("HomeAssistantIconMenuItem execScript() mIdentifier=" + mIdentifier); + } + Communications.makeWebRequest( + url, + null, + options, + method(:onReturnExecScript) + ); + } else { + var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + mService.substring(0, mService.find(".")) + "/" + mService.substring(mService.find(".")+1, null); + if (Globals.scDebug) { + System.println("HomeAssistantIconMenuItem execScript() URL=" + url); + System.println("HomeAssistantIconMenuItem execScript() mService=" + mService); + } + Communications.makeWebRequest( + url, + { + "entity_id" => id + }, + options, + method(:onReturnExecScript) + ); + } + if (Attention has :vibrate) { + Attention.vibrate([ + new Attention.VibeProfile(50, 100), // On for 100ms + new Attention.VibeProfile( 0, 100), // Off for 100ms + new Attention.VibeProfile(50, 100) // On for 100ms + ]); + } + } else { + if (Globals.scDebug) { + System.println("HomeAssistantIconMenuItem execScript(): No Internet connection, skipping API call."); + } + WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); + } + } + +} diff --git a/source/HomeAssistantView.mc b/source/HomeAssistantView.mc index fe74847..4c26d4b 100644 --- a/source/HomeAssistantView.mc +++ b/source/HomeAssistantView.mc @@ -18,15 +18,17 @@ // //----------------------------------------------------------------------------------- +using Toybox.Application; using Toybox.Lang; using Toybox.Graphics; using Toybox.WatchUi; class HomeAssistantView extends WatchUi.Menu2 { - hidden var strMenuItemTap as Lang.String; + // List of items that need to have their status updated periodically - hidden var mListToggleItems = []; - hidden var mListMenuItems = []; + hidden var mListToggleItems = []; + hidden var mListMenuItems = []; + hidden var mListIconMenuItems = []; function initialize( definition as Lang.Dictionary, @@ -36,11 +38,17 @@ class HomeAssistantView extends WatchUi.Menu2 { :theme as WatchUi.MenuTheme or Null } or Null ) { - strMenuItemTap = WatchUi.loadResource($.Rez.Strings.MenuItemTap); - var toggle_obj = { - :enabled => WatchUi.loadResource($.Rez.Strings.MenuItemOn) as Lang.String, - :disabled => WatchUi.loadResource($.Rez.Strings.MenuItemOff) as Lang.String - }; + + var toggle_obj = null; + var strMenuItemTap = null; + + if ((Application.Properties.getValue("lean_ui") as Lang.Boolean) == false){ + toggle_obj = { + :enabled => WatchUi.loadResource($.Rez.Strings.MenuItemOn) as Lang.String, + :disabled => WatchUi.loadResource($.Rez.Strings.MenuItemOff) as Lang.String + }; + strMenuItemTap = WatchUi.loadResource($.Rez.Strings.MenuItemTap); + } if (options == null) { options = { @@ -69,19 +77,45 @@ class HomeAssistantView extends WatchUi.Menu2 { addItem(item); mListToggleItems.add(item); } else if (type.equals("tap") && service != null) { - addItem( - new HomeAssistantMenuItem( - name, - strMenuItemTap, - entity, - service, - null - ) - ); + if ((Application.Properties.getValue("lean_ui") as Lang.Boolean) == true){ + + var icon = new WatchUi.Bitmap({ + :rezId=>Rez.Drawables.ErrorIcon + }); + + var alignement = {:alignment => WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_RIGHT}; + + addItem( + new HomeAssistantIconMenuItem( + name, + strMenuItemTap, + entity, + service, + icon, + alignement + ) + ); + } else { + addItem( + new HomeAssistantMenuItem( + name, + strMenuItemTap, + entity, + service, + null + ) + ); + } } else if (type.equals("group")) { - var item = new HomeAssistantViewMenuItem(items[i]); - addItem(item); - mListMenuItems.add(item); + if ((Application.Properties.getValue("lean_ui") as Lang.Boolean) == true){ + var item = new HomeAssistantViewIconMenuItem(items[i]); + addItem(item); + mListIconMenuItems.add(item); + } else { + var item = new HomeAssistantViewMenuItem(items[i]); + addItem(item); + mListMenuItems.add(item); + } } } } @@ -89,10 +123,17 @@ class HomeAssistantView extends WatchUi.Menu2 { function getItemsToUpdate() as Lang.Array { var fullList = []; + var lmi = mListMenuItems as Lang.Array; for(var i = 0; i < lmi.size(); i++) { fullList.addAll(lmi[i].getMenuView().getItemsToUpdate()); } + + var limi = mListMenuItems as Lang.Array; + for(var i = 0; i < limi.size(); i++) { + fullList.addAll(limi[i].getMenuView().getItemsToUpdate()); + } + return fullList.addAll(mListToggleItems); } @@ -125,6 +166,12 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate { System.println(haItem.getLabel() + " " + haItem.getId()); } haItem.execScript(); + } else if (item instanceof HomeAssistantIconMenuItem) { + var haItem = item as HomeAssistantIconMenuItem; + if (Globals.scDebug) { + System.println(haItem.getLabel() + " " + haItem.getId()); + } + haItem.execScript(); } else if (item instanceof HomeAssistantViewMenuItem) { var haMenuItem = item as HomeAssistantViewMenuItem; if (Globals.scDebug) { @@ -132,6 +179,13 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate { } // No delegate state to be amended, so re-use 'self'. WatchUi.pushView(haMenuItem.getMenuView(), self, WatchUi.SLIDE_LEFT); + } else if (item instanceof HomeAssistantViewIconMenuItem) { + var haMenuItem = item as HomeAssistantViewIconMenuItem; + if (Globals.scDebug) { + System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId()); + } + // No delegate state to be amended, so re-use 'self'. + WatchUi.pushView(haMenuItem.getMenuView(), self, WatchUi.SLIDE_LEFT); } else { if (Globals.scDebug) { System.println(item.getLabel() + " " + item.getId()); @@ -143,4 +197,4 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate { WatchUi.popView(WatchUi.SLIDE_RIGHT); } -} \ No newline at end of file +} diff --git a/source/HomeAssistantViewIconMenuItem.mc b/source/HomeAssistantViewIconMenuItem.mc new file mode 100644 index 0000000..f2156b3 --- /dev/null +++ b/source/HomeAssistantViewIconMenuItem.mc @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------------- +// +// Distributed under MIT Licence +// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE. +// +//----------------------------------------------------------------------------------- +// +// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely +// tested on a Venu 2 device. The source code is provided at: +// https://github.com/house-of-abbey/GarminHomeAssistant. +// +// P A Abbey & J D Abbey, 31 October 2023 +// +// +// Description: +// +// Menu button that opens a sub-menu. +// +//----------------------------------------------------------------------------------- + +using Toybox.Lang; +using Toybox.WatchUi; + +class HomeAssistantViewIconMenuItem extends WatchUi.IconMenuItem { + hidden var mMenu as HomeAssistantView; + + function initialize(definition as Lang.Dictionary) { + var label = definition.get("name") as Lang.String; + var identifier = definition.get("entity") as Lang.String; + + var icon = new WatchUi.Bitmap({ + :rezId=>Rez.Drawables.LauncherIcon, + :locX=>WatchUi.LAYOUT_HALIGN_CENTER, + :locY=>WatchUi.LAYOUT_VALIGN_CENTER + }); + + var alignement = {:alignment => WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_RIGHT}; + + WatchUi.IconMenuItem.initialize( + label, + null, + identifier, + icon, + alignement + ); + + mMenu = new HomeAssistantView(definition, null); + } + + function getMenuView() as HomeAssistantView { + return mMenu; + } + +}