- Tap on non-selected icon rotates instead of opening, flag prevents onSelect from double-firing - GpsService.stop() for clean cancellation - LoadingView stops GPS on hide (BACK button) - LoadingDelegate allows BACK to cancel GPS acquisition Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
83 lines
2.6 KiB
MonkeyC
83 lines
2.6 KiB
MonkeyC
import Toybox.Lang;
|
|
import Toybox.Position;
|
|
import Toybox.Timer;
|
|
|
|
// Single-shot GPS acquisition with a hard timeout. Uses both
|
|
// enableLocationEvents (fires on real hardware) and a polling
|
|
// fallback via Position.getInfo() (works in the simulator where
|
|
// "Set Position" doesn't trigger location events).
|
|
class GpsService {
|
|
|
|
private var _callback as Method(result as Dictionary or Null) as Void;
|
|
private var _timer as Timer.Timer;
|
|
private var _pollTimer 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();
|
|
_pollTimer = new Timer.Timer();
|
|
}
|
|
|
|
function start() as Void {
|
|
Position.enableLocationEvents(
|
|
Position.LOCATION_CONTINUOUS,
|
|
method(:_onPosition)
|
|
);
|
|
// Poll Position.getInfo() every 500ms as fallback for simulator.
|
|
_pollTimer.start(method(:_poll), 500, true);
|
|
_timer.start(method(:_onTimeout), Config.GPS_TIMEOUT_MS, false);
|
|
}
|
|
|
|
function _poll() as Void {
|
|
if (_finished) { return; }
|
|
var info = Position.getInfo();
|
|
if (info != null && info.position != null) {
|
|
_processInfo(info);
|
|
}
|
|
}
|
|
|
|
function _onPosition(info as Position.Info) as Void {
|
|
if (_finished) { return; }
|
|
_processInfo(info);
|
|
}
|
|
|
|
private function _processInfo(info as Position.Info) as Void {
|
|
if (info == null || info.position == null) { return; }
|
|
|
|
var degrees = info.position.toDegrees();
|
|
var quality = (info.accuracy != null) ? info.accuracy : 0;
|
|
|
|
// Always keep the best quality fix.
|
|
if (_bestFix == null || quality >= (_bestFix["q"] as Number)) {
|
|
_bestFix = {
|
|
"lat" => degrees[0].toFloat(),
|
|
"lon" => degrees[1].toFloat(),
|
|
"acc" => quality.toFloat(),
|
|
"q" => quality
|
|
};
|
|
}
|
|
}
|
|
|
|
function stop() as Void {
|
|
if (_finished) { return; }
|
|
_finished = true;
|
|
_timer.stop();
|
|
_pollTimer.stop();
|
|
Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:_onPosition));
|
|
}
|
|
|
|
function _onTimeout() as Void {
|
|
_finish(_bestFix);
|
|
}
|
|
|
|
function _finish(result as Dictionary or Null) as Void {
|
|
if (_finished) { return; }
|
|
_finished = true;
|
|
_timer.stop();
|
|
_pollTimer.stop();
|
|
Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:_onPosition));
|
|
_callback.invoke(result);
|
|
}
|
|
}
|