diff --git a/source/ErrorView.mc b/source/ErrorView.mc index 5dbadc0..ce42c36 100644 --- a/source/ErrorView.mc +++ b/source/ErrorView.mc @@ -18,6 +18,15 @@ // should not happen of course... but they do, so best make sure errors can be // reported. // +// Designed so that a single ErrorView is used for all errors and hence can ensure +// that only the first call to display is honoured until the view is dismissed. +// This compensates for older devices not being able to call WatchUi.getCurrentView() +// due to not supporting API level 3.4.0. +// +// Usage: +// 1) ErrorView.show("Error message"); +// 2) return ErrorView.create("Error message"); // as Lang.Array +// //----------------------------------------------------------------------------------- using Toybox.Graphics; @@ -26,18 +35,22 @@ using Toybox.WatchUi; using Toybox.Communications; class ErrorView extends ScalableView { - private const cSettings as Lang.Dictionary = { + private var mText as Lang.String = ""; + private var mDelegate as ErrorDelegate; + private const cSettings as Lang.Dictionary = { :errorIconMargin => 7f }; // Vertical spacing between the top of the face and the error icon - private var mErrorIconMargin; - private var mText as Lang.String; + private var mErrorIconMargin as Lang.Number; private var mErrorIcon; private var mTextArea; - function initialize(text as Lang.String) { + private static var instance; + private static var mShown as Lang.Boolean = false; + + function initialize() { ScalableView.initialize(); - mText = text; + mDelegate = new ErrorDelegate(self); // Convert the settings from % of screen size to pixels mErrorIconMargin = pixelsForScreen(cSettings.get(:errorIconMargin) as Lang.Float); } @@ -75,15 +88,59 @@ class ErrorView extends ScalableView { mTextArea.draw(dc); } + function getDelegate() as ErrorDelegate { + return mDelegate; + } + + static function create(text as Lang.String) as Lang.Array { + if (instance == null) { + instance = new ErrorView(); + } + if (!mShown) { + instance.setText(text); + } + return [instance, instance.getDelegate()]; + } + + // Create or reuse an existing ErrorView, and pass on the text. + static function show(text as Lang.String) as Void { + create(text); // Ignore returned values + if (!mShown) { + WatchUi.pushView(instance, instance.getDelegate(), WatchUi.SLIDE_UP); + mShown = true; + } + } + + // Internal show now we're not a static method like 'show()'. + function setText(text as Lang.String) as Void { + mText = text; + if (mTextArea != null) { + mTextArea.setText(text); + requestUpdate(); + } + } + + static function unShow() as Void { + if (mShown) { + mShown = false; + WatchUi.popView(WatchUi.SLIDE_DOWN); + } + } + } class ErrorDelegate extends WatchUi.BehaviorDelegate { - function initialize() { + //private var mView as ErrorView; + + function initialize(view as ErrorView) { WatchUi.BehaviorDelegate.initialize(); + //mView = view; } + function onBack() { getApp().getQuitTimer().reset(); - WatchUi.popView(WatchUi.SLIDE_DOWN); + ErrorView.unShow(); return true; } + } \ No newline at end of file diff --git a/source/HomeAssistantApp.mc b/source/HomeAssistantApp.mc index dc1651f..fe862e8 100644 --- a/source/HomeAssistantApp.mc +++ b/source/HomeAssistantApp.mc @@ -74,32 +74,32 @@ class HomeAssistantApp extends Application.AppBase { if (Globals.scDebug) { System.println("HomeAssistantApp getInitialView(): No API key in the application settings."); } - return [new ErrorView(strNoApiKey + "."), new ErrorDelegate()] as Lang.Array; + return ErrorView.create(strNoApiKey + "."); } else if (api_url.length() == 0) { if (Globals.scDebug) { System.println("HomeAssistantApp getInitialView(): No API URL in the application settings."); } - return [new ErrorView(strNoApiUrl + "."), new ErrorDelegate()] as Lang.Array; + return ErrorView.create(strNoApiUrl + "."); } else if (api_url.substring(-1, api_url.length()).equals("/")) { if (Globals.scDebug) { System.println("HomeAssistantApp getInitialView(): API URL must not have a trailing slash '/'."); } - return [new ErrorView(strTrailingSlashErr + "."), new ErrorDelegate()] as Lang.Array; + return ErrorView.create(strTrailingSlashErr + "."); } else if ((Properties.getValue("config_url") as Lang.String).length() == 0) { if (Globals.scDebug) { System.println("HomeAssistantApp getInitialView(): No configuration URL in the application settings."); } - return [new ErrorView(strNoConfigUrl + "."), new ErrorDelegate()] as Lang.Array; + return ErrorView.create(strNoConfigUrl + "."); } else if (! System.getDeviceSettings().phoneConnected) { if (Globals.scDebug) { System.println("HomeAssistantApp fetchMenuConfig(): No Phone connection, skipping API call."); } - return [new ErrorView(strNoPhone + "."), new ErrorDelegate()] as Lang.Array; + return ErrorView.create(strNoPhone + "."); } else if (! System.getDeviceSettings().connectionAvailable) { if (Globals.scDebug) { System.println("HomeAssistantApp fetchMenuConfig(): No Internet connection, skipping API call."); } - return [new ErrorView(strNoInternet + "."), new ErrorDelegate()] as Lang.Array; + return ErrorView.create(strNoInternet + "."); } else { fetchMenuConfig(); return [new WatchUi.View(), new WatchUi.BehaviorDelegate()] as Lang.Array; @@ -117,25 +117,23 @@ class HomeAssistantApp extends Application.AppBase { if (Globals.scDebug) { System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); } - WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoPhone + "."); } else if (responseCode == Communications.BLE_QUEUE_FULL) { if (Globals.scDebug) { System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_QUEUE_FULL, API calls too rapid."); } - if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) { - // Avoid pushing multiple ErrorViews - WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP); - } + // Don't need to worry about multiple ErrorViews here as the fetch does not happen a second time. + ErrorView.show(strApiFlood); } else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) { if (Globals.scDebug) { System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); } - WatchUi.pushView(new ErrorView(strNoResponse), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoResponse); } else if (responseCode == 404) { if (Globals.scDebug) { System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: 404, page not found. Check Configuration URL setting."); } - WatchUi.pushView(new ErrorView(strConfigUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strConfigUrlNotFound); } else if (responseCode == 200) { mHaMenu = new HomeAssistantView(data, null); WatchUi.switchToView(mHaMenu, new HomeAssistantViewDelegate(), WatchUi.SLIDE_IMMEDIATE); @@ -149,12 +147,12 @@ class HomeAssistantApp extends Application.AppBase { if (Globals.scDebug) { System.println("HomeAssistantApp onReturnFetchMenuConfig(): Network request timeout."); } - WatchUi.pushView(new ErrorView(strNoMenu + ". " + strNoInternet + "?"), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoMenu + ". " + strNoInternet + "?"); } else { if (Globals.scDebug) { System.println("HomeAssistantApp onReturnFetchMenuConfig(): Unhandled HTTP response code = " + responseCode); } - WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strUnhandledHttpErr + responseCode ); } } diff --git a/source/HomeAssistantConfirmation.mc b/source/HomeAssistantConfirmation.mc index 24381a3..951fd1d 100644 --- a/source/HomeAssistantConfirmation.mc +++ b/source/HomeAssistantConfirmation.mc @@ -26,6 +26,7 @@ using Toybox.Timer; using Toybox.Application.Properties; class HomeAssistantConfirmation extends WatchUi.Confirmation { + function initialize() { WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm)); } @@ -33,32 +34,32 @@ class HomeAssistantConfirmation extends WatchUi.Confirmation { } class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate { - private var confirmMethod; - private var timeout; + private var mConfirmMethod; + private var mTimer; function initialize(callback as Method() as Void) { WatchUi.ConfirmationDelegate.initialize(); - confirmMethod = callback; + mConfirmMethod = callback; var timeoutSeconds = Properties.getValue("confirm_timeout") as Lang.Number; if (timeoutSeconds > 0) { - timeout = new Timer.Timer(); - timeout.start(method(:onTimeout), timeoutSeconds * 1000, true); + mTimer = new Timer.Timer(); + mTimer.start(method(:onTimeout), timeoutSeconds * 1000, true); } } function onResponse(response) as Lang.Boolean { getApp().getQuitTimer().reset(); - if (timeout) { - timeout.stop(); + if (mTimer) { + mTimer.stop(); } if (response == WatchUi.CONFIRM_YES) { - confirmMethod.invoke(); + mConfirmMethod.invoke(); } return true; } function onTimeout() as Void { - timeout.stop(); + mTimer.stop(); WatchUi.popView(WatchUi.SLIDE_RIGHT); } } diff --git a/source/HomeAssistantService.mc b/source/HomeAssistantService.mc index 2f371a9..6b2b905 100644 --- a/source/HomeAssistantService.mc +++ b/source/HomeAssistantService.mc @@ -54,25 +54,23 @@ class HomeAssistantService { if (Globals.scDebug) { System.println("HomeAssistantService onReturnCall() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); } - WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoPhone + "."); } else if (responseCode == Communications.BLE_QUEUE_FULL) { if (Globals.scDebug) { System.println("HomeAssistantService onReturnCall() Response Code: BLE_QUEUE_FULL, API calls too rapid."); } - if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) { - // Avoid pushing multiple ErrorViews - WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP); - } + // Don't need to worry about multiple ErrorViews here as the call is not on a repeat timer. + ErrorView.show(strApiFlood); } else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) { if (Globals.scDebug) { System.println("HomeAssistantService onReturnCall() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); } - WatchUi.pushView(new ErrorView(strNoResponse), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoResponse); } else if (responseCode == 404) { if (Globals.scDebug) { System.println("HomeAssistantService onReturnCall() Response Code: 404, page not found. Check API URL setting."); } - WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strApiUrlNotFound); } else if (responseCode == 200) { if (Globals.scDebug) { System.println("HomeAssistantService onReturnCall(): Service executed."); @@ -99,7 +97,7 @@ class HomeAssistantService { if (Globals.scDebug) { System.println("HomeAssistantService onReturnCall(): Unhandled HTTP response code = " + responseCode); } - WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strUnhandledHttpErr + responseCode ); } } @@ -117,14 +115,15 @@ class HomeAssistantService { if (Globals.scDebug) { System.println("HomeAssistantService call(): No Phone connection, skipping API call."); } - WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoPhone + "."); } else if (! System.getDeviceSettings().connectionAvailable) { if (Globals.scDebug) { System.println("HomeAssistantService call(): No Internet connection, skipping API call."); } - WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoInternet + "."); } else { - var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + service.substring(0, service.find(".")) + "/" + service.substring(service.find(".")+1, null); + // Can't user null for parameters due to API version level. + var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + service.substring(0, service.find(".")) + "/" + service.substring(service.find(".")+1, service.length()); if (Globals.scDebug) { System.println("HomeAssistantService call() URL=" + url); System.println("HomeAssistantService call() service=" + service); diff --git a/source/HomeAssistantToggleMenuItem.mc b/source/HomeAssistantToggleMenuItem.mc index 9bbbe89..a862663 100644 --- a/source/HomeAssistantToggleMenuItem.mc +++ b/source/HomeAssistantToggleMenuItem.mc @@ -77,56 +77,68 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { } // Provide the ability to terminate updating chain of calls for a permanent network error. var keepUpdating = true; - if (responseCode == Communications.BLE_HOST_TIMEOUT || responseCode == Communications.BLE_CONNECTION_UNAVAILABLE) { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - } - if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) { - // Avoid pushing multiple ErrorViews - WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); - } - } else if (responseCode == Communications.BLE_QUEUE_FULL) { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); - } - if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) { - // Avoid pushing multiple ErrorViews - WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP); - } - } else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); - } - if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) { - // Avoid pushing multiple ErrorViews - WatchUi.pushView(new ErrorView(strNoResponse), new ErrorDelegate(), WatchUi.SLIDE_UP); - } - } else if (responseCode == 404) { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting."); - } - if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) { - // Avoid pushing multiple ErrorViews - WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP); - } - keepUpdating = false; - } else if (responseCode == 200) { - var state = data.get("state") as Lang.String; - if (Globals.scDebug) { - System.println((data.get("attributes") as Lang.Dictionary).get("friendly_name") + " State=" + state); - } - if (getLabel().equals("...")) { - setLabel((data.get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String); - } - setUiToggle(state); - } else { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode); - } - if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) { - // Avoid pushing multiple ErrorViews - WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP); - } + switch (responseCode) { + case Communications.BLE_HOST_TIMEOUT: + case Communications.BLE_CONNECTION_UNAVAILABLE: + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); + } + ErrorView.show(strNoPhone + "."); + break; + case Communications.BLE_QUEUE_FULL: + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + } + ErrorView.show(strApiFlood); + break; + case Communications.NETWORK_REQUEST_TIMED_OUT: + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); + } + ErrorView.show(strNoResponse); + break; + case 404: + var msg = null; + if (data != null) { + msg = data.get("message"); + } + if (msg != null) { + // Should be an HTTP 405 according to curl queries + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404. " + mIdentifier + " " + msg); + } + ErrorView.show("HTTP 405, " + mIdentifier + ". " + data.get("message")); + } else { + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting."); + } + ErrorView.show(strApiUrlNotFound); + } + keepUpdating = false; + break; + case 405: + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 405. " + mIdentifier + " " + data.get("message")); + } + ErrorView.show("HTTP 405, " + mIdentifier + ". " + data.get("message")); + keepUpdating = false; + break; + case 200: + var state = data.get("state") as Lang.String; + if (Globals.scDebug) { + System.println((data.get("attributes") as Lang.Dictionary).get("friendly_name") + " State=" + state); + } + if (getLabel().equals("...")) { + setLabel((data.get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String); + } + setUiToggle(state); + ErrorView.unShow(); + break; + default: + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode); + } + ErrorView.show(strUnhandledHttpErr + responseCode); } if (keepUpdating) { // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. @@ -147,18 +159,12 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call."); } - if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) { - // Avoid pushing multiple ErrorViews - WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); - } + ErrorView.show(strNoPhone + "."); } else if (! System.getDeviceSettings().connectionAvailable) { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call."); } - if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) { - // Avoid pushing multiple ErrorViews - WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); - } + ErrorView.show(strNoInternet + "."); } else { var url = Properties.getValue("api_url") + "/states/" + mIdentifier; if (Globals.scDebug) { @@ -180,7 +186,9 @@ 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), 500, false); - System.println("HomeAssistantToggleMenuItem getState(): Updated failed " + mIdentifier); + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem getState(): Updated failed " + mIdentifier); + } } } @@ -195,22 +203,22 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); } - WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoPhone + "."); } else if (responseCode == Communications.BLE_QUEUE_FULL) { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); } - WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strApiFlood); } else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); } - WatchUi.pushView(new ErrorView(strNoResponse), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoResponse); } else if (responseCode == 404) { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: 404, page not found. Check API URL setting."); } - WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strApiUrlNotFound); } else if (responseCode == 200) { var state; var d = data as Lang.Array; @@ -227,7 +235,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnSetState(): Unhandled HTTP response code = " + responseCode); } - WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strUnhandledHttpErr + responseCode ); } } @@ -246,14 +254,14 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { } // Toggle the UI back setEnabled(!isEnabled()); - WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoPhone + "."); } else if (! System.getDeviceSettings().connectionAvailable) { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call."); } // Toggle the UI back setEnabled(!isEnabled()); - WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP); + ErrorView.show(strNoInternet + "."); } else { // Updated SDK and got a new error // ERROR: venu: Cannot find symbol ':substring' on type 'PolyType'.