Phase 5: delete last event with confirmation countdown

Two-step delete: navigate to "Letzten löschen", press START/STOP
to open confirmation, press again to start 2.5s red arc countdown.
BACK cancels at any point. Currently uses click-to-start instead
of press-and-hold due to simulator key event limitations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
EiSiMo 2026-04-13 14:42:57 +02:00
parent 902121bd42
commit eea1a835cd
3 changed files with 118 additions and 1 deletions

24
source/DeleteDelegate.mc Normal file
View file

@ -0,0 +1,24 @@
import Toybox.Lang;
import Toybox.WatchUi;
// Input for DeleteView. START/STOP starts the countdown,
// BACK cancels at any time.
class DeleteDelegate extends WatchUi.BehaviorDelegate {
private var _view as DeleteView;
function initialize(view as DeleteView) {
BehaviorDelegate.initialize();
_view = view;
}
function onSelect() as Boolean {
_view.startHold();
return true;
}
function onBack() as Boolean {
_view.cancel();
return true;
}
}

92
source/DeleteView.mc Normal file
View file

@ -0,0 +1,92 @@
import Toybox.Attention;
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.Timer;
import Toybox.WatchUi;
// Long-press confirmation for deleting the last event. Shows a red
// arc that fills over DELETE_HOLD_MS. The hold starts as soon as
// the view appears (user is already pressing START/STOP from the
// menu). Releasing the key before the arc completes cancels.
class DeleteView extends WatchUi.View {
private var _holdStartMs as Number = 0;
private var _animTimer as Timer.Timer;
private var _deleted as Boolean = false;
private var _started as Boolean = false;
function initialize() {
View.initialize();
_animTimer = new Timer.Timer();
}
function onShow() as Void {
_animTimer.start(method(:_tick), 50, true);
}
function onHide() as Void {
_animTimer.stop();
}
function startHold() as Void {
if (_deleted || _started) { return; }
_started = true;
_holdStartMs = System.getTimer();
}
function cancel() as Void {
if (!_deleted) {
WatchUi.popView(WatchUi.SLIDE_RIGHT);
}
}
function _tick() as Void {
if (_deleted) { return; }
if (_started) {
var elapsed = System.getTimer() - _holdStartMs;
if (elapsed >= Config.DELETE_HOLD_MS) {
_performDelete();
return;
}
}
WatchUi.requestUpdate();
}
private function _performDelete() as Void {
_deleted = true;
_animTimer.stop();
EventStore.deleteLast();
if (Attention has :vibrate) {
Attention.vibrate([new Attention.VibeProfile(75, 200)] as Array<Attention.VibeProfile>);
}
WatchUi.switchToView(new SuccessView(), null, WatchUi.SLIDE_IMMEDIATE);
}
function onUpdate(dc as Dc) as Void {
dc.setColor(Config.COLOR_FG, Config.COLOR_BG);
dc.clear();
var cx = LayoutMetrics.centerX(dc);
var cy = LayoutMetrics.centerY(dc);
var radius = LayoutMetrics.edgeArcRadius(dc);
// Red arc showing hold progress.
var progress = 0.0;
if (_started) {
var elapsed = System.getTimer() - _holdStartMs;
progress = elapsed.toFloat() / Config.DELETE_HOLD_MS.toFloat();
if (progress > 1.0) { progress = 1.0; }
}
if (progress > 0.0) {
dc.setPenWidth(LayoutMetrics.edgeArcPenWidth(dc));
dc.setColor(Config.COLOR_DELETE, Config.COLOR_BG);
var sweep = progress * 360.0;
dc.drawArc(cx, cy, radius, Graphics.ARC_CLOCKWISE, 90, 90 - sweep);
}
// Prompt text.
dc.setColor(Config.COLOR_FG, Config.COLOR_BG);
TextUtils.drawResourceCentered(dc, Rez.Strings.delete_hold, cx, cy, Graphics.FONT_TINY);
}
}

View file

@ -33,7 +33,8 @@ class MenuDelegate extends WatchUi.BehaviorDelegate {
return true; return true;
} }
if (key.equals(Config.ACTION_DELETE)) { if (key.equals(Config.ACTION_DELETE)) {
// Phase 5 var view = new DeleteView();
WatchUi.pushView(view, new DeleteDelegate(view), WatchUi.SLIDE_LEFT);
return true; return true;
} }