Merge pull request #1 from thmichel:Picker

Picker
This commit is contained in:
thmichel
2025-10-11 22:01:51 +02:00
committed by GitHub
6 changed files with 218 additions and 290 deletions

View File

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

View File

@@ -28,10 +28,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
private var mExit as Lang.Boolean;
private var mPin as Lang.Boolean;
private var mData as Lang.Dictionary?;
private var mStep as Lang.Float=1.0;
private var mValueChanged as Lang.Boolean = false;
private var mValue as Lang.Float?;
private var mEntity as Lang.String?;
private var mValue as Lang.String?;
private var mFormatString as Lang.String="%.1f";
@@ -51,7 +48,6 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
//
function initialize(
label as Lang.String or Lang.Symbol,
entity as Lang.String,
template as Lang.String,
service as Lang.String?,
data as Lang.Dictionary?,
@@ -71,7 +67,6 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
mPin = options[:pin];
mLabel = label;
mHomeAssistantService = haService;
mEntity = entity;
HomeAssistantMenuItem.initialize(
label,
@@ -81,22 +76,11 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
: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 {
if (!mValueChanged) { return; }
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
if (mPin && hasTouchScreen) {
var pin = Settings.getPin();
@@ -147,43 +131,17 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
}
//! 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.
//
function onConfirm(b as Lang.Boolean) as Void {
if (mService != null) {
mHomeAssistantService.call(mService, {"entity_id" => mEntity,mData.get("valueLabel").toString() => mValue}, mExit);
}
mHomeAssistantService.call(mService, {"entity_id" => mData.get("entity_id").toString(),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.
//!
@@ -191,41 +149,28 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
//! 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 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) {
setSubLabel($.Rez.Strings.Empty);
} else if(data instanceof Lang.String) {
setSubLabel(data);
} else if(data instanceof Lang.Number) {
var d = data as Lang.Number;
setSubLabel(d.format("%d"));
} else if(data instanceof Lang.Float) {
var f = data as Lang.Float;
setSubLabel(f.format(mFormatString));
} else if(data instanceof Lang.Dictionary) {
// System.println("HomeAssistantMenuItem updateState() data = " + data);
if (data.get("error") != null) {
setSubLabel($.Rez.Strings.TemplateError);
} else {
setSubLabel($.Rez.Strings.PotentialError);
}
} else {
// The template must return a Lang.String, Number or Float, or the item cannot be formatted locally without error.
// The template must return a Float, or the item cannot be formatted locally without error.
setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String);
}
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 {
mValueChanged = b;
function setValue(value as Lang.String) as Void {
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) {
var haItem = item as HomeAssistantNumericMenuItem;
// System.println(haItem.getLabel() + " " + haItem.getId());
// create new view to select new valu
var numView = new HomeAssistantNumericView(haItem);
WatchUi.pushView(numView, new HomeAssistantNumericViewDelegate(false,haItem), WatchUi.SLIDE_LEFT);
// create new view to select new value
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) {
var haMenuItem = item as HomeAssistantGroupMenuItem;
// System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId());
WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT);
// } else {
// 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;
}
}