diff --git a/BackgroundService.md b/BackgroundService.md index a2dbdbe..15fa8bf 100644 --- a/BackgroundService.md +++ b/BackgroundService.md @@ -1,4 +1,4 @@ -[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | Battery Reporting | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md) +[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md) # Background Service diff --git a/HISTORY.md b/HISTORY.md index 19907d0..7a0b4fc 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,4 @@ -[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | Version History +[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md) # Version History @@ -42,3 +42,4 @@ | 2.27 | Trivial bug fix for the glance view to prevent the "Unconfigured" result being erroneously displayed because the settings were not yet pulled from persistent storage. | | 2.28 | Added support for Vivoactive 6 device which also required an SDK update to 8.1.0. | | 2.29 | Added support for three new devices, Forerunners 570 42mm & 47mm and 970. | +| 2.30 | Extensive re-work of the Glance view, including the ability to customise it with a user supplied template. | diff --git a/README.md b/README.md index fd4b76d..59543d2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Home | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md) +[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md) # GarminHomeAssistant @@ -24,7 +24,7 @@ As of version 2.0, there are now two installable versions. For older devices bef | Version | Explanation | |------------------------|-------------| -| Application (original) | For newer devices that allow glance views in their applications, e.g. Venu 2, the GarminHomeAssistant application can be started either from a glance or from the list of applications and activities. Head over to the [GarminHomeAssistant](https://apps.garmin.com/en-US/apps/61c91d28-ec5e-438d-9f83-39e9f45b199d) application page on the [Connect IQ application store](https://apps.garmin.com/en-US/) to download the application. The application can be started two different ways, either from the glance in the carousel, or as an application from the list of applications & activities. With the latter, it is worth marking the application as a favourite.

If you place the application on your list of favourites, and rearrange it to appear near the top, then the item is just one button press away from the watch face. This second picture here shows the application menu on a Vivoactive 3 watch.

On newer watches, you can also start the application from the glance carousel. The glance view here typically displays some trackable status, so ours provides some early indication of availability. Older watches will still allow you to start this application from the list of applications and activities. | +| Application (original) | For newer devices that allow glance views in their applications, e.g. Venu 2, the GarminHomeAssistant application can be started either from a glance or from the list of applications and activities. Head over to the [GarminHomeAssistant](https://apps.garmin.com/en-US/apps/61c91d28-ec5e-438d-9f83-39e9f45b199d) application page on the [Connect IQ application store](https://apps.garmin.com/en-US/) to download the application. The application can be started two different ways, either from the glance in the carousel, or as an application from the list of applications & activities. With the latter, it is worth marking the application as a favourite.

If you place the application on your list of favourites, and rearrange it to appear near the top, then the item is just one button press away from the watch face. This second picture here shows the application menu on a Vivoactive 3 watch.

On newer watches, you can also start the application from the glance carousel. The glance view here typically displays some trackable status, so ours provides some early indication of availability. Older watches will still allow you to start this application from the list of applications and activities. | | Widget | **"Maintenance only mode"** so no new features will be added to this version.
For older devices that use widgets, e.g. Venu (1) as opposed to applications with "glances", the GarminHomeAssistant application can instead be started from the widget carousel. This is a separate item in the Connect IQ AppStore and with this installation, the application will no longer appear in the list of applications and activities. Head over to the [GarminHomeAssistant](https://apps.garmin.com/en-US/apps/) widget page on the [Connect IQ application store](https://apps.garmin.com/en-US/) to download the widget.

Typically the widget view implements something similar to the glance view, e.g. status, and exists in a widget carousel to allow you to select an application to launch.
**Please note that memory in widgets is more limited than applications. This means a large menu definition can crash the widget without the code catching the error.**
This version was born out of the application version and from Ver 2.0 shared the same source code repository until Ver 2.8 when they were [separated](https://github.com/house-of-abbey/GarminHomeAssistantWidget) to allow the application version to take advantage of its increase memory availability. | ### Features diff --git a/TroubleShooting.md b/TroubleShooting.md index 8dadb38..b61b839 100644 --- a/TroubleShooting.md +++ b/TroubleShooting.md @@ -1,4 +1,4 @@ -[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Background Service](BackgroundService.md) | Trouble Shooting | [Version History](HISTORY.md) +[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md) # Troubleshooting Guides diff --git a/config.schema.json b/config.schema.json index 92dbf8f..cf8088d 100644 --- a/config.schema.json +++ b/config.schema.json @@ -2,17 +2,22 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { + "$schema": { + "type": "string", + "description": "The schema will prevent JSON file errors." + }, "title": { - "type": "string" + "type": "string", + "description": "Top level menu title" + }, + "glance": { + "$ref": "#/$defs/glance" }, "items": { "$ref": "#/$defs/items" - }, - "$schema": { - "type": "string" } }, - "required": ["title", "items"], + "required": ["$schema", "title", "items"], "additionalProperties": false, "$defs": { "toggle": { @@ -29,8 +34,7 @@ "const": "toggle" }, "content": { - "$ref": "#/$defs/content", - "description": "Optional in a toggle." + "$ref": "#/$defs/content" }, "tap_action": { "type": "object", @@ -135,8 +139,7 @@ "const": "tap" }, "content": { - "$ref": "#/$defs/content", - "description": "Optional in a tap." + "$ref": "#/$defs/content" }, "service": { "$ref": "#/$defs/entity", @@ -174,8 +177,7 @@ "const": "group" }, "content": { - "$ref": "#/$defs/content", - "description": "Optional in a group." + "$ref": "#/$defs/content" }, "items": { "$ref": "#/$defs/items" @@ -190,7 +192,6 @@ }, "items": { "type": "array", - "maxItems": 16, "items": { "oneOf": [ { @@ -248,7 +249,8 @@ "required": ["service"] }, "content": { - "title": "Jinja2 template defining the text to display.", + "title": "Home Assistant Template", + "description": "Jinja2 template defining the text to display. Must be included in an 'info'. Optional in a 'toggle', 'tap' and 'group'. Special characters may not render in the glance context.", "type": "string" }, "confirm": { @@ -262,6 +264,35 @@ "default": false, "title": "PIN Confirmation", "description": "Optional PIN confirmation of the action before execution as a precaution. Has precedence over 'confirm': true if both are set." + }, + "glance": { + "type": "object", + "title": "Glance customisation", + "oneOf": [ + { + "properties": { + "type": { + "title": "Glance type", + "description": "One of 'info' or 'status'. 'info' renders the template specified in the 'content' field inside the glance view. 'status' reverts to the default glance view and ignores the 'content' field. This allows for disabling the template temporarily.", + "const": "info" + }, + "content": { + "$ref": "#/$defs/content" + } + }, + "required": ["type", "content"] + }, + { + "properties": { + "type": { + "title": "Glance type", + "description": "One of 'info' or 'status'.", + "const": "status" + } + }, + "required": ["type"] + } + ] } } } diff --git a/examples/Actions.md b/examples/Actions.md index b67e50b..cb2154c 100644 --- a/examples/Actions.md +++ b/examples/Actions.md @@ -1,4 +1,5 @@ -[Home](../README.md) | [Switches](Switches.md) | Actions | [Templates](Templates.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md) +[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md) + # Actions diff --git a/examples/Glance.md b/examples/Glance.md new file mode 100644 index 0000000..5594e8f --- /dev/null +++ b/examples/Glance.md @@ -0,0 +1,80 @@ +[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md) + +# Glance + +Since [version 2.30](../History.md), it is possible to ovverride the text displayed on the Glance view. This page explains how to customise the text. + + +## Default View + +The default view has always been to display the status of the menu and API availability to indicate if there's a problem. This view has now been updated to be more colourful. + + + +When either the API or the menu file is inaccessible, the fields will turn red. + + +## Customised View + +In order to customise the Glance view you need to add a `glance` field to the top level of the JSON menu file as illustrated here: + +```json +{ + "$schema": "https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json", + "title": "Home", + "glance": { + "type": "info", + "content": "Text: {% .. %}" + }, + "items": [...] +} +``` + +For example: + + + +```json +{ + "$schema": "https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json", + "glance": { + "type": "info", + "content": "Solar Battery: {{ states('sensor.battery_capacity_charge') }}%" + }, + : +} +``` + +You may make this as complicated as you like! But you have limited space and only ASCII text characters. + +The default view will persist showing until the errors are resolved. In order to extract the custom glance template both the menu and the API are required. So it is logical that the two tests must pass first. The exception here is if the menu is cached, in which case only the API needs to pass. + +> [!IMPORTANT] +> Sadly what you cannot do is use special characters like: 🌞🔋⛅đŸĒĢ. Whilst these do display in menu items, they do not seem to work on the Glance view. We really like them, so have tried but failed. Only ASCII text appears to be supported by the Garmin Connect IQ SDK's Glance View. This is not something we have any control over, please do not request this to be "fixed". + +It is possible to revert to the default glance content without deleting the template by changing the `type` to `status`. + +```json +{ + "$schema": "https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json", + "title": "Home", + "glance": { + "type": "status", + "content": "Text: {% .. %}" + }, + "items": [...] +} +``` + +So the glance view object has a `type` field with two possible values: `info` and `status`. When the type is `status` the `content` field is not required. + + +## Displayed Errors + +The following shows the default glance when the menu file is not available at the specified URL. + + + +Once the custom glance template has been retrieved and evaluated the display will change. Should the connectivity to your Home Assistant then be lost, e.g. you move out of range of your phone, the glance reflects this in the colour of the residual two rectangles. The top one remains an indicator for the API, and the bottom rectangle remains an indicator for the menu availability, reflecting the original placement in the default glance view that has now been replaced. + + diff --git a/examples/Switches.md b/examples/Switches.md index fa22fa5..9b200e1 100644 --- a/examples/Switches.md +++ b/examples/Switches.md @@ -1,4 +1,5 @@ -[Home](../README.md) | Switches | [Actions](Actions.md) | [Templates](Templates.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md) +[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md) + # Switches diff --git a/examples/Templates.md b/examples/Templates.md index 743c267..d09390b 100644 --- a/examples/Templates.md +++ b/examples/Templates.md @@ -1,4 +1,4 @@ -[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | Templates | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md) +[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md) # Templates diff --git a/images/Venu2_app_start.png b/images/Venu2_app_start.png index f82c6de..7fcbafd 100644 Binary files a/images/Venu2_app_start.png and b/images/Venu2_app_start.png differ diff --git a/images/Venu2_glance_custom.png b/images/Venu2_glance_custom.png new file mode 100644 index 0000000..e5bb73e Binary files /dev/null and b/images/Venu2_glance_custom.png differ diff --git a/images/Venu2_glance_default.png b/images/Venu2_glance_default.png new file mode 100644 index 0000000..e0bcf14 Binary files /dev/null and b/images/Venu2_glance_default.png differ diff --git a/images/Venu2_glance_no_bt.png b/images/Venu2_glance_no_bt.png new file mode 100644 index 0000000..512b910 Binary files /dev/null and b/images/Venu2_glance_no_bt.png differ diff --git a/images/Venu2_glance_no_menu.png b/images/Venu2_glance_no_menu.png new file mode 100644 index 0000000..b6e9326 Binary files /dev/null and b/images/Venu2_glance_no_menu.png differ diff --git a/images/Venu2_glance_start.png b/images/Venu2_glance_start.png deleted file mode 100644 index 57238da..0000000 Binary files a/images/Venu2_glance_start.png and /dev/null differ diff --git a/source/HomeAssistantApp.mc b/source/HomeAssistantApp.mc index a28209b..bda9906 100644 --- a/source/HomeAssistantApp.mc +++ b/source/HomeAssistantApp.mc @@ -24,18 +24,20 @@ using Toybox.Timer; // (:glance, :background) class HomeAssistantApp extends Application.AppBase { - private var mApiStatus as Lang.String or Null; - private var mMenuStatus as Lang.String or Null; - private var mHaMenu as HomeAssistantView or Null; - private var mQuitTimer as QuitTimer or Null; - private var mGlanceTimer as Timer.Timer or Null; - private var mUpdateTimer as Timer.Timer or Null; + private var mApiStatus as Lang.String or Null; + private var mMenuStatus as Lang.String or Null; + private var mHaMenu as HomeAssistantView or Null; + private var mGlanceTemplate as Lang.String or Null = null; + private var mGlanceText as Lang.String or Null = null; + private var mQuitTimer as QuitTimer or Null; + private var mGlanceTimer as Timer.Timer or Null; + private var mUpdateTimer as Timer.Timer or Null; // Array initialised by onReturnFetchMenuConfig() - private var mItemsToUpdate as Lang.Array or Null; - private var mIsGlance as Lang.Boolean = false; - private var mIsApp as Lang.Boolean = false; // Or Widget - private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates - private var mTemplates as Lang.Dictionary = {}; + private var mItemsToUpdate as Lang.Array or Null; + private var mIsGlance as Lang.Boolean = false; + private var mIsApp as Lang.Boolean = false; // Or Widget + private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates + private var mTemplates as Lang.Dictionary = {}; //! Class Constructor // @@ -203,7 +205,9 @@ class HomeAssistantApp extends Application.AppBase { mMenuStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String; } } - if (!mIsGlance) { + if (mIsGlance) { + glanceTemplate(data); + } else { if (data == null) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String); } else { @@ -272,7 +276,9 @@ class HomeAssistantApp extends Application.AppBase { } else { mMenuStatus = WatchUi.loadResource($.Rez.Strings.Cached) as Lang.String; WatchUi.requestUpdate(); - if (!mIsGlance) { + if (mIsGlance) { + glanceTemplate(menu); + } else { buildMenu(menu); } return true; @@ -303,6 +309,22 @@ class HomeAssistantApp extends Application.AppBase { } } + //! Extract the optional template to override the default glance view. + // + function glanceTemplate(menu as Lang.Dictionary) { + if (menu != null) { + if (menu.get("glance") != null) { + var glance = menu.get("glance") as Lang.Dictionary; + if (glance.get("type").equals("info")) { + mGlanceTemplate = glance.get("content") as Lang.String; + // System.println("HomeAssistantApp glanceTemplate() " + mGlanceTemplate); + } else { // if glance.get("type").equals("status") + mGlanceTemplate = null; + } + } + } + } + //! Callback function for each menu update GET request. //! //! @param responseCode Response code. @@ -416,10 +438,9 @@ class HomeAssistantApp extends Application.AppBase { } } // https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates - var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(); // System.println("HomeAssistantApp updateMenuItems() URL=" + url + ", Template='" + mTemplate + "'"); Communications.makeWebRequest( - url, + Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(), { "type" => "render_template", "data" => mTemplates @@ -548,6 +569,100 @@ class HomeAssistantApp extends Application.AppBase { } } + + //! Callback function after completing the GET request to render the glance template. + //! + //! @param responseCode Response code. + //! @param data Response data. + // + (:glance) + function onReturnFetchGlanceContent( + responseCode as Lang.Number, + data as Null or Lang.Dictionary or Lang.String + ) as Void { + // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: " + responseCode); + // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Data: " + data); + + switch (responseCode) { + case Communications.BLE_HOST_TIMEOUT: + case Communications.BLE_CONNECTION_UNAVAILABLE: + // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); + if (!mIsGlance) { + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); + } + break; + + case Communications.BLE_QUEUE_FULL: + // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + if (!mIsGlance) { + ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String); + } + break; + + case Communications.NETWORK_REQUEST_TIMED_OUT: + // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); + if (!mIsGlance) { + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String); + } + break; + + case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE: + // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned."); + if (!mIsGlance) { + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String); + } + break; + + case 404: + // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: 404, page not found. Check Configuration URL setting."); + if (!mIsGlance) { + ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String); + } + break; + + case 200: + if (data != null) { + mGlanceText = data.get("glanceTemplate"); + } + break; + + default: + // System.println("HomeAssistantApp onReturnFetchGlanceContent(): Unhandled HTTP response code = " + responseCode); + if (!mIsGlance) { + ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode); + } + } + WatchUi.requestUpdate(); + } + + //! Construct the GET request to convert the optional glance template to text for display. + // + (:glance) + function fetchGlanceContent() as Void { + if (mGlanceTemplate != null) { + // https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates + Communications.makeWebRequest( + Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(), + { + "type" => "render_template", + "data" => { + "glanceTemplate" => { + "template" => mGlanceTemplate + } + } + }, + { + :method => Communications.HTTP_REQUEST_METHOD_POST, + :headers => { + "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON + }, + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON + }, + method(:onReturnFetchGlanceContent) + ); + } + } + //! Record the API status result. //! //! @param s A string describing the API status @@ -574,6 +689,16 @@ class HomeAssistantApp extends Application.AppBase { return mMenuStatus; } + //! Return the optional glance text that overrides the default glance content. This + //! is derived from the glance template. + //! + //! @return A string derived from the glance template + // + (:glance) + function getGlanceText() as Lang.String or Null { + return mGlanceText; + } + //! Return the Menu construction status. //! //! @return A Boolean indicating if the menu is loaded into the application. @@ -623,12 +748,21 @@ class HomeAssistantApp extends Application.AppBase { return [new HomeAssistantGlanceView(self)]; } + //! Return the glance theme. + //! + //! @return The glance colour + // + function getGlanceTheme() as Application.AppBase.GlanceTheme { + return Application.AppBase.GLANCE_THEME_LIGHT_BLUE; + } + //! Update the menu and API statuses. Required for the Glance update timer. // function updateStatus() as Void { mGlanceTimer = null; fetchMenuConfig(); fetchApiStatus(); + fetchGlanceContent(); } //! Code for when the application settings are updated. diff --git a/source/HomeAssistantGlanceView.mc b/source/HomeAssistantGlanceView.mc index 1a29822..9f7cd6d 100644 --- a/source/HomeAssistantGlanceView.mc +++ b/source/HomeAssistantGlanceView.mc @@ -21,15 +21,34 @@ using Toybox.Graphics; // (:glance) class HomeAssistantGlanceView extends WatchUi.GlanceView { - private static const scLeftMargin = 5; // in pixels - private static const scMidSep = 10; // Middle Separator "text:_text" in pixels - private var mApp as HomeAssistantApp; - private var mTitle as WatchUi.Text or Null; - private var mApiText as WatchUi.Text or Null; - private var mApiStatus as WatchUi.Text or Null; - private var mMenuText as WatchUi.Text or Null; - private var mMenuStatus as WatchUi.Text or Null; - private var mAntiAlias as Lang.Boolean = false; + //! Margin left of the filled rectangle in pixels. + private static const scLeftRectMargin = 5; + //! Filled rectangle width in pixels. + private static const scRectWidth = 20; + //! Margin right of the filled rectangle in pixels. + private static const scRightRectMargin = 5; + //! Separator between the first column of text and the second in pixels. + //! i.e. Middle Separator "text:_text" + private static const scMidSep = 10; + //! Margin on the right side of the glance in pixels. + private static const scRightGlanceMargin = 15; + //! Margin top and bottom of the rectangles in pixels. + private static const scVertMargin = 5; + //! Size of the rounded rectangle corners in pixels. + private static const scRectRadius = 5; + + //! Dynamically scale the width of the first column of text based on the + //! language selection for the word "Menu". + private var mTextWidth as Lang.Number = 0; + // Re-usable text items for drawing + private var mApp as HomeAssistantApp; + private var mTitle as WatchUi.Text or Null; + private var mApiText as WatchUi.Text or Null; + private var mApiStatus as WatchUi.Text or Null; + private var mMenuText as WatchUi.Text or Null; + private var mMenuStatus as WatchUi.Text or Null; + private var mGlanceContent as WatchUi.TextArea or Null; + private var mAntiAlias as Lang.Boolean = false; //! Class Constructor // @@ -46,15 +65,16 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView { //! @param dc Device context // function onLayout(dc as Graphics.Dc) as Void { - var h = dc.getHeight(); - var tw = dc.getTextWidthInPixels(WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String, Graphics.FONT_XTINY); + var h = dc.getHeight(); + + mTextWidth = dc.getTextWidthInPixels(WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String + ":", Graphics.FONT_XTINY); mTitle = new WatchUi.Text({ :text => WatchUi.loadResource($.Rez.Strings.AppName) as Lang.String, :color => Graphics.COLOR_WHITE, :font => Graphics.FONT_TINY, :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, - :locX => scLeftMargin, + :locX => scLeftRectMargin, :locY => 1 * h / 6 }); @@ -63,7 +83,7 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView { :color => Graphics.COLOR_WHITE, :font => Graphics.FONT_XTINY, :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, - :locX => scLeftMargin, + :locX => scLeftRectMargin + scRectWidth + scRightRectMargin, :locY => 3 * h / 6 }); mApiStatus = new WatchUi.Text({ @@ -71,15 +91,16 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView { :color => Graphics.COLOR_WHITE, :font => Graphics.FONT_XTINY, :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, - :locX => scLeftMargin + scMidSep + tw, + :locX => scLeftRectMargin + scRectWidth + scRightRectMargin + scMidSep + mTextWidth, :locY => 3 * h / 6 }); + mMenuText = new WatchUi.Text({ :text => WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String + ":", :color => Graphics.COLOR_WHITE, :font => Graphics.FONT_XTINY, :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, - :locX => scLeftMargin, + :locX => scLeftRectMargin + scRectWidth + scRightRectMargin, :locY => 5 * h / 6 }); mMenuStatus = new WatchUi.Text({ @@ -87,9 +108,20 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView { :color => Graphics.COLOR_WHITE, :font => Graphics.FONT_XTINY, :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, - :locX => scLeftMargin + scMidSep + tw, + :locX => scLeftRectMargin + scRectWidth + scRightRectMargin + scMidSep + mTextWidth, :locY => 5 * h / 6 }); + + mGlanceContent = new WatchUi.TextArea({ + :text => "A longer piece of text to wrap.", + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_XTINY, + :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => scLeftRectMargin + scRectWidth + scRightRectMargin + scMidSep, + :locY => (2 * h / 6) + scVertMargin, + :width => dc.getWidth() - scLeftRectMargin - scRightGlanceMargin, + :height => (4 * h / 6) - (2 * scVertMargin) + }); } //! Update the view with the latest status text. @@ -97,8 +129,17 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView { //! @param dc Device context // function onUpdate(dc as Graphics.Dc) as Void { + var h = dc.getHeight(); + var w = dc.getWidth() - scLeftRectMargin - scRightGlanceMargin; + var apiStatus = mApp.getApiStatus(); + var menuStatus = mApp.getMenuStatus(); + var glanceText = mApp.getGlanceText(); + var apiCol; + var menuCol; + // System.println("HomeAssistantGlanceView onUpdate() glanceText=" + glanceText); + GlanceView.onUpdate(dc); - if(mAntiAlias) { + if (mAntiAlias) { dc.setAntiAlias(true); } dc.setColor( @@ -106,12 +147,60 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView { Graphics.COLOR_TRANSPARENT ); dc.clear(); + mTitle.setColor(Graphics.COLOR_BLUE); mTitle.draw(dc); - mApiText.draw(dc); - mApiStatus.setText(mApp.getApiStatus()); - mApiStatus.draw(dc); - mMenuText.draw(dc); - mMenuStatus.setText(mApp.getMenuStatus()); - mMenuStatus.draw(dc); - } + + if (apiStatus.equals(WatchUi.loadResource($.Rez.Strings.Checking))) { + apiCol = Graphics.COLOR_YELLOW; + } else if (apiStatus.equals(WatchUi.loadResource($.Rez.Strings.Available))) { + apiCol = Graphics.COLOR_GREEN; + } else { + apiCol = Graphics.COLOR_RED; + } + + if (menuStatus.equals(WatchUi.loadResource($.Rez.Strings.Checking))) { + menuCol = Graphics.COLOR_YELLOW; + } else if (menuStatus.equals(WatchUi.loadResource($.Rez.Strings.Available))) { + menuCol = Graphics.COLOR_GREEN; + } else if (menuStatus.equals(WatchUi.loadResource($.Rez.Strings.Cached))) { + menuCol = Graphics.COLOR_GREEN; + } else { + menuCol = Graphics.COLOR_RED; + } + + if (glanceText == null) { + // Default Glance View + mApiText.draw(dc); + mApiStatus.setText(apiStatus); + mApiStatus.setColor(apiCol); + dc.setColor(apiCol, apiCol); + dc.drawRoundedRectangle(scLeftRectMargin, 2 * h / 6 + scVertMargin, w, 2 * h / 6 - (2 * scVertMargin), scRectRadius); + dc.fillRoundedRectangle(scLeftRectMargin, 2 * h / 6 + scVertMargin, scRectWidth, 2 * h / 6 - (2 * scVertMargin), scRectRadius); + mApiStatus.draw(dc); + + mMenuText.draw(dc); + mMenuStatus.setText(menuStatus); + mMenuStatus.setColor(menuCol); + dc.setColor(menuCol, menuCol); + dc.drawRoundedRectangle(scLeftRectMargin, 4 * h / 6 + scVertMargin, w, 2 * h / 6 - (2 * scVertMargin), scRectRadius); + dc.fillRoundedRectangle(scLeftRectMargin, 4 * h / 6 + scVertMargin, scRectWidth, 2 * h / 6 - (2 * scVertMargin), scRectRadius); + mMenuStatus.draw(dc); + } else { + // Customised Glance View + dc.setColor(Graphics.COLOR_BLUE, Graphics.COLOR_BLUE); + dc.drawRoundedRectangle( + scLeftRectMargin + scRectWidth + scRightRectMargin, + 2 * h / 6 + scVertMargin, + w - scRectWidth - scRightRectMargin, + 4 * h / 6 - (2 * scVertMargin), + scRectRadius + ); + dc.setColor(apiCol, apiCol); + dc.fillRoundedRectangle(scLeftRectMargin, 2 * h / 6 + scVertMargin, scRectWidth, 2 * h / 6 - (2 * scVertMargin), scRectRadius); + dc.setColor(menuCol, menuCol); + dc.fillRoundedRectangle(scLeftRectMargin, 4 * h / 6 + scVertMargin, scRectWidth, 2 * h / 6 - (2 * scVertMargin), scRectRadius); + mGlanceContent.setText(glanceText); + mGlanceContent.draw(dc); + } + } } diff --git a/source/WebLog.mc b/source/WebLog.mc index 53858f9..7af8bf1 100644 --- a/source/WebLog.mc +++ b/source/WebLog.mc @@ -167,7 +167,7 @@ class WebLog { // } } - // Callback function to print the outcome of a clear() method. Typically used for debugging this class. + //! Callback function to print the outcome of a clear() method. Typically used for debugging this class. //! //! @param responseCode Response code. //! @param data Response data.