Suggested code changes from philipabbey

1. attribute is option, so needs a different template in the API call when absent.
2. Automatically derive the format string from the picker step value for any precision of step.
3. Changed all Lang.String representations of numbers to Lang.Number or Lang.Float. I'm keen to remove the use of strings to hold a numeric value.
4. Tidied up and completed some code comments.
5. Adjusted the JSON schema definition. This is still not finished as the 'picker' object is required for 'numeric' menu items and must not be present for the others. Additional schema changes are required for greater precision.
6. Moved fields over from 'data' to 'picker'.
This commit is contained in:
Philip Abbey
2025-10-29 14:26:02 +00:00
parent 2cd171637c
commit a5ddb65512
8 changed files with 223 additions and 207 deletions

View File

@@ -283,50 +283,14 @@
"exit": { "exit": {
"$ref": "#/$defs/exit" "$ref": "#/$defs/exit"
}, },
"min": {
"type": "number",
"title": "Minimum Value"
},
"max": {
"type": "number",
"title": "Maximum Value"
},
"step": {
"type": "number",
"title": "Step Size"
},
"display_format": {
"type": "string",
"title": "Display Format",
"description": "A C-Style format string for displaying the value in the UI. https://developer.garmin.com/connect-iq/api-docs/Toybox/Lang/Number.html#format-instance_function",
"default": "%.1f"
},
"entity": { "entity": {
"$ref": "#/$defs/entity" "$ref": "#/$defs/entity"
},
"attribute": {
"type": "string",
"title": "Attribute on the entity",
"description": "Attribute on the entity with the current numeric value. To use the state of the entity, do not specify."
},
"service": {
"$ref": "#/$defs/service"
},
"data_attribute": {
"type": "string",
"title": "Attribute on the service data",
"description": "Attribute on the service data for the value to set."
} }
}, },
"required": [ "required": [
"name", "name",
"type", "type",
"min", "entity"
"max",
"step",
"entity",
"service",
"data_attribute"
], ],
"additionalProperties": false "additionalProperties": false
}, },
@@ -816,6 +780,9 @@
"title": "Action", "title": "Action",
"description": "'confirm' field is optional.", "description": "'confirm' field is optional.",
"properties": { "properties": {
"picker": {
"$ref": "#/$defs/picker"
},
"confirm": { "confirm": {
"$ref": "#/$defs/confirm" "$ref": "#/$defs/confirm"
}, },
@@ -824,6 +791,41 @@
} }
} }
}, },
"picker": {
"type": "object",
"title": "Number picker configuration",
"description": "'attribute' field is optional.",
"properties": {
"min": {
"type": "number",
"title": "Minimum Value"
},
"max": {
"type": "number",
"title": "Maximum Value"
},
"step": {
"type": "number",
"title": "Step Size"
},
"attribute": {
"type": "string",
"title": "Attribute on the entity",
"description": "Attribute on the entity with the current numeric value. To use the state of the entity, do not specify."
},
"data_attribute": {
"type": "string",
"title": "Attribute on the service data",
"description": "Attribute on the service data for the value to set."
}
},
"required": [
"min",
"max",
"step",
"data_attribute"
]
},
"content": { "content": {
"title": "Home Assistant Template", "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.", "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.",
@@ -888,4 +890,4 @@
"description": "Choose to exit the application after this item has been selected. Disabled (false) by default. N.B. Only actionable menu items can have this field added." "description": "Choose to exit the application after this item has been selected. Disabled (false) by default. N.B. Only actionable menu items can have this field added."
} }
} }
} }

View File

@@ -630,8 +630,10 @@ class HomeAssistantApp extends Application.AppBase {
(item as HomeAssistantToggleMenuItem).updateToggleState(data[i.toString() + "t"]); (item as HomeAssistantToggleMenuItem).updateToggleState(data[i.toString() + "t"]);
} }
if (item instanceof HomeAssistantNumericMenuItem) { if (item instanceof HomeAssistantNumericMenuItem) {
// (item as HomeAssistantNumericMenuItem).updateNumericState("22"); var s = data[i.toString() + "n"];
(item as HomeAssistantNumericMenuItem).updateNumericState(data[i.toString() + "n"].toString()); if ((s instanceof Lang.Number) or (s instanceof Lang.Float)) {
(item as HomeAssistantNumericMenuItem).setValue(s);
}
} }
} }
if (Settings.getMenuCheck() && Settings.getCacheConfig() && !mIsCacheChecked) { if (Settings.getMenuCheck() && Settings.getCacheConfig() && !mIsCacheChecked) {
@@ -831,7 +833,7 @@ class HomeAssistantApp extends Application.AppBase {
var phoneConnected = System.getDeviceSettings().phoneConnected; var phoneConnected = System.getDeviceSettings().phoneConnected;
var connectionAvailable = System.getDeviceSettings().connectionAvailable; var connectionAvailable = System.getDeviceSettings().connectionAvailable;
// System.println("API URL = " + Settings.getApiUrl()); // System.println("HomeAssistantApp fetchApiStatus(): API URL = " + Settings.getApiUrl());
if (Settings.getApiUrl().equals("")) { if (Settings.getApiUrl().equals("")) {
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String; mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
WatchUi.requestUpdate(); WatchUi.requestUpdate();

View File

@@ -158,7 +158,7 @@ class HomeAssistantMenuItemFactory {
entity_id as Lang.String?, entity_id as Lang.String?,
template as Lang.String?, template as Lang.String?,
service as Lang.String?, service as Lang.String?,
data as Lang.Dictionary?, picker as Lang.Dictionary,
options as { options as {
:exit as Lang.Boolean, :exit as Lang.Boolean,
:confirm as Lang.Boolean, :confirm as Lang.Boolean,
@@ -166,25 +166,21 @@ class HomeAssistantMenuItemFactory {
:icon as WatchUi.Bitmap :icon as WatchUi.Bitmap
} }
) as WatchUi.MenuItem { ) as WatchUi.MenuItem {
var data = null;
if (entity_id != null) { if (entity_id != null) {
if (data == null) { data = { "entity_id" => entity_id };
data = { "entity_id" => entity_id }; }
} else {
data.put("entity_id", entity_id);
}
}
var keys = mMenuItemOptions.keys(); var keys = mMenuItemOptions.keys();
for (var i = 0; i < keys.size(); i++) { for (var i = 0; i < keys.size(); i++) {
options.put(keys[i], mMenuItemOptions.get(keys[i])); options.put(keys[i], mMenuItemOptions.get(keys[i]));
} }
options.put(:icon, mTapTypeIcon); options.put(:icon, mTapTypeIcon);
return new HomeAssistantNumericMenuItem( return new HomeAssistantNumericMenuItem(
label, label,
template, template,
service, service,
data, data,
picker,
options, options,
mHomeAssistantService mHomeAssistantService
); );

View File

@@ -9,80 +9,92 @@
// tested on a Venu 2 device. The source code is provided at: // tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant. // https://github.com/house-of-abbey/GarminHomeAssistant.
// //
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 // P A Abbey & J D Abbey & @thmichel, 13 October 2025
// //
//------------------------------------------------------------ //------------------------------------------------------------
import Toybox.Graphics; using Toybox.Graphics;
import Toybox.Lang; using Toybox.Lang;
import Toybox.WatchUi; using Toybox.WatchUi;
//! Factory that controls which numbers can be picked //! Factory that controls which numbers can be picked
class HomeAssistantNumericFactory extends WatchUi.PickerFactory { class HomeAssistantNumericFactory extends WatchUi.PickerFactory {
// define default values in case not contained in data // define default values in case not contained in data
private var mStart as Lang.Float = 0.0; private var mStart as Lang.Float = 0.0;
private var mStop as Lang.Float = 100.0; private var mStop as Lang.Float = 100.0;
private var mStep as Lang.Float = 1.0; private var mStep as Lang.Float = 1.0;
private var mFormatString as Lang.String = "%.2f"; private var mFormatString as Lang.String = "%d";
//! Class Constructor //! Class Constructor
//! //
public function initialize(data as Lang.Dictionary) { public function initialize(picker as Lang.Dictionary) {
PickerFactory.initialize(); PickerFactory.initialize();
// Get values from data // Get values from data
var val = picker["min"];
var val = data.get("min");
if (val != null) { if (val != null) {
mStart = val.toString().toFloat(); mStart = val.toString().toFloat();
} }
val = data.get("max"); val = picker["max"];
if (val != null) { if (val != null) {
mStop = val.toString().toFloat(); mStop = val.toString().toFloat();
} }
val = data.get("step"); val = picker["step"];
if (val != null) { if (val != null) {
mStep = val.toString().toFloat(); mStep = val.toString().toFloat();
} }
val = data.get("display_format"); if (mStep > 0.0) {
if (val != null) { var s = mStep;
mFormatString = val.toString(); var dp = 0;
} while (s < 1.0) {
s *= 10;
} dp++;
// Assigned inside the loop and in each iteration to avoid clobbering the default '%d'.
//! Get the index of a number item mFormatString = "%." + dp.toString() + "f";
//! @param value The number to get the index of }
//! @return The index of the number } else {
public function getIndex(value as Float) as Number { // The JSON menu definition defined a step size of 0, revert to the default.
return ((value / mStep) - mStart).toNumber(); mStep = 1.0;
}
} }
//! Generate a Drawable instance for an item //! Generate a Drawable instance for an item
//!
//! @param index The item index //! @param index The item index
//! @param selected true if the current item is selected, false otherwise //! @param selected true if the current item is selected, false otherwise
//! @return Drawable for the item //! @return Drawable for the item
public function getDrawable(index as Number, selected as Boolean) as Drawable? { //
public function getDrawable(
index as Lang.Number,
selected as Lang.Boolean
) as WatchUi.Drawable? {
var value = getValue(index); var value = getValue(index);
var text = "No item"; var text = "No item";
if (value instanceof Lang.Float) { if (value instanceof Lang.Float) {
text = value.format(mFormatString); text = value.format(mFormatString);
} }
return new WatchUi.Text({:text=>text, :color=>Graphics.COLOR_WHITE, return new WatchUi.Text({
:locX=>WatchUi.LAYOUT_HALIGN_CENTER, :locY=>WatchUi.LAYOUT_VALIGN_CENTER}); :text => text,
:color => Graphics.COLOR_WHITE,
:locX => WatchUi.LAYOUT_HALIGN_CENTER,
:locY => WatchUi.LAYOUT_VALIGN_CENTER
});
} }
//! Get the value of the item at the given index //! Get the value of the item at the given index
//!
//! @param index Index of the item to get the value of //! @param index Index of the item to get the value of
//! @return Value of the item //! @return Value of the item
public function getValue(index as Number) as Object? { //
public function getValue(index as Lang.Number) as Lang.Object? {
return mStart + (index * mStep); return mStart + (index * mStep);
} }
//! Get the number of picker items //! Get the number of picker items
//!
//! @return Number of items //! @return Number of items
public function getSize() as Number { //
public function getSize() as Lang.Number {
return ((mStop - mStart) / mStep).toNumber() + 1; return ((mStop - mStart) / mStep).toNumber() + 1;
} }
} }

View File

@@ -9,7 +9,7 @@
// tested on a Venu 2 device. The source code is provided at: // tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant. // https://github.com/house-of-abbey/GarminHomeAssistant.
// //
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 // P A Abbey & J D Abbey & @thmichel, 13 October 2025
// //
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
@@ -17,7 +17,6 @@ using Toybox.Lang;
using Toybox.WatchUi; using Toybox.WatchUi;
using Toybox.Graphics; using Toybox.Graphics;
//! Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders //! Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders
//! a Home Assistant Template. //! a Home Assistant Template.
// //
@@ -28,9 +27,9 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
private var mExit as Lang.Boolean; private var mExit as Lang.Boolean;
private var mPin as Lang.Boolean; private var mPin as Lang.Boolean;
private var mData as Lang.Dictionary?; private var mData as Lang.Dictionary?;
private var mValue as Lang.String?; private var mPicker as Lang.Dictionary?;
private var mFormatString as Lang.String="%.1f"; private var mValue as Lang.Number or Lang.Float = 0;
private var mFormatString as Lang.String = "%d";
//! Class Constructor //! Class Constructor
//! //!
@@ -51,6 +50,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
template as Lang.String, template as Lang.String,
service as Lang.String?, service as Lang.String?,
data as Lang.Dictionary?, data as Lang.Dictionary?,
picker as Lang.Dictionary,
options as { options as {
:alignment as WatchUi.MenuItem.Alignment, :alignment as WatchUi.MenuItem.Alignment,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol, :icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol,
@@ -62,33 +62,37 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
) { ) {
mService = service; mService = service;
mData = data; mData = data;
mPicker = picker;
mExit = options[:exit]; mExit = options[:exit];
mConfirm = options[:confirm]; mConfirm = options[:confirm];
mPin = options[:pin]; mPin = options[:pin];
mLabel = label; mLabel = label;
mHomeAssistantService = haService; mHomeAssistantService = haService;
var val = data.get("display_format");
if (val != null) {
mFormatString = val.toString();
}
else {
mFormatString = "%.1f";
}
HomeAssistantMenuItem.initialize( HomeAssistantMenuItem.initialize(
label, label,
template, template,
{ {
:alignment => options[:alignment], :alignment => options[:alignment],
:icon => options[:icon] :icon => options[:icon]
} }
); );
if (picker != null) {
var s = picker["step"];
if (s != null) {
var step = s.toFloat() as Lang.Float;
var dp = 0;
while (step < 1.0) {
step *= 10;
dp++;
// Assigned inside the loop and in each iteration to avoid clobbering the default '%d'.
mFormatString = "%." + dp.toString() + "f";
}
}
}
} }
function callService() as Void { function callService() as Void {
var hasTouchScreen = System.getDeviceSettings().isTouchScreen; var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
if (mPin && hasTouchScreen) { if (mPin && hasTouchScreen) {
@@ -98,10 +102,10 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
WatchUi.pushView( WatchUi.pushView(
pinConfirmationView, pinConfirmationView,
new HomeAssistantPinConfirmationDelegate({ new HomeAssistantPinConfirmationDelegate({
:callback => method(:onConfirm), :callback => method(:onConfirm),
:pin => pin, :pin => pin,
:state => false, :state => false,
:view => pinConfirmationView, :view => pinConfirmationView,
}), }),
WatchUi.SLIDE_IMMEDIATE WatchUi.SLIDE_IMMEDIATE
); );
@@ -139,62 +143,58 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
} }
} }
//! Callback function after the menu items selection has been (optionally) confirmed. //! Callback function after the menu items selection has been (optionally) confirmed.
//! //!
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method. //! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
// //
function onConfirm(b as Lang.Boolean) as Void { function onConfirm(b as Lang.Boolean) as Void {
//mHomeAssistantService.call(mService, {"entity_id" => mData.get("entity_id").toString(),mData.get("valueLabel").toString() => mValue}, mExit); var dataAttribute = mPicker["data_attribute"];
var dataAttribute = mData.get("data_attribute");
if (dataAttribute == null) { if (dataAttribute == null) {
//return without call service if no data attribute is set to avoid crash //return without call service if no data attribute is set to avoid crash
WatchUi.popView(WatchUi.SLIDE_RIGHT); WatchUi.popView(WatchUi.SLIDE_RIGHT);
return; return;
} }
var entity_id = mData.get("entity_id"); var entity_id = mData["entity_id"];
if (entity_id == null) { if (entity_id == null) {
//return without call service if no entity_id is set to avoid crash //return without call service if no entity_id is set to avoid crash
WatchUi.popView(WatchUi.SLIDE_RIGHT); WatchUi.popView(WatchUi.SLIDE_RIGHT);
return; return;
} }
mHomeAssistantService.call(mService, {"entity_id" => entity_id.toString(),dataAttribute.toString() => mValue}, mExit); mHomeAssistantService.call(
mService,
{
"entity_id" => entity_id.toString(),
dataAttribute.toString() => mValue
},
mExit
);
WatchUi.popView(WatchUi.SLIDE_RIGHT); WatchUi.popView(WatchUi.SLIDE_RIGHT);
} }
//! Return a toggle menu item's state template. //! Return a numeric menu item's fetch state template.
//! //!
//! @return A string with the menu item's template definition (or null). //! @return A string with the menu item's template definition (or null).
// //
function getNumericTemplate() as Lang.String? { function getNumericTemplate() as Lang.String? {
var entity_id = mData.get("entity_id"); var entity_id = mData["entity_id"];
if (entity_id != null) { var attribute = (mPicker["attribute"] as Lang.String);
return "{{state_attr('" + entity_id.toString() + "','" + mData.get("attribute").toString() +"')}}"; if (entity_id == null) {
} return null;
return null;
}
function updateNumericState(data as Lang.String or Lang.Dictionary or Null) as Void {
if (data == null) {
mValue="0";
return;
} else if(data instanceof Lang.String) {
mValue=data;
} else { } else {
// Catch possible error if (attribute == null) {
mValue="0"; return "{{states('" + entity_id.toString() + "')}}";
} else {
return "{{state_attr('" + entity_id.toString() + "','" + attribute + "')}}";
}
} }
} }
//! Update the menu item's sub label to display the template rendered by Home Assistant. //! Update the menu item's sub label to display the template rendered by Home Assistant.
//! //!
//! @param data The rendered template (typically a string) to be placed in the sub label. This may //! @param data The rendered template (typically a string) to be placed in the sub label. This may
//! unusually be a number if the SDK interprets the JSON returned by Home Assistant as such. //! unusually be a number if the SDK interprets the JSON returned by Home Assistant as such.
// //
function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void { public function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
if (data == null) { if (data == null) {
setSubLabel($.Rez.Strings.Empty); setSubLabel($.Rez.Strings.Empty);
} else if(data instanceof Lang.Float) { } else if(data instanceof Lang.Float) {
@@ -203,31 +203,45 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
} else if(data instanceof Lang.Number) { } else if(data instanceof Lang.Number) {
var f = data.toFloat() as Lang.Float; var f = data.toFloat() as Lang.Float;
setSubLabel(f.format(mFormatString)); setSubLabel(f.format(mFormatString));
} else if (data instanceof Lang.String){ } else if (data instanceof Lang.String) {
// This should not happen // This should not happen
setSubLabel(data); setSubLabel(data);
} } else {
else {
// The template must return a Float on Numeric value, or the item cannot be formatted locally without error. // The template must return a Float on Numeric value, or the item cannot be formatted locally without error.
setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String);
} }
WatchUi.requestUpdate(); WatchUi.requestUpdate();
} }
//! Set the mValue value. //! Set the Picker's value. Needed to set new value via the Service call
//! //!
//! Needed to set new value via the Service call //! @param value New value to set.
// //
function setValue(value as Lang.String) as Void { public function setValue(value as Lang.Number or Lang.Float) as Void {
mValue = value; mValue = value;
} }
function getValue() as Lang.String { //! Get the Picker's value.
//!
//! Needed to set new value via the Service call
//
public function getValue() as Lang.Number or Lang.Float {
return mValue; return mValue;
} }
function getData() as Lang.Dictionary { //! Get the original 'data' field supplied by the JSON menu.
//!
//! @return Dictionary containing the 'data' field.
//
public function getData() as Lang.Dictionary {
return mData; return mData;
} }
// Get the original 'picker' field supplied by the JSON menu.
//!
//! @return Dictionary containing the 'picker' field.
//
public function getPicker() as Lang.Dictionary {
return mPicker;
}
} }

View File

@@ -9,7 +9,7 @@
// tested on a Venu 2 device. The source code is provided at: // tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant. // https://github.com/house-of-abbey/GarminHomeAssistant.
// //
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 // P A Abbey & J D Abbey & @thmichel, 13 October 2025
// //
//------------------------------------------------------------ //------------------------------------------------------------
@@ -20,88 +20,79 @@ using Toybox.System;
using Toybox.WatchUi; using Toybox.WatchUi;
//! Picker that allows the user to choose a float value //! Picker that allows the user to choose a float value
//
class HomeAssistantNumericPicker extends WatchUi.Picker { class HomeAssistantNumericPicker extends WatchUi.Picker {
private var mFactory as HomeAssistantNumericFactory;
private var mItem as HomeAssistantNumericMenuItem; private var mItem as HomeAssistantNumericMenuItem;
//! Constructor //! Constructor
public function initialize(factory as HomeAssistantNumericFactory, haItem as HomeAssistantNumericMenuItem) { //
public function initialize(
factory as HomeAssistantNumericFactory,
haItem as HomeAssistantNumericMenuItem
) {
mItem = haItem;
var picker = mItem.getPicker();
var min = (picker.get("min") as Lang.String).toFloat();
var step = (picker.get("step") as Lang.String).toFloat();
var val = haItem.getValue();
mFactory = factory; if (min == null) {
min = 0.0;
}
var pickerOptions = {:pattern=>[mFactory]}; if (step == null) {
mItem=haItem; step = 1.0;
var data = mItem.getData();
var min = 0.0;
var val = data.get("min");
if (val != null) {
min = val.toString().toFloat();
}
var step = 1.0;
val = data.get("step");
if (val != null) {
step = val.toString().toFloat();
}
val = haItem.getValue();
if (val != null) {
val = val.toString().toFloat();
} else {
// catch missing state to avoid crash
val = min;
} }
var index = ((val -min) / step).toNumber();
pickerOptions[:defaults] =[index];
var title = new WatchUi.Text({:text=>haItem.getLabel(), :locX=>WatchUi.LAYOUT_HALIGN_CENTER,
:locY=>WatchUi.LAYOUT_VALIGN_BOTTOM});
pickerOptions[:title] = title;
Picker.initialize(pickerOptions);
WatchUi.Picker.initialize({
:title => new WatchUi.Text({
:text => haItem.getLabel(),
:locX => WatchUi.LAYOUT_HALIGN_CENTER,
:locY => WatchUi.LAYOUT_VALIGN_BOTTOM
}),
:pattern => [factory],
:defaults => [((val - min) / step).toNumber()]
});
} }
//! Called when the user has completed picking.
//! Get whether the user is done picking //!
//! @param value Value user selected //! @param value Value user selected
//! @return true if user is done, false otherwise //! @return true if user is done, false otherwise
public function onConfirm(value as Lang.String) as Void { //
public function onConfirm(value as Lang.Number or Lang.Float) as Void {
mItem.setValue(value); mItem.setValue(value);
mItem.callService(); mItem.callService();
} }
} }
//! Responds to a numeric picker selection or cancellation //! Responds to a numeric picker selection or cancellation.
//
class HomeAssistantNumericPickerDelegate extends WatchUi.PickerDelegate { class HomeAssistantNumericPickerDelegate extends WatchUi.PickerDelegate {
private var mPicker as HomeAssistantNumericPicker; private var mPicker as HomeAssistantNumericPicker;
//! Constructor //! Constructor
//
public function initialize(picker as HomeAssistantNumericPicker) { public function initialize(picker as HomeAssistantNumericPicker) {
PickerDelegate.initialize(); PickerDelegate.initialize();
mPicker = picker; mPicker = picker;
} }
//! Handle a cancel event from the picker //! Handle a cancel event from the picker
//!
//! @return true if handled, false otherwise //! @return true if handled, false otherwise
//
public function onCancel() as Lang.Boolean { public function onCancel() as Lang.Boolean {
WatchUi.popView(WatchUi.SLIDE_RIGHT); WatchUi.popView(WatchUi.SLIDE_RIGHT);
return true; return true;
} }
//! Handle a confirm event from the picker //! Handle a confirm event from the picker
//!
//! @param values The values chosen in the picker //! @param values The values chosen in the picker
//! @return true if handled, false otherwise //! @return true if handled, false otherwise
//
public function onAccept(values as Lang.Array) as Lang.Boolean { public function onAccept(values as Lang.Array) as Lang.Boolean {
var chosenValue = values[0].toString(); mPicker.onConfirm(values[0]);
mPicker.onConfirm(chosenValue);
return true; return true;
} }
} }

View File

@@ -128,7 +128,7 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
//! //!
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method. //! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
// //
function onConfirm(b as Lang.Boolean) as Void { public function onConfirm(b as Lang.Boolean) as Void {
if (mService != null) { if (mService != null) {
mHomeAssistantService.call(mService, mData, mExit); mHomeAssistantService.call(mService, mData, mExit);
} }

View File

@@ -127,18 +127,23 @@ class HomeAssistantView extends WatchUi.Menu2 {
)); ));
} }
} else if (type.equals("numeric") && service != null) { } else if (type.equals("numeric") && service != null) {
addItem(HomeAssistantMenuItemFactory.create().numeric( if (tap_action != null) {
name, var picker = tap_action.get("picker") as Lang.Dictionary?;
entity, if (picker != null) {
content, addItem(HomeAssistantMenuItemFactory.create().numeric(
service, name,
data, entity,
{ content,
:exit => exit, service,
:confirm => confirm, picker,
:pin => pin {
:exit => exit,
:confirm => confirm,
:pin => pin
}
));
} }
)); }
} else if (type.equals("info") && content != null) { } else if (type.equals("info") && content != null) {
// Cannot exit from a non-actionable information only menu item. // Cannot exit from a non-actionable information only menu item.
addItem(HomeAssistantMenuItemFactory.create().tap( addItem(HomeAssistantMenuItemFactory.create().tap(
@@ -167,7 +172,6 @@ class HomeAssistantView extends WatchUi.Menu2 {
//! //!
//! @return An array of menu items that need to be updated periodically to reflect the latest Home Assistant state. //! @return An array of menu items that need to be updated periodically to reflect the latest Home Assistant state.
// //
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or HomeAssistantNumericMenuItem or Null> { function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or HomeAssistantNumericMenuItem or Null> {
var fullList = []; var fullList = [];
var lmi = mItems as Lang.Array<WatchUi.MenuItem>; var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
@@ -203,8 +207,8 @@ class HomeAssistantView extends WatchUi.Menu2 {
//! Called when this View is brought to the foreground. Restore //! Called when this View is brought to the foreground. Restore
//! the state of this View and prepare it to be shown. This includes //! the state of this View and prepare it to be shown. This includes
//! loading resources into memory. //! loading resources into memory.
//
function onShow() as Void {} function onShow() as Void {}
} }
//! Delegate for the HomeAssistantView. //! Delegate for the HomeAssistantView.
@@ -272,18 +276,13 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
var haItem = item as HomeAssistantNumericMenuItem; var haItem = item as HomeAssistantNumericMenuItem;
// System.println(haItem.getLabel() + " " + haItem.getId()); // System.println(haItem.getLabel() + " " + haItem.getId());
// create new view to select new value // create new view to select new value
var mPickerFactory = new HomeAssistantNumericFactory((haItem as HomeAssistantNumericMenuItem).getPicker());
var mPickerFactory = new HomeAssistantNumericFactory(haItem.getData()); var mPicker = new HomeAssistantNumericPicker(mPickerFactory,haItem);
var mPicker = new HomeAssistantNumericPicker(mPickerFactory,haItem);
var mPickerDelegate = new HomeAssistantNumericPickerDelegate(mPicker); var mPickerDelegate = new HomeAssistantNumericPickerDelegate(mPicker);
WatchUi.pushView(mPicker,mPickerDelegate,WatchUi.SLIDE_LEFT); WatchUi.pushView(mPicker,mPickerDelegate,WatchUi.SLIDE_LEFT);
} else if (item instanceof HomeAssistantGroupMenuItem) { } else if (item instanceof HomeAssistantGroupMenuItem) {
var haMenuItem = item as HomeAssistantGroupMenuItem; var haMenuItem = item as HomeAssistantGroupMenuItem;
// System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId());
WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT); WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT);
// } else {
// System.println(item.getLabel() + " " + item.getId());
} }
} }