add pin confirmation

This commit is contained in:
Matthias Oesterheld
2024-10-15 19:59:04 +02:00
parent 04c61981ea
commit b48102f9a6
7 changed files with 214 additions and 4 deletions

View File

@ -85,7 +85,8 @@ class HomeAssistantMenuItemFactory {
template as Lang.String or Null,
service as Lang.String or Null,
confirm as Lang.Boolean,
data as Lang.Dictionary or Null
data as Lang.Dictionary or Null,
pin as Lang.String or Null
) as WatchUi.MenuItem {
if (entity != null) {
if (data == null) {
@ -100,6 +101,7 @@ class HomeAssistantMenuItemFactory {
template,
service,
confirm,
pin,
data,
mTapTypeIcon,
mMenuItemOptions,
@ -111,6 +113,7 @@ class HomeAssistantMenuItemFactory {
template,
service,
confirm,
pin,
data,
mInfoTypeIcon,
mMenuItemOptions,

View File

@ -0,0 +1,161 @@
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.WatchUi;
import Toybox.Timer;
import Toybox.Attention;
class PinDigit extends WatchUi.Selectable {
private var mDigit as Number;
function initialize(digit as Number, halfX as Number, halfY as Number) {
var margin = 40;
var x = (digit % 2 == 1) ? 0 + margin : halfX + margin; // place even numbers in right half, odd in left half
var y = (digit < 3) ? 0 + margin : halfY + margin; // place 1&2 on top half, 3&4 on bottom half
var width = halfX - 2 * margin;
var height = halfY - 2 * margin;
// build text area
var textArea = new WatchUi.TextArea({
:text=>digit.format("%d"),
:color=>Graphics.COLOR_WHITE,
:font=>[Graphics.FONT_NUMBER_THAI_HOT, Graphics.FONT_NUMBER_HOT, Graphics.FONT_NUMBER_MEDIUM, Graphics.FONT_NUMBER_MILD],
:width=>width,
:height=>height,
:justification=>Graphics.TEXT_JUSTIFY_CENTER
});
// initialize selectable
Selectable.initialize({
:stateDefault=>textArea,
:locX =>x,
:locY=>y,
:width=>width,
:height=>height
});
mDigit = digit;
}
function getDigit() as Number {
return mDigit;
}
}
class HomeAssistantPinConfirmationView extends WatchUi.View {
function initialize() {
View.initialize();
}
function onLayout(dc as Dc) as Void {
var halfX = dc.getWidth()/2;
var halfY = dc.getHeight()/2;
// draw digits
setLayout([
new PinDigit(1, halfX, halfY),
new PinDigit(2, halfX, halfY),
new PinDigit(3, halfX, halfY),
new PinDigit(4, halfX, halfY)
]);
}
function onUpdate(dc as Dc) as Void {
View.onUpdate(dc);
// draw cross
var halfX = dc.getWidth()/2;
var halfY = dc.getHeight()/2;
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
dc.drawRectangle(halfX, dc.getHeight() * 0.1, 2, dc.getHeight() * 0.8);
dc.drawRectangle(dc.getWidth() * 0.1, halfY, dc.getWidth() * 0.8, 2);
}
}
class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
private var mPin as Array<Char>;
private var mCurrentIndex as Number;
private var mConfirmMethod as Method(state as Lang.Boolean) as Void;
private var mTimer as Timer.Timer or Null;
private var mState as Lang.Boolean;
function initialize(callback as Method(state as Lang.Boolean) as Void, state as Lang.Boolean, pin as String) {
BehaviorDelegate.initialize();
mPin = pin.toCharArray();
mCurrentIndex = 0;
mConfirmMethod = callback;
mState = state;
resetTimer();
}
function onSelectable(event as SelectableEvent) as Boolean {
var instance = event.getInstance();
if (instance instanceof PinDigit && event.getPreviousState() == :stateSelected) {
var currentDigit = getTranscodedCurrentDigit();
if (currentDigit != null && currentDigit == instance.getDigit()) {
// System.println("Pin digit " + (mCurrentIndex+1) + " matches");
if (mCurrentIndex == mPin.size()-1) {
getApp().getQuitTimer().reset();
if (mTimer != null) {
mTimer.stop();
}
mConfirmMethod.invoke(mState);
WatchUi.popView(WatchUi.SLIDE_RIGHT);
} else {
mCurrentIndex++;
resetTimer();
}
} else {
// System.println("Pin digit " + (mCurrentIndex+1) + " doesn't match");
// TODO: add maxFailures counter & protection
error();
}
}
return true;
}
function getTranscodedCurrentDigit() as Number {
var currentDigit = mPin[mCurrentIndex].toString().toNumber(); // this is ugly, but apparently the only way for char<->number comparisons
// TODO: Transcode digit using a pin mask for additional security
return currentDigit;
}
function resetTimer() {
var timeout = Settings.getConfirmTimeout(); // ms
if (timeout > 0) {
if (mTimer != null) {
mTimer.stop();
} else {
mTimer = new Timer.Timer();
}
mTimer.start(method(:goBack), timeout, true);
}
}
function goBack() as Void {
if (mTimer != null) {
mTimer.stop();
}
WatchUi.popView(WatchUi.SLIDE_RIGHT);
}
function error() as Void {
if (Attention has :vibrate && Settings.getVibrate()) {
Attention.vibrate([
new Attention.VibeProfile(100, 100),
new Attention.VibeProfile(0, 200),
new Attention.VibeProfile(75, 100),
new Attention.VibeProfile(0, 200),
new Attention.VibeProfile(50, 100),
new Attention.VibeProfile(0, 200),
new Attention.VibeProfile(25, 100)
]);
}
goBack();
}
}

View File

@ -28,12 +28,14 @@ class HomeAssistantTapMenuItem extends WatchUi.IconMenuItem {
private var mService as Lang.String or Null;
private var mConfirm as Lang.Boolean;
private var mData as Lang.Dictionary or Null;
private var mPin as Lang.String or Null;
function initialize(
label as Lang.String or Lang.Symbol,
template as Lang.String,
service as Lang.String or Null,
confirm as Lang.Boolean,
pin as Lang.String or Null,
data as Lang.Dictionary or Null,
icon as Graphics.BitmapType or WatchUi.Drawable,
options as {
@ -54,6 +56,7 @@ class HomeAssistantTapMenuItem extends WatchUi.IconMenuItem {
mService = service;
mConfirm = confirm;
mData = data;
mPin = pin;
}
function hasTemplate() as Lang.Boolean {
@ -84,7 +87,14 @@ class HomeAssistantTapMenuItem extends WatchUi.IconMenuItem {
}
function callService() as Void {
if (mConfirm) {
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
if (mPin != null && hasTouchScreen) {
WatchUi.pushView(
new HomeAssistantPinConfirmationView(),
new HomeAssistantPinConfirmationDelegate(method(:onConfirm), false, mPin),
WatchUi.SLIDE_IMMEDIATE
);
} else if (mConfirm || (mPin!=null && !hasTouchScreen)) {
WatchUi.pushView(
new HomeAssistantConfirmation(),
new HomeAssistantConfirmationDelegate(method(:onConfirm), false),

View File

@ -52,10 +52,12 @@ class HomeAssistantView extends WatchUi.Menu2 {
var service = items[i].get("service") as Lang.String or Null; // Deprecated schema
var confirm = false as Lang.Boolean or Null;
var data = null as Lang.Dictionary or Null;
var pin = null as Lang.String or Null;
if (tap_action != null) {
service = tap_action.get("service");
confirm = tap_action.get("confirm"); // Optional
data = tap_action.get("data"); // Optional
pin = tap_action.get("pin"); // Optional
if (confirm == null) {
confirm = false;
}
@ -64,7 +66,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
if (type.equals("toggle") && entity != null) {
addItem(HomeAssistantMenuItemFactory.create().toggle(name, entity, content, confirm));
} else if ((type.equals("tap") && service != null) || (type.equals("template") && content != null)) {
addItem(HomeAssistantMenuItemFactory.create().tap(name, entity, content, service, confirm, data));
addItem(HomeAssistantMenuItemFactory.create().tap(name, entity, content, service, confirm, data, pin));
} else if (type.equals("group")) {
addItem(HomeAssistantMenuItemFactory.create().group(items[i], content));
}