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>
60 lines
1.9 KiB
MonkeyC
60 lines
1.9 KiB
MonkeyC
import Toybox.Lang;
|
|
import Toybox.Position;
|
|
import Toybox.Timer;
|
|
|
|
// Single-shot GPS acquisition with a hard timeout. Caller supplies
|
|
// a callback that fires exactly once with either a position fix or
|
|
// null (if nothing usable was received before the timeout). Accuracy
|
|
// shortcut: if the fix already hits Config.GPS_TARGET_ACCURACY_M we
|
|
// stop waiting even if the timeout hasn't expired.
|
|
class GpsService {
|
|
|
|
private var _callback as Method(result as Dictionary or Null) as Void;
|
|
private var _timer as Timer.Timer;
|
|
private var _finished as Boolean = false;
|
|
private var _bestFix as Dictionary or Null = null;
|
|
|
|
function initialize(callback as Method(result as Dictionary or Null) as Void) {
|
|
_callback = callback;
|
|
_timer = new Timer.Timer();
|
|
}
|
|
|
|
function start() as Void {
|
|
Position.enableLocationEvents(
|
|
Position.LOCATION_CONTINUOUS,
|
|
method(:_onPosition)
|
|
);
|
|
_timer.start(method(:_onTimeout), Config.GPS_TIMEOUT_MS, false);
|
|
}
|
|
|
|
function _onPosition(info as Position.Info) as Void {
|
|
if (_finished) { return; }
|
|
if (info == null || info.position == null) { return; }
|
|
|
|
var degrees = info.position.toDegrees();
|
|
var acc = (info.accuracy != null) ? info.accuracy.toFloat() : null;
|
|
|
|
_bestFix = {
|
|
"lat" => degrees[0].toFloat(),
|
|
"lon" => degrees[1].toFloat(),
|
|
"acc" => acc
|
|
};
|
|
|
|
// Good enough → stop early.
|
|
if (acc != null && acc <= Config.GPS_TARGET_ACCURACY_M) {
|
|
_finish(_bestFix);
|
|
}
|
|
}
|
|
|
|
function _onTimeout() as Void {
|
|
_finish(_bestFix);
|
|
}
|
|
|
|
function _finish(result as Dictionary or Null) as Void {
|
|
if (_finished) { return; }
|
|
_finished = true;
|
|
_timer.stop();
|
|
Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:_onPosition));
|
|
_callback.invoke(result);
|
|
}
|
|
}
|