mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
feat(NearbyScreen): waypoint/navigate from list, TIME filter, addWaypoint helper
NearbyScreen: - Hold Enter on a list row now shows Navigate and Save waypoint directly, without entering the detail view first - TIME added as the last filter slot (LEFT/RIGHT cycles Fav→ALL→…→Snsr→TIME); time-sort shows age in right column and sorts by last-heard descending - Save waypoint also available from detail view Options menu UITask: - Extract addWaypoint() helper (two overloads: with/without explicit timestamp) consolidating the full()+add()+saveWaypoints()+alert pattern; TrailScreen and QuickMsgScreen updated to use it Fixes: - SoundNotifier.h: correct NodePrefs.h include path (../NodePrefs.h) - kbAddSensorPlaceholders: dereference _kb pointer in SettingsScreen, QuickMsgScreen and BotScreen (signature changed to KeyboardWidget&) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -184,7 +184,7 @@ public:
|
||||
if (_sel == 2) {
|
||||
_kb->clearPlaceholders(); // trigger is literal text — placeholders never match incoming msgs
|
||||
} else {
|
||||
kbAddSensorPlaceholders(_kb, &sensors);
|
||||
kbAddSensorPlaceholders(*_kb, &sensors);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class NearbyScreen : public UIScreen {
|
||||
|
||||
int _visible = 4; // updated each render; used by handleInput for scroll clamping
|
||||
|
||||
static const int FILTER_COUNT = 6;
|
||||
static const int FILTER_COUNT = 7;
|
||||
static const char* FILTER_LABELS[FILTER_COUNT];
|
||||
static const uint8_t FILTER_TYPES[FILTER_COUNT];
|
||||
|
||||
@@ -121,7 +121,7 @@ class NearbyScreen : public UIScreen {
|
||||
ContactInfo ci;
|
||||
if (!the_mesh.getContactByIdx(i, ci)) continue;
|
||||
if (_filter == 0 && !(ci.flags & 1)) continue;
|
||||
if (_filter >= 2 && ci.type != FILTER_TYPES[_filter]) continue;
|
||||
if (_filter >= 2 && _filter < FILTER_COUNT - 1 && ci.type != FILTER_TYPES[_filter]) continue;
|
||||
|
||||
Entry& e = _entries[_count++];
|
||||
strncpy(e.name, ci.name, sizeof(e.name) - 1);
|
||||
@@ -137,12 +137,16 @@ class NearbyScreen : public UIScreen {
|
||||
e.lastmod = ci.lastmod;
|
||||
}
|
||||
|
||||
// sort by distance ascending; nodes without GPS go to the end
|
||||
for (int i = 0; i < _count - 1; i++) {
|
||||
int best = i;
|
||||
for (int j = i + 1; j < _count; j++) {
|
||||
float dj = _entries[j].dist_km, db = _entries[best].dist_km;
|
||||
if (dj >= 0.0f && (db < 0.0f || dj < db)) best = j;
|
||||
if (_filter == FILTER_COUNT - 1) {
|
||||
uint32_t tj = _entries[j].lastmod, tb = _entries[best].lastmod;
|
||||
if (tj > 0 && (tb == 0 || tj > tb)) best = j; // descending — most recent first
|
||||
} else {
|
||||
float dj = _entries[j].dist_km, db = _entries[best].dist_km;
|
||||
if (dj >= 0.0f && (db < 0.0f || dj < db)) best = j; // ascending — closest first
|
||||
}
|
||||
}
|
||||
if (best != i) { Entry tmp = _entries[i]; _entries[i] = _entries[best]; _entries[best] = tmp; }
|
||||
}
|
||||
@@ -629,11 +633,19 @@ public:
|
||||
display.drawTextEllipsized(2, y, dist_col - 4, filt);
|
||||
|
||||
display.setColor(sel ? DisplayDriver::DARK : DisplayDriver::LIGHT);
|
||||
char dist[10];
|
||||
if (e.dist_km >= 0.0f) geo::fmtDist(dist, sizeof(dist), e.dist_km, useImperial());
|
||||
else strncpy(dist, "?GPS", sizeof(dist));
|
||||
char right[10];
|
||||
if (_filter == FILTER_COUNT - 1) {
|
||||
uint32_t now = rtc_clock.getCurrentTime();
|
||||
uint32_t age = (e.lastmod > 0 && now >= e.lastmod) ? now - e.lastmod : 0;
|
||||
if (age < 60) snprintf(right, sizeof(right), "%us", age);
|
||||
else if (age < 3600) snprintf(right, sizeof(right), "%um", age / 60);
|
||||
else snprintf(right, sizeof(right), "%uh", age / 3600);
|
||||
} else {
|
||||
if (e.dist_km >= 0.0f) geo::fmtDist(right, sizeof(right), e.dist_km, useImperial());
|
||||
else strncpy(right, "?GPS", sizeof(right));
|
||||
}
|
||||
display.setCursor(dist_col, y);
|
||||
display.print(dist);
|
||||
display.print(right);
|
||||
}
|
||||
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
@@ -669,16 +681,27 @@ public:
|
||||
if (_opts.active) {
|
||||
auto res = _opts.handleInput(c);
|
||||
if (res == PopupMenu::SELECTED) {
|
||||
if (_opts.selectedIndex() == 0) { // Navigate
|
||||
int idx = _opts.selectedIndex();
|
||||
if (idx == 0) { // Navigate
|
||||
if (_sel < _count) {
|
||||
const Entry& e = _entries[_sel];
|
||||
if (e.lat_e6 != 0 || e.lon_e6 != 0) _nav = true;
|
||||
else _task->showAlert("No node GPS", 1000);
|
||||
}
|
||||
} else { // Ping
|
||||
} else if (idx == 1) { // Ping
|
||||
uint8_t pk[PUB_KEY_SIZE];
|
||||
openPingMenu();
|
||||
if (selectedStoredPubKey(pk)) startPingForKey(pk);
|
||||
} else if (idx == 2 && _sel < _count) { // Save waypoint
|
||||
const Entry& e = _entries[_sel];
|
||||
if (e.lat_e6 == 0 && e.lon_e6 == 0) {
|
||||
_task->showAlert("No node GPS", 1000);
|
||||
} else {
|
||||
char label[WAYPOINT_LABEL_LEN];
|
||||
strncpy(label, e.name, WAYPOINT_LABEL_LEN - 1);
|
||||
label[WAYPOINT_LABEL_LEN - 1] = '\0';
|
||||
_task->addWaypoint(e.lat_e6, e.lon_e6, label);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -694,9 +717,10 @@ public:
|
||||
|
||||
if (c == KEY_CANCEL) { _detail = false; closePingMenu(); return true; }
|
||||
if (c == KEY_CONTEXT_MENU) {
|
||||
_opts.begin("Options", 2);
|
||||
_opts.begin("Options", 3);
|
||||
_opts.addItem("Navigate");
|
||||
_opts.addItem("Ping");
|
||||
_opts.addItem("Save waypoint");
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
@@ -705,16 +729,39 @@ public:
|
||||
// ── context menu ─────────────────────────────────────────────────────────
|
||||
if (_ctx_menu.active) {
|
||||
auto res = _ctx_menu.handleInput(c);
|
||||
if (res == PopupMenu::SELECTED)
|
||||
enterDiscoverMode();
|
||||
if (res == PopupMenu::SELECTED) {
|
||||
int sel = _ctx_menu.selectedIndex();
|
||||
if (sel == 0) {
|
||||
enterDiscoverMode();
|
||||
} else if (sel == 1 && _sel < _count) {
|
||||
const Entry& e = _entries[_sel];
|
||||
if (e.lat_e6 != 0 || e.lon_e6 != 0) _nav = true;
|
||||
else _task->showAlert("No node GPS", 1000);
|
||||
} else if (sel == 2 && _sel < _count) {
|
||||
const Entry& e = _entries[_sel];
|
||||
if (e.lat_e6 == 0 && e.lon_e6 == 0) {
|
||||
_task->showAlert("No node GPS", 1000);
|
||||
} else {
|
||||
char label[WAYPOINT_LABEL_LEN];
|
||||
strncpy(label, e.name, WAYPOINT_LABEL_LEN - 1);
|
||||
label[WAYPOINT_LABEL_LEN - 1] = '\0';
|
||||
_task->addWaypoint(e.lat_e6, e.lon_e6, label);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── list view ────────────────────────────────────────────────────────────
|
||||
if (c == KEY_CANCEL) { _task->gotoToolsScreen(); return true; }
|
||||
if (c == KEY_CONTEXT_MENU) {
|
||||
_ctx_menu.begin("Options", 1);
|
||||
bool has_node = (_count > 0 && _sel < _count);
|
||||
_ctx_menu.begin("Options", has_node ? 3 : 1);
|
||||
_ctx_menu.addItem("Discover nearby");
|
||||
if (has_node) {
|
||||
_ctx_menu.addItem("Navigate");
|
||||
_ctx_menu.addItem("Save waypoint");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_UP && _sel > 0) {
|
||||
@@ -747,5 +794,5 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
const char* NearbyScreen::FILTER_LABELS[6] = { "Fav", "ALL", "Comp", "Rpt", "Room", "Snsr" };
|
||||
const uint8_t NearbyScreen::FILTER_TYPES[6] = { 0, 0, ADV_TYPE_CHAT, ADV_TYPE_REPEATER, ADV_TYPE_ROOM, ADV_TYPE_SENSOR };
|
||||
const char* NearbyScreen::FILTER_LABELS[7] = { "Fav", "ALL", "Comp", "Rpt", "Room", "Snsr", "TIME" };
|
||||
const uint8_t NearbyScreen::FILTER_TYPES[7] = { 0, 0, ADV_TYPE_CHAT, ADV_TYPE_REPEATER, ADV_TYPE_ROOM, ADV_TYPE_SENSOR, 0 };
|
||||
|
||||
@@ -189,7 +189,6 @@ class QuickMsgScreen : public UIScreen {
|
||||
// Save the location parsed from the open message as a waypoint. Uses the
|
||||
// [WAY] label when present, else auto-names it like a manually-marked point.
|
||||
void saveSharedWaypoint() {
|
||||
if (_task->waypoints().full()) { _task->showAlert("Waypoints full", 1000); return; }
|
||||
char label[WAYPOINT_LABEL_LEN];
|
||||
if (_nav_label[0]) {
|
||||
strncpy(label, _nav_label, sizeof(label) - 1);
|
||||
@@ -197,13 +196,7 @@ class QuickMsgScreen : public UIScreen {
|
||||
} else {
|
||||
snprintf(label, sizeof(label), "WP%d", _task->waypoints().count() + 1);
|
||||
}
|
||||
if (_task->waypoints().add(_nav_lat, _nav_lon,
|
||||
(uint32_t)rtc_clock.getCurrentTime(), label)) {
|
||||
_task->saveWaypoints();
|
||||
_task->showAlert("Waypoint saved", 800);
|
||||
} else {
|
||||
_task->showAlert("Waypoints full", 1000);
|
||||
}
|
||||
_task->addWaypoint(_nav_lat, _nav_lon, label);
|
||||
}
|
||||
|
||||
void renderNav(DisplayDriver& display) {
|
||||
@@ -1590,7 +1583,7 @@ public:
|
||||
if (c == KEY_ENTER) {
|
||||
if (_msg_sel == 0) {
|
||||
_kb->begin(_reply_mode ? _reply_prefix : "");
|
||||
kbAddSensorPlaceholders(_kb, &sensors);
|
||||
kbAddSensorPlaceholders(*_kb, &sensors);
|
||||
_phase = KEYBOARD;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -836,7 +836,7 @@ public:
|
||||
// can't overflow it on save.
|
||||
_kb->begin(p ? p->custom_msgs[slot] : "",
|
||||
p ? (int)sizeof(p->custom_msgs[slot]) - 1 : KB_MAX_LEN);
|
||||
kbAddSensorPlaceholders(_kb, &sensors);
|
||||
kbAddSensorPlaceholders(*_kb, &sensors);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// UITask::notify() don't create merge conflicts.
|
||||
#ifdef PIN_BUZZER
|
||||
#include <helpers/ui/buzzer.h>
|
||||
#include "NodePrefs.h"
|
||||
#include "../NodePrefs.h"
|
||||
|
||||
class SoundNotifier {
|
||||
genericBuzzer& _buz;
|
||||
|
||||
@@ -468,13 +468,7 @@ private:
|
||||
char label[WAYPOINT_LABEL_LEN];
|
||||
if (_add_label[0]) { strncpy(label, _add_label, sizeof(label) - 1); label[sizeof(label) - 1] = '\0'; }
|
||||
else snprintf(label, sizeof(label), "WP%d", _task->waypoints().count() + 1);
|
||||
if (_task->waypoints().add(lat, lon, (uint32_t)rtc_clock.getCurrentTime(), label)) {
|
||||
_task->saveWaypoints();
|
||||
_task->showAlert("Waypoint saved", 800);
|
||||
_wp_mode = WP_OFF;
|
||||
} else {
|
||||
_task->showAlert("Waypoints full", 1000);
|
||||
}
|
||||
if (_task->addWaypoint(lat, lon, label)) { _wp_mode = WP_OFF; }
|
||||
}
|
||||
|
||||
void renderAddForm(DisplayDriver& display) {
|
||||
@@ -511,12 +505,7 @@ private:
|
||||
snprintf(auto_lbl, sizeof(auto_lbl), "WP%d", _task->waypoints().count() + 1);
|
||||
buf = auto_lbl;
|
||||
}
|
||||
if (_task->waypoints().add(_mark_lat, _mark_lon, _mark_ts, buf)) {
|
||||
_task->saveWaypoints();
|
||||
_task->showAlert("Waypoint saved", 800);
|
||||
} else {
|
||||
_task->showAlert("Waypoints full", 1000);
|
||||
}
|
||||
_task->addWaypoint(_mark_lat, _mark_lon, _mark_ts, buf);
|
||||
}
|
||||
|
||||
void renderWpList(DisplayDriver& display) {
|
||||
|
||||
@@ -1964,6 +1964,21 @@ void UITask::saveWaypoints() {
|
||||
f.close();
|
||||
}
|
||||
|
||||
bool UITask::addWaypoint(int32_t lat, int32_t lon, uint32_t ts, const char* label) {
|
||||
if (_waypoints.full()) { showAlert("Waypoints full", 1000); return false; }
|
||||
if (_waypoints.add(lat, lon, ts, label)) {
|
||||
saveWaypoints();
|
||||
showAlert("Waypoint saved", 800);
|
||||
return true;
|
||||
}
|
||||
showAlert("Waypoints full", 1000);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UITask::addWaypoint(int32_t lat, int32_t lon, const char* label) {
|
||||
return addWaypoint(lat, lon, (uint32_t)rtc_clock.getCurrentTime(), label);
|
||||
}
|
||||
|
||||
char UITask::checkDisplayOn(char c) {
|
||||
if (_display != NULL) {
|
||||
if (!_display->isOn()) {
|
||||
|
||||
@@ -151,7 +151,11 @@ public:
|
||||
void gotoCompassScreen();
|
||||
TrailStore& trail() { return _trail; }
|
||||
WaypointStore& waypoints() { return _waypoints; }
|
||||
void saveWaypoints(); // persist the table to /waypoints
|
||||
void saveWaypoints();
|
||||
// Add a waypoint, persist, and show the standard "Waypoint saved" / "Waypoints
|
||||
// full" alert. Returns true on success. The ts-less overload uses current RTC time.
|
||||
bool addWaypoint(int32_t lat, int32_t lon, uint32_t ts, const char* label);
|
||||
bool addWaypoint(int32_t lat, int32_t lon, const char* label);
|
||||
// Current course over ground in degrees (0..359), or false if not enough
|
||||
// recent movement to derive a stable heading. Independent of trail logging.
|
||||
bool currentCourse(int& deg_out) const;
|
||||
|
||||
Reference in New Issue
Block a user