Philip Abbey 76c2aaa249 Finishing off widget+app code changes.
Glance now updates the status.
Fix for quitting the application when persistently displaying an ErrorView.
Added option for Widget RootView to immediately start HomeAssistant without waiting for a tap as requested by a user.
2023-12-23 14:30:58 +00:00

151 lines
5.2 KiB
MonkeyC

//-----------------------------------------------------------------------------------
//
// 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.
//
// J D Abbey & P A Abbey, 28 December 2022
//
//
// Description:
//
// ErrorView provides a means to present application errors to the user. These
// 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<WatchUi.Views or WatchUi.InputDelegates>
//
//-----------------------------------------------------------------------------------
using Toybox.Graphics;
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Communications;
using Toybox.Timer;
class ErrorView extends ScalableView {
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 as Lang.Number;
private var mErrorIcon;
private var mTextArea;
private static var instance;
private static var mShown as Lang.Boolean = false;
function initialize() {
ScalableView.initialize();
mDelegate = new ErrorDelegate(self);
// Convert the settings from % of screen size to pixels
mErrorIconMargin = pixelsForScreen(cSettings.get(:errorIconMargin) as Lang.Float);
mErrorIcon = Application.loadResource(Rez.Drawables.ErrorIcon) as Graphics.BitmapResource;
}
// Load your resources here
function onLayout(dc as Graphics.Dc) as Void {
var w = dc.getWidth();
mTextArea = new WatchUi.TextArea({
:text => mText,
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER,
:locX => 0,
:locY => pixelsForScreen(20.0),
:width => w,
:height => pixelsForScreen(60.0)
});
}
// Update the view
function onUpdate(dc as Graphics.Dc) as Void {
var w = dc.getWidth();
if(dc has :setAntiAlias) {
dc.setAntiAlias(true);
}
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLUE);
dc.clear();
dc.drawBitmap(w/2 - mErrorIcon.getWidth()/2, mErrorIconMargin, mErrorIcon);
mTextArea.draw(dc);
}
function getDelegate() as ErrorDelegate {
return mDelegate;
}
static function create(text as Lang.String) as Lang.Array<ErrorView or ErrorDelegate> {
if (instance == null) {
instance = new ErrorView();
}
if (!mShown) {
instance.setText(text);
mShown = true;
}
return [instance, instance.getDelegate()];
}
// Create or reuse an existing ErrorView, and pass on the text.
static function show(text as Lang.String) as Void {
if (!mShown) {
create(text); // Ignore returned values
WatchUi.pushView(instance, instance.getDelegate(), WatchUi.SLIDE_UP);
// This must be last to avoid a race condition with unShow(), where the
// ErrorView can't be dismissed.
mShown = true;
}
}
static function unShow() as Void {
if (mShown) {
WatchUi.popView(WatchUi.SLIDE_DOWN);
// The call to 'updateNextMenuItem()' must be on another thread so that the view is popped above.
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.scApiResume, false);
// This must be last to avoid a race condition with show(), where the
// ErrorView can't be dismissed.
mShown = false;
}
}
// 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();
}
}
}
class ErrorDelegate extends WatchUi.BehaviorDelegate {
function initialize(view as ErrorView) {
WatchUi.BehaviorDelegate.initialize();
}
function onBack() as Lang.Boolean {
getApp().getQuitTimer().reset();
ErrorView.unShow();
return true;
}
}