mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-14 15:16:40 +00:00
fix(ui): unify the four navigate views, add Set as target everywhere
Continuing the consistency pass: the four screens that show the distance/bearing "navigate to a point" view (Nodes, Waypoints, Trail's Track back, and navigating to a location shared in a message) had drifted apart in three ways. Only two of the four passed an EtaTracker to navview::draw(), so only Nodes and Track back showed the closing-speed/ETA line -- navigating to a waypoint or a shared location left it off for no reason. All four get one now. They also left the view on three different key sets (Back alone, Back+LEFT/RIGHT, Back+Enter); Back is now the only way out of any of them, so a stray sideways nudge can't drop you out of a running track-back. Messages' renderNav() also switched from reading node_prefs directly to the shared useImperial() helper the other three already used. Set as target -- the row Nodes and Waypoints both offer for a coordinate -- was missing from the message-location Options menu; added alongside Navigate and Save waypoint. Nodes' own Set as target required a full 32-byte public key, which a name-only live-track entry (someone sharing position on a channel who isn't a saved contact -- the group-outing case this exists for) never has. One flag was doing two jobs: "can be pinged" (needs the full key) and "can be identified" (needs only the 6-byte prefix a person target actually uses). Split into has_key/has_prefix; Set as target now only needs a position, resolving to a person target (follows them) when a prefix is available and a place target (pinned where they were) when it isn't -- the same distinction Locator's own picker already draws. Locator's target picker separately still listed the people pinned to the Favourites Dial as its privileged top tier, which stopped making sense once pinning and favouriting became separate concepts. It now leads with favourites instead, matching every other list in the firmware.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
// Locator config tool. Tools › Locator.
|
||||
// A single geofence whose target is either a saved waypoint (a place) or a
|
||||
// person — a favourite/contact or a live [LOC] sender, keyed by pubkey prefix.
|
||||
// person — a contact or a live [LOC] sender, keyed by pubkey prefix.
|
||||
// When armed the device beeps / alerts as it crosses into (arrive/near) or out
|
||||
// of (leave/away) the radius. A waypoint target is snapshotted (coord + label);
|
||||
// a person target follows their latest shared position. The crossing engine
|
||||
// lives in UITask::evaluateLocator(). The Target row's Enter opens a picker
|
||||
// ("None" first — the only way to unset a target once chosen — then
|
||||
// ("None" first — the only way to unset a target once chosen — then your
|
||||
// favourites, offered even with no known position yet so you can arm ahead
|
||||
// of time, then any other contact with a currently-known position:
|
||||
// live-sharing or just last-advertised, e.g. a repeater; then waypoints).
|
||||
@@ -188,8 +188,8 @@ public:
|
||||
}
|
||||
|
||||
// Build the selectable target set into _targets: "None" first (clears the
|
||||
// target — the only way to unset it once chosen), then favourites (the quick
|
||||
// path you pin ahead of time, offered even with no known position yet), then
|
||||
// target — the only way to unset it once chosen), then your favourites (the
|
||||
// quick path, offered even with no known position yet), then
|
||||
// any other contact with a currently-known position — live-sharing or just
|
||||
// last-advertised (a repeater, a room, or someone who shared a fix once) —
|
||||
// then saved waypoints. A person is keyed by pubkey prefix so the engine
|
||||
@@ -200,14 +200,11 @@ public:
|
||||
none.kind = 2; none.lat = 0; none.lon = 0; none.ts = 0; none.live = false; none.fav = false;
|
||||
memset(none.key, 0, 6);
|
||||
snprintf(none.name, sizeof(none.name), "(none)");
|
||||
for (int i = 0; i < NodePrefs::FAVOURITES_COUNT; i++) {
|
||||
const uint8_t* pre = _prefs->favourite_contacts[i];
|
||||
bool empty = true;
|
||||
for (int b = 0; b < NodePrefs::FAVOURITE_PREFIX_LEN; b++) if (pre[b]) { empty = false; break; }
|
||||
if (empty) continue;
|
||||
ContactInfo* c = the_mesh.lookupContactByPubKey(pre, NodePrefs::FAVOURITE_PREFIX_LEN);
|
||||
if (!c) continue;
|
||||
addPersonTarget(pre, c->name, /*require_position=*/false, (c->flags & 0x01) != 0);
|
||||
for (int idx = 0; _target_n < TARGET_MAX; idx++) {
|
||||
ContactInfo c;
|
||||
if (!the_mesh.getContactByIdx(idx, c)) break;
|
||||
if (!(c.flags & 0x01)) continue;
|
||||
addPersonTarget(c.id.pub_key, c.name, /*require_position=*/false, true);
|
||||
}
|
||||
for (int idx = 0; _target_n < TARGET_MAX; idx++) {
|
||||
ContactInfo c;
|
||||
|
||||
@@ -81,8 +81,8 @@ class MessagesScreen : public UIScreen {
|
||||
// location (a {loc} string or a [WAY] share), plus Path/Relayed by when the
|
||||
// entry has hop data recorded. _fs_act maps each visible row back to an
|
||||
// action so the index math survives the conditional layout.
|
||||
enum FsAct : uint8_t { FS_REPLY, FS_NAV, FS_SAVE, FS_PATH };
|
||||
uint8_t _fs_act[4];
|
||||
enum FsAct : uint8_t { FS_REPLY, FS_NAV, FS_SAVE, FS_TARGET, FS_PATH };
|
||||
uint8_t _fs_act[5];
|
||||
int _fs_act_n = 0;
|
||||
// Which history entry the currently-open Options menu (and, after FS_PATH is
|
||||
// chosen, the path/relay detail popup) refers to -- set by buildFsMenu().
|
||||
@@ -102,6 +102,7 @@ class MessagesScreen : public UIScreen {
|
||||
char _fs_path_item[24];
|
||||
// Inline navigate-to-location view layered over the fullscreen message.
|
||||
bool _nav_active = false;
|
||||
navview::EtaTracker _nav_eta; // closing speed / ETA, as in the other navigate views
|
||||
int32_t _nav_lat = 0, _nav_lon = 0;
|
||||
char _nav_label[24] = "";
|
||||
|
||||
@@ -292,11 +293,11 @@ class MessagesScreen : public UIScreen {
|
||||
}
|
||||
|
||||
// Build the fullscreen-message options popup: Reply (if allowed), Navigate /
|
||||
// Save waypoint when `body` carries a location, and Path / Relayed by when
|
||||
// the entry (ring_pos, in the DM ring or the channel ring per is_channel)
|
||||
// has hop data recorded. Opens _ctx_menu only when there's at least one
|
||||
// action. Parses the location once here and stashes it for the action
|
||||
// handler; stashes ring_pos/is_channel too, for showPathDetail().
|
||||
// Save waypoint / Set as target when `body` carries a location, and Path /
|
||||
// Relayed by when the entry (ring_pos, in the DM ring or the channel ring
|
||||
// per is_channel) has hop data recorded. Opens _ctx_menu only when there's at
|
||||
// least one action. Parses the location once here and stashes it for the
|
||||
// action handler; stashes ring_pos/is_channel too, for showPathDetail().
|
||||
void buildFsMenu(const char* body, bool reply_allowed, int ring_pos, bool is_channel) {
|
||||
_fs_menu_pos = ring_pos;
|
||||
_fs_menu_is_channel = is_channel;
|
||||
@@ -312,13 +313,14 @@ class MessagesScreen : public UIScreen {
|
||||
}
|
||||
bool has_path_item = hop_count > 0;
|
||||
|
||||
int n = (reply_allowed ? 1 : 0) + (has_loc ? 2 : 0) + (has_path_item ? 1 : 0);
|
||||
int n = (reply_allowed ? 1 : 0) + (has_loc ? 3 : 0) + (has_path_item ? 1 : 0);
|
||||
if (n == 0) return;
|
||||
_fs_act_n = 0;
|
||||
_ctx_menu.begin("Options", n);
|
||||
if (reply_allowed) { _ctx_menu.addItem("Reply"); _fs_act[_fs_act_n++] = FS_REPLY; }
|
||||
if (has_loc) { _ctx_menu.addItem("Navigate"); _fs_act[_fs_act_n++] = FS_NAV;
|
||||
_ctx_menu.addItem("Save waypoint"); _fs_act[_fs_act_n++] = FS_SAVE; }
|
||||
_ctx_menu.addItem("Save waypoint"); _fs_act[_fs_act_n++] = FS_SAVE;
|
||||
_ctx_menu.addItem("Set as target"); _fs_act[_fs_act_n++] = FS_TARGET; }
|
||||
if (has_path_item) {
|
||||
if (path_is_relay) snprintf(_fs_path_item, sizeof(_fs_path_item), "Relayed by (%u)", (unsigned)hop_count);
|
||||
else snprintf(_fs_path_item, sizeof(_fs_path_item), "Path (%u hop%s)", (unsigned)hop_count, hop_count == 1 ? "" : "s");
|
||||
@@ -384,8 +386,14 @@ class MessagesScreen : public UIScreen {
|
||||
startReply(channel);
|
||||
} else if (a == FS_NAV) {
|
||||
_nav_active = true; // keep the message view active underneath
|
||||
_nav_eta.reset();
|
||||
} else if (a == FS_SAVE) {
|
||||
saveSharedWaypoint();
|
||||
} else if (a == FS_TARGET) {
|
||||
// Kind 0 (a place): the coordinates were snapshotted out of the message
|
||||
// text, so there is no contact to keep re-resolving them from.
|
||||
_task->setTargetNow(0, nullptr, _nav_lat, _nav_lon,
|
||||
_nav_label[0] ? _nav_label : "Msg loc");
|
||||
} else if (a == FS_PATH) {
|
||||
showPathDetail();
|
||||
}
|
||||
@@ -407,9 +415,8 @@ class MessagesScreen : public UIScreen {
|
||||
void renderNav(DisplayDriver& display) {
|
||||
int32_t mylat, mylon; bool have = _task->currentLocation(mylat, mylon);
|
||||
int cog; bool cogv = _task->currentCourse(cog);
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
navview::draw(display, have, mylat, mylon, _nav_lat, _nav_lon,
|
||||
_nav_label[0] ? _nav_label : "Msg loc", cogv, cog, p && p->units_imperial);
|
||||
_nav_label[0] ? _nav_label : "Msg loc", cogv, cog, _task->useImperial(), &_nav_eta);
|
||||
}
|
||||
|
||||
void setupMsgPick() {
|
||||
@@ -1783,9 +1790,9 @@ public:
|
||||
// Channel Add/Edit form consumes all input while active.
|
||||
if (_ch_view.active()) return _ch_view.handleInput(c);
|
||||
|
||||
// Navigate view: Back or Enter returns to the message it was opened from.
|
||||
// Navigate view: Back returns to the message it was opened from.
|
||||
if (_nav_active) {
|
||||
if (c == KEY_CANCEL || c == KEY_ENTER) _nav_active = false;
|
||||
if (c == KEY_CANCEL) _nav_active = false;
|
||||
return true;
|
||||
}
|
||||
if (_phase == MODE_SELECT) {
|
||||
|
||||
@@ -41,7 +41,14 @@ class NearbyScreen : public UIScreen {
|
||||
char name[32];
|
||||
uint8_t type;
|
||||
uint8_t pub_key[PUB_KEY_SIZE];
|
||||
// has_key: the full 32-byte pubkey is present -- what Ping and the base64
|
||||
// key view need. has_prefix: at least the leading FAVOURITE_PREFIX_LEN
|
||||
// bytes are, which is all an identity-keyed reference needs (a Locator
|
||||
// person target, resolved via UITask::resolvePersonPos). Every has_key row
|
||||
// also has_prefix; the reverse doesn't hold -- a [LOC] share and a heard
|
||||
// advert carry a prefix and nothing more.
|
||||
bool has_key;
|
||||
bool has_prefix;
|
||||
// stored-source fields
|
||||
int32_t lat_e6, lon_e6;
|
||||
float dist_km;
|
||||
@@ -193,6 +200,7 @@ class NearbyScreen : public UIScreen {
|
||||
e.name[sizeof(e.name) - 1] = '\0';
|
||||
memcpy(e.pub_key, ci.id.pub_key, PUB_KEY_SIZE);
|
||||
e.has_key = true;
|
||||
e.has_prefix = true;
|
||||
e.lat_e6 = ci.gps_lat;
|
||||
e.lon_e6 = ci.gps_lon;
|
||||
bool remote_gps = (ci.gps_lat != 0 || ci.gps_lon != 0);
|
||||
@@ -280,7 +288,12 @@ class NearbyScreen : public UIScreen {
|
||||
// We only keep a key *prefix* for shares, not the full pubkey, so Ping
|
||||
// and the base64 key view (which need 32 bytes) stay unavailable for a
|
||||
// non-contact live entry. Navigate / Save-waypoint work off lat/lon.
|
||||
// A DM share is pubkey-keyed, so the prefix is real and good enough to
|
||||
// keep re-resolving them as a Locator person target; a channel share is
|
||||
// matched by name and carries no identity at all.
|
||||
e.has_key = false;
|
||||
e.has_prefix = s.verified;
|
||||
if (s.verified) memcpy(e.pub_key, s.key, LiveTrackStore::KEY_LEN);
|
||||
e.type = ADV_TYPE_CHAT;
|
||||
e.lat_e6 = s.lat_1e6;
|
||||
e.lon_e6 = s.lon_1e6;
|
||||
@@ -318,6 +331,8 @@ class NearbyScreen : public UIScreen {
|
||||
e.name[sizeof(e.name) - 1] = '\0';
|
||||
e.type = ADV_TYPE_CHAT; // unknown from AdvertPath — best-effort label
|
||||
e.has_key = false;
|
||||
e.has_prefix = true;
|
||||
memcpy(e.pub_key, a.pubkey_prefix, sizeof(a.pubkey_prefix));
|
||||
e.dist_km = -1.0f;
|
||||
e.lastmod = a.recv_timestamp;
|
||||
e.contact_idx = -1;
|
||||
@@ -363,6 +378,7 @@ class NearbyScreen : public UIScreen {
|
||||
e.type = dr[i].type;
|
||||
memcpy(e.pub_key, dr[i].pub_key, PUB_KEY_SIZE);
|
||||
e.has_key = true;
|
||||
e.has_prefix = true;
|
||||
e.rssi = dr[i].rssi;
|
||||
e.snr_x4 = dr[i].snr_x4;
|
||||
e.remote_snr_x4 = dr[i].remote_snr_x4;
|
||||
@@ -598,9 +614,9 @@ class NearbyScreen : public UIScreen {
|
||||
if (has_gps) add("Navigate", ACT_NAV);
|
||||
if (has_key) add("Ping", ACT_PING);
|
||||
if (has_gps) add("Save waypoint", ACT_WAYPOINT);
|
||||
// Needs both a position and a stable identity — a person target is keyed
|
||||
// by pubkey prefix, so a name-only live-scan/channel row can't offer this.
|
||||
if (has_gps && has_key) add("Set as target", ACT_LOCATOR);
|
||||
// Only a position is needed: with an identity prefix this becomes a person
|
||||
// target that follows them, without one a place target pinned where they were.
|
||||
if (has_gps) add("Set as target", ACT_LOCATOR);
|
||||
if (can_add) add("Add contact", ACT_ADD);
|
||||
if (is_contact && has_key) {
|
||||
snprintf(_fav_label, sizeof(_fav_label), e->fav ? "Fav: ON" : "Fav: OFF");
|
||||
@@ -661,8 +677,12 @@ class NearbyScreen : public UIScreen {
|
||||
case ACT_WAYPOINT: saveSelectedWaypoint(); break;
|
||||
case ACT_LOCATOR: {
|
||||
const Entry* e = selected();
|
||||
if (e && e->has_key && (e->lat_e6 != 0 || e->lon_e6 != 0))
|
||||
_task->setTargetNow(1, e->pub_key, e->lat_e6, e->lon_e6, e->name);
|
||||
if (!e || (e->lat_e6 == 0 && e->lon_e6 == 0)) break;
|
||||
// A prefix is enough to keep re-resolving someone who is moving; without
|
||||
// one (a channel share, matched by name) the honest target is the place
|
||||
// they were last seen, snapshotted like a waypoint.
|
||||
if (e->has_prefix) _task->setTargetNow(1, e->pub_key, e->lat_e6, e->lon_e6, e->name);
|
||||
else _task->setTargetNow(0, nullptr, e->lat_e6, e->lon_e6, e->name);
|
||||
break;
|
||||
}
|
||||
case ACT_ADD: {
|
||||
@@ -963,7 +983,7 @@ public:
|
||||
bool handleInput(char c) override {
|
||||
// ── navigate-to-node view — any nav key returns to detail ─────────────────
|
||||
if (_nav) {
|
||||
if (c == KEY_CANCEL || keyIsPrev(c) || keyIsNext(c)) _nav = false;
|
||||
if (c == KEY_CANCEL) _nav = false; // only Back leaves a navigate view
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class WaypointsView {
|
||||
static const int TB_ARRIVE_M = 20;
|
||||
int _tb_idx = 0;
|
||||
navview::EtaTracker _tb_eta;
|
||||
navview::EtaTracker _wp_eta; // same readout for plain waypoint navigation
|
||||
|
||||
// GPS averaging (Tools › Trail › Settings › Mark avg). When enabled, markHere()
|
||||
// accumulates fixes for gps_avg_idx seconds and marks the mean position — a
|
||||
@@ -235,7 +236,7 @@ class WaypointsView {
|
||||
if (!rowTarget(_sel, tlat, tlon, label)) { _mode = LIST; return; }
|
||||
int32_t mylat, mylon; bool have = ownPos(mylat, mylon);
|
||||
int cog; bool cogv = _task->currentCourse(cog);
|
||||
navview::draw(display, have, mylat, mylon, tlat, tlon, label, cogv, cog, useImperial());
|
||||
navview::draw(display, have, mylat, mylon, tlat, tlon, label, cogv, cog, useImperial(), &_wp_eta);
|
||||
}
|
||||
|
||||
// Navigate to the current track-back breadcrumb. The header doubles as the
|
||||
@@ -462,9 +463,9 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Navigation view — any nav key returns to the list.
|
||||
// Navigation view — Back returns to the list, as in every navigate view.
|
||||
if (_mode == NAV) {
|
||||
if (c == KEY_CANCEL || keyIsPrev(c) || keyIsNext(c)) { _mode = LIST; }
|
||||
if (c == KEY_CANCEL) { _mode = LIST; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -485,7 +486,7 @@ public:
|
||||
if (c == KEY_DOWN) { _sel = (_sel < total - 1) ? _sel + 1 : 0; return true; }
|
||||
if (c == KEY_ENTER) {
|
||||
if (_sel == n) openAddForm(); // last row → open the add form
|
||||
else _mode = NAV; // a waypoint / Trail-start row
|
||||
else { _mode = NAV; _wp_eta.reset(); } // a waypoint / Trail-start row
|
||||
return true;
|
||||
}
|
||||
// Rename/Delete/Send/Locator apply to saved waypoints only — not Trail-start or Add.
|
||||
|
||||
Reference in New Issue
Block a user