einsatzprotokoll/source/LoadingView.mc
EiSiMo 025d3007db Phase 3: event creation flow (GPS + success / error)
GpsService runs a single-shot position request with a hard timeout
and early-exit when Config.GPS_TARGET_ACCURACY_M is reached.
LoadingView renders a circular progress bar around the edge plus the
"Standort wird bestimmt" prompt; on callback it persists a new Event
via EventStore.add and transitions to SuccessView (green checkmark,
short vibration, auto-close) or ErrorView (red alert, 3× vibration,
German message, longer hold). TextUtils extracts the shared
multi-line centered text rendering so MenuView, LoadingView and
ErrorView all render wrapped German text consistently. Positioning
permission added to manifest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 20:36:22 +02:00

91 lines
3 KiB
MonkeyC

import Toybox.Graphics;
import Toybox.Lang;
import Toybox.Math;
import Toybox.Time;
import Toybox.Timer;
import Toybox.WatchUi;
// Full-screen loading state shown while GpsService is acquiring a
// fix. Draws a circular progress bar along the display edge plus
// the German prompt "Standort wird bestimmt" in the center.
class LoadingView extends WatchUi.View {
private var _eventType as String;
private var _gps as GpsService or Null;
private var _animTimer as Timer.Timer;
private var _startMs as Number = 0;
function initialize(eventType as String) {
View.initialize();
_eventType = eventType;
_animTimer = new Timer.Timer();
}
function onShow() as Void {
_startMs = _nowMs();
_animTimer.start(method(:_tick), 50, true);
_gps = new GpsService(method(:_onGpsResult));
_gps.start();
}
function onHide() as Void {
_animTimer.stop();
}
function _tick() as Void {
WatchUi.requestUpdate();
}
function _onGpsResult(result as Dictionary or Null) as Void {
_animTimer.stop();
var now = Time.now().value();
if (result != null) {
var event = new Event(
_eventType,
now,
result["lat"] as Float or Null,
result["lon"] as Float or Null,
result["acc"] as Float or Null
);
EventStore.add(event);
WatchUi.switchToView(new SuccessView(), null, WatchUi.SLIDE_IMMEDIATE);
} else {
// No usable fix at all → save event with null coords (per
// spec), but still surface an error screen so the user
// knows GPS did not lock.
var event = new Event(_eventType, now, null, null, null);
EventStore.add(event);
WatchUi.switchToView(new ErrorView(), 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);
// Circular progress — white arc that grows with elapsed time.
var elapsed = _nowMs() - _startMs;
var progress = elapsed.toFloat() / Config.GPS_TIMEOUT_MS.toFloat();
if (progress > 1.0) { progress = 1.0; }
dc.setPenWidth(LayoutMetrics.edgeArcPenWidth(dc));
dc.setColor(Config.COLOR_ACCENT, Config.COLOR_BG);
var sweep = progress * 360.0;
// drawArc uses degrees with 0° = 3 o'clock and counter-clockwise.
// Start at 12 o'clock and sweep clockwise.
var startDeg = 90;
var endDeg = 90 - sweep;
dc.drawArc(cx, cy, radius, Graphics.ARC_CLOCKWISE, startDeg, endDeg);
dc.setColor(Config.COLOR_FG, Config.COLOR_BG);
TextUtils.drawResourceCentered(dc, Rez.Strings.loading_gps, cx, cy, Graphics.FONT_TINY);
}
private function _nowMs() as Number {
return System.getTimer();
}
}