Using a Picker to set new value

This commit is contained in:
thmichel
2025-10-11 21:51:50 +02:00
parent 52e2efddd8
commit 2981893af7
6 changed files with 216 additions and 275 deletions

View File

@@ -182,7 +182,6 @@ class HomeAssistantMenuItemFactory {
return new HomeAssistantNumericMenuItem( return new HomeAssistantNumericMenuItem(
label, label,
entity_id,
template, template,
service, service,
data, data,

View File

@@ -28,10 +28,7 @@ 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 mStep as Lang.Float=1.0; private var mValue as Lang.String?;
private var mValueChanged as Lang.Boolean = false;
private var mValue as Lang.Float?;
private var mEntity as Lang.String?;
private var mFormatString as Lang.String="%.1f"; private var mFormatString as Lang.String="%.1f";
@@ -51,7 +48,6 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
// //
function initialize( function initialize(
label as Lang.String or Lang.Symbol, label as Lang.String or Lang.Symbol,
entity as Lang.String,
template as Lang.String, template as Lang.String,
service as Lang.String?, service as Lang.String?,
data as Lang.Dictionary?, data as Lang.Dictionary?,
@@ -71,7 +67,6 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
mPin = options[:pin]; mPin = options[:pin];
mLabel = label; mLabel = label;
mHomeAssistantService = haService; mHomeAssistantService = haService;
mEntity = entity;
HomeAssistantMenuItem.initialize( HomeAssistantMenuItem.initialize(
label, label,
@@ -81,22 +76,11 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
:icon => options[:icon] :icon => options[:icon]
} }
); );
if (mData.get("step") != null) {
mStep = mData.get("step").toString().toFloat();
} }
if (mData.get("formatString") != null) {
mFormatString=mData.get("formatString").toString();
}
}
function callService() as Void { function callService() as Void {
if (!mValueChanged) { return; }
var hasTouchScreen = System.getDeviceSettings().isTouchScreen; var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
if (mPin && hasTouchScreen) { if (mPin && hasTouchScreen) {
var pin = Settings.getPin(); var pin = Settings.getPin();
@@ -147,43 +131,18 @@ 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 {
if (mService != null) { mHomeAssistantService.call(mService, {"entity_id" => mData.get("entity_id").toString(),mData.get("valueLabel").toString() => mValue}, mExit);
mHomeAssistantService.call(mService, {"entity_id" => mEntity,mData.get("valueLabel").toString() => mValue}, mExit);
}
} }
///! Increase value when Up button is pressed or touch screen swipe down
function increaseValue() as Void {
if (mValueChanged)
{
mValue += mStep;
}
else {
mValue= getSubLabel().toFloat() + mStep;
mValueChanged=true;
}
setSubLabel(mValue.format(mFormatString));
}
///! Decrease value when Down button is pressed or touch screen swipe up
function decreaseValue() as Void {
if (mValueChanged)
{
mValue -= mStep;
}
else {
mValue= getSubLabel().toFloat() - mStep;
mValueChanged=true;
}
setSubLabel(mValue.format(mFormatString));
}
//! 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.
//! //!
@@ -191,12 +150,6 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
//! 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 { function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
// If vlue has changed, don't use value from HomeAssitant but display target value
if (mValueChanged) {
setSubLabel(mValue.format(mFormatString));
WatchUi.requestUpdate();
return;
}
if (data == null) { if (data == null) {
setSubLabel($.Rez.Strings.Empty); setSubLabel($.Rez.Strings.Empty);
} else if(data instanceof Lang.String) { } else if(data instanceof Lang.String) {
@@ -221,11 +174,16 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
WatchUi.requestUpdate(); WatchUi.requestUpdate();
} }
//! Set the mValuChanged value. //! Set the mValue value.
//! //!
//! Can be used to reenable update of subLabel //! Needed to set new value via the Service call
// //
function setValueChanged(b as Lang.Boolean) as Void { function setValue(value as Lang.String) as Void {
mValueChanged = b; mValue = value;
} }
function getData() as Lang.Dictionary {
return mData;
}
} }

View File

@@ -1,212 +0,0 @@
//-----------------------------------------------------------------------------------
//
// 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 & Someone0nEarth & moesterheld, 31 October 2023
//
//-----------------------------------------------------------------------------------
using Toybox.Application;
using Toybox.Lang;
using Toybox.Graphics;
using Toybox.System;
using Toybox.WatchUi;
using Toybox.Application.Properties;
using Toybox.Timer;
//! Home Assistant menu construction.
//
class HomeAssistantNumericView extends WatchUi.Menu2 {
private var mMenuItem as HomeAssistantNumericMenuItem;
//! Class Constructor
//
function initialize(
menuItem as HomeAssistantNumericMenuItem
) {
mMenuItem = menuItem;
WatchUi.Menu2.initialize({:title => mMenuItem.getLabel()});
addItem(mMenuItem);
//updateState(mData);
}
//! Return the menu item
//!
//! @return A HomeAssitantTapMenuItem (or null).
//
function getMenuItem() as HomeAssistantNumericMenuItem? {
return mMenuItem;
}
//! 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
//! 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 {
if (data == null) {
mMenuItem.setSubLabel($.Rez.Strings.Empty);
} else if(data instanceof Lang.String) {
mMenuItem.setSubLabel(data);
} else if(data instanceof Lang.Number) {
var d = data as Lang.Number;
mMenuItem.setSubLabel(d.format("%d"));
} else if(data instanceof Lang.Float) {
var f = data as Lang.Float;
mMenuItem.setSubLabel(f.format("%f"));
} else if(data instanceof Lang.Dictionary) {
// System.println("HomeAssistantMenuItem updateState() data = " + data);
if (data.get("error") != null) {
mMenuItem.setSubLabel($.Rez.Strings.TemplateError);
} else {
mMenuItem.setSubLabel($.Rez.Strings.PotentialError);
}
} else {
// The template must return a Lang.String, Number or Float, or the item cannot be formatted locally without error.
mMenuItem.setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String);
}
WatchUi.requestUpdate();
}
//! Return a list of items that need to be updated within this menu structure.
//!
//! MN. Lang.Array.addAll() fails structural type checking without including "Null" in the return type
//!
//! @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 Null> {
var fullList = [];
var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
for(var i = 0; i < mItems.size(); i++) {
var item = lmi[i];
if (item instanceof HomeAssistantGroupMenuItem) {
// Group menu items can now have an optional template to evaluate
var gmi = item as HomeAssistantGroupMenuItem;
if (gmi.hasTemplate()) {
fullList.add(item);
}
fullList.addAll(item.getMenuView().getItemsToUpdate());
} else if (item instanceof HomeAssistantToggleMenuItem) {
fullList.add(item);
} else if (item instanceof HomeAssistantTapMenuItem) {
var tmi = item as HomeAssistantTapMenuItem;
if (tmi.hasTemplate()) {
fullList.add(item);
}
}
}
return fullList;
}
//! Called when this View is brought to the foreground. Restore
//! the state of this View and prepare it to be shown. This includes
//! loading resources into memory.
function onShow() as Void {}
}
//! Delegate for the HomeAssistantView.
//!
//! Reference: https://developer.garmin.com/connect-iq/core-topics/input-handling/
//
class HomeAssistantNumericViewDelegate extends WatchUi.Menu2InputDelegate {
private var mIsRootMenuView as Lang.Boolean = false;
private var mTimer as QuitTimer;
private var mItem as HomeAssistantNumericMenuItem;
//! Class Constructor
//!
//! @param isRootMenuView As menus can be nested, this state marks the top level menu so that the
//! back event can exit the application completely rather than just popping
//! a menu view.
//tap
function initialize(isRootMenuView as Lang.Boolean, item as HomeAssistantNumericMenuItem) {
Menu2InputDelegate.initialize();
mIsRootMenuView = isRootMenuView;
mTimer = getApp().getQuitTimer();
mItem = item;
}
//! Handle the back button (ESC)
//
function onBack() {
mTimer.reset();
mItem.setValueChanged(false);
if (mIsRootMenuView) {
// If its started from glance or as an activity, directly exit the widget/app
// (on widgets without glance, this exit() won't do anything,
// so the base view will be shown instead, through the popView below this "if body")
System.exit();
}
WatchUi.popView(WatchUi.SLIDE_RIGHT);
}
//! Only for CheckboxMenu
//
function onDone() {
mTimer.reset();
}
//! Only for CustomMenu
//
function onFooter() {
mTimer.reset();
}
// Decrease Value
function onNextPage() as Lang.Boolean {
mItem.decreaseValue();
return true;
}
//Increase Value
function onPreviousPage() as Lang.Boolean {
mItem.increaseValue();
return true;
}
//! Select event
//!
//! @param item Selected menu item.
//
function onSelect(item as WatchUi.MenuItem) as Void {
mTimer.reset();
mItem.callService();
WatchUi.popView(WatchUi.SLIDE_RIGHT);
return;
}
//! Only for CustomMenu
//
function onTitle() {
mTimer.reset();
}
}

View File

@@ -271,12 +271,18 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
} else if (item instanceof HomeAssistantNumericMenuItem) { } else if (item instanceof HomeAssistantNumericMenuItem) {
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 valu // create new view to select new value
var numView = new HomeAssistantNumericView(haItem);
WatchUi.pushView(numView, new HomeAssistantNumericViewDelegate(false,haItem), WatchUi.SLIDE_LEFT); var mPickerFactory = new HomeAssistantNumericFactory(haItem.getData());
var mPicker = new HomeAssistantNumericPicker(mPickerFactory,haItem);//{:pattern => [mPickerFactory});
var mPickerDelegate = new HomeAssistantNumericPickerDelegate(mPicker);
WatchUi.pushView(mPicker,mPickerDelegate,WatchUi.SLIDE_LEFT);
//WatchUi.pushView(numView, new HomeAssistantNumericViewDelegate(false,haItem), 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()); // 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 { // } else {
// System.println(item.getLabel() + " " + item.getId()); // System.println(item.getLabel() + " " + item.getId());

View File

@@ -0,0 +1,88 @@
//-----------------------------------------------------------------------------------
//
// 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 & Someone0nEarth, 31 October 2023
//
//------------------------------------------------------------
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.WatchUi;
//! Factory that controls which numbers can be picked
class HomeAssistantNumericFactory extends WatchUi.PickerFactory {
// define default values in case not contained in data
private var mStart as Lang.Float = 0.0;
private var mStop as Lang.Float = 100.0;
private var mStep as Lang.Float = 1.0;
private var mFormatString as Lang.String = "%.2f";
//! Class Constructor
//!
public function initialize(data as Lang.Dictionary) {
PickerFactory.initialize();
// Get values from data
var val = data.get("start");
if (val != null) {
mStart = val.toString().toFloat();
}
val = data.get("stop");
if (val != null) {
mStop = val.toString().toFloat();
}
val = data.get("step");
if (val != null) {
mStep = val.toString().toFloat();
}
val = data.get("formatString");
if (val != null) {
mFormatString = val.toString();
}
}
//! Get the index of a number item
//! @param value The number to get the index of
//! @return The index of the number
public function getIndex(value as Float) as Number {
return ((value / mStep) - mStart).toNumber();
}
//! Generate a Drawable instance for an item
//! @param index The item index
//! @param selected true if the current item is selected, false otherwise
//! @return Drawable for the item
public function getDrawable(index as Number, selected as Boolean) as Drawable? {
var value = getValue(index);
var text = "No item";
if (value instanceof Float) {
text = value.format(mFormatString);
}
return new WatchUi.Text({: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
//! @param index Index of the item to get the value of
//! @return Value of the item
public function getValue(index as Number) as Object? {
return mStart + (index * mStep);
}
//! Get the number of picker items
//! @return Number of items
public function getSize() as Number {
return ((mStop - mStart) / mStep).toNumber() + 1;
}
}

View File

@@ -0,0 +1,102 @@
//-----------------------------------------------------------------------------------
//
// 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 & Someone0nEarth, 31 October 2023
//
//------------------------------------------------------------
using Toybox.Application;
using Toybox.Lang;
using Toybox.Graphics;
using Toybox.System;
using Toybox.WatchUi;
//! Picker that allows the user to choose a float value
class HomeAssistantNumericPicker extends WatchUi.Picker {
private var mFactory as HomeAssistantNumericFactory;
private var mItem as HomeAssistantNumericMenuItem;
//! Constructor
public function initialize(factory as HomeAssistantNumericFactory, haItem as HomeAssistantNumericMenuItem) {
mFactory = factory;
var pickerOptions = {:pattern=>[mFactory]};
mItem=haItem;
var data = mItem.getData();
var start = 0.0;
var val = data.get("start");
if (val != null) {
start = val.toString().toFloat();
}
var step = 1.0;
val = data.get("step");
if (val != null) {
step = val.toString().toFloat();
}
val = haItem.getSubLabel().toFloat();
var index = ((val -start) / 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);
}
//! Get whether the user is done picking
//! @param value Value user selected
//! @return true if user is done, false otherwise
public function onConfirm(value as Lang.String) as Void {
mItem.setValue(value);
mItem.callService();
}
}
//! Responds to a numeric picker selection or cancellation
class HomeAssistantNumericPickerDelegate extends WatchUi.PickerDelegate {
private var mPicker as HomeAssistantNumericPicker;
//! Constructor
public function initialize(picker as HomeAssistantNumericPicker) {
PickerDelegate.initialize();
mPicker = picker;
}
//! Handle a cancel event from the picker
//! @return true if handled, false otherwise
public function onCancel() as Lang.Boolean {
WatchUi.popView(WatchUi.SLIDE_RIGHT);
return true;
}
//! Handle a confirm event from the picker
//! @param values The values chosen in the picker
//! @return true if handled, false otherwise
public function onAccept(values as Lang.Array) as Lang.Boolean {
var chosenValue = values[0].toString();
mPicker.onConfirm(chosenValue);
WatchUi.popView(WatchUi.SLIDE_RIGHT);
return true;
}
}