mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-08-02 10:16:11 +00:00
The "_dirty bool, then if (_dirty) the_mesh.savePrefs() on exit" pattern was duplicated across 9 screens with subtle divergence: some cleared the flag after saving, some left it set and relied on onShow() to reset. Replace all 12 exit sites with a single _task->savePrefsIfDirty(flag) helper that saves once only if dirty and always clears the flag, so the "did we touch flash?" answer lives in one place and the reset is consistent. Edit sites still mark the flag manually (inherent to change tracking); only the persist-on-exit boilerplate is centralised. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
176 lines
7.2 KiB
C++
176 lines
7.2 KiB
C++
#pragma once
|
||
// Live location sharing config tool. Tools › Live Share.
|
||
// One place for both directions of position sharing:
|
||
// Track loc — receive [LOC] shares from others (LiveTrackStore).
|
||
// Auto share — periodically broadcast my own [LOC] to a channel/contact
|
||
// while moving (movement-gated engine in UITask).
|
||
// Independent of (and able to run alongside) the 0-hop Auto-Advert. The
|
||
// one-shot "Share my pos" lives on the Map screen.
|
||
// Included by UITask.cpp after AutoAdvertScreen.h.
|
||
|
||
#include "../NodePrefs.h"
|
||
#include "icons.h" // drawList (shared scrolling-list helper)
|
||
|
||
class LiveShareScreen : public UIScreen {
|
||
UITask* _task;
|
||
NodePrefs* _prefs;
|
||
bool _dirty = false;
|
||
int _sel = 0;
|
||
int _scroll = 0;
|
||
|
||
enum Kind : uint8_t { K_TRACK, K_AUTO, K_TARGET, K_MOVE, K_GAP, K_HB };
|
||
struct Row { Kind kind; const char* label; };
|
||
static const int ROW_COUNT = 6;
|
||
static Row rows(int i) {
|
||
static const Row R[ROW_COUNT] = {
|
||
{ K_TRACK, "Track loc" },
|
||
{ K_AUTO, "Auto share" },
|
||
{ K_TARGET, "To" },
|
||
{ K_MOVE, "Move" },
|
||
{ K_GAP, "Min gap" },
|
||
{ K_HB, "Heartbeat" },
|
||
};
|
||
return R[i];
|
||
}
|
||
|
||
public:
|
||
LiveShareScreen(UITask* task, NodePrefs* prefs) : _task(task), _prefs(prefs) {}
|
||
|
||
void onShow() override { _dirty = false; _sel = 0; _scroll = 0; }
|
||
|
||
// Resolve the configured target's display name (channel name or contact name).
|
||
void currentTargetName(char* buf, int n) {
|
||
if (!_prefs) { snprintf(buf, n, "none"); return; }
|
||
if (_prefs->loc_share_target_type == 0) {
|
||
ChannelDetails ch;
|
||
if (the_mesh.getChannel(_prefs->loc_share_channel_idx, ch) && ch.name[0])
|
||
snprintf(buf, n, "%s", ch.name);
|
||
else
|
||
snprintf(buf, n, "Ch %d", (int)_prefs->loc_share_channel_idx);
|
||
} else {
|
||
ContactInfo* c = the_mesh.lookupContactByPubKey(_prefs->loc_share_dm_prefix, NodePrefs::FAVOURITE_PREFIX_LEN);
|
||
if (c) snprintf(buf, n, "%s", c->name);
|
||
else snprintf(buf, n, "(contact?)");
|
||
}
|
||
}
|
||
|
||
void valueLabel(Kind k, char* buf, int n) {
|
||
switch (k) {
|
||
case K_TRACK:
|
||
snprintf(buf, n, "%s", (_prefs && _prefs->track_shared_loc) ? "ON" : "OFF");
|
||
break;
|
||
case K_AUTO:
|
||
snprintf(buf, n, "%s", (_prefs && _prefs->loc_share_enabled) ? "ON" : "OFF");
|
||
break;
|
||
case K_TARGET:
|
||
currentTargetName(buf, n);
|
||
break;
|
||
case K_MOVE:
|
||
snprintf(buf, n, "%um", (unsigned)NodePrefs::locShareMoveMeters(_prefs ? _prefs->loc_share_move_idx : 1));
|
||
break;
|
||
case K_GAP: {
|
||
uint16_t s = NodePrefs::locShareIntervalSecs(_prefs ? _prefs->loc_share_interval_idx : 1);
|
||
if (s < 60) snprintf(buf, n, "%us", (unsigned)s);
|
||
else snprintf(buf, n, "%um", (unsigned)(s / 60));
|
||
break;
|
||
}
|
||
case K_HB: {
|
||
uint16_t s = NodePrefs::locShareHeartbeatSecs(_prefs ? _prefs->loc_share_heartbeat_idx : 0);
|
||
if (s == 0) snprintf(buf, n, "OFF");
|
||
else snprintf(buf, n, "%um", (unsigned)(s / 60));
|
||
break;
|
||
}
|
||
default: buf[0] = '\0';
|
||
}
|
||
}
|
||
|
||
int render(DisplayDriver& display) override {
|
||
display.setTextSize(1);
|
||
display.setColor(DisplayDriver::LIGHT);
|
||
display.drawCenteredHeader("LIVE SHARE");
|
||
|
||
const int valx = display.width() / 2 + 6;
|
||
drawList(display, ROW_COUNT, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
|
||
Row r = rows(i);
|
||
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel);
|
||
display.setCursor(4, y);
|
||
display.print(r.label);
|
||
char val[24];
|
||
valueLabel(r.kind, val, sizeof(val));
|
||
if (val[0]) display.drawTextEllipsized(valx, y, display.width() - valx - reserve, val);
|
||
});
|
||
return 500;
|
||
}
|
||
|
||
void moveSel(int dir) { _sel = (_sel + dir + ROW_COUNT) % ROW_COUNT; }
|
||
|
||
// Cycle/act on the selected row. `enter` opens the target picker (vs LEFT/RIGHT
|
||
// value cycling).
|
||
void activate(int dir, bool enter) {
|
||
if (!_prefs) return;
|
||
switch (rows(_sel).kind) {
|
||
case K_TRACK: _prefs->track_shared_loc ^= 1; _dirty = true; break;
|
||
case K_AUTO: _prefs->loc_share_enabled ^= 1; _dirty = true; break;
|
||
case K_TARGET:
|
||
if (enter) { _task->pickLocShareTarget(); return; } // full chooser
|
||
cycleTarget(dir); _dirty = true; // L/R quick cycle
|
||
break;
|
||
case K_MOVE:
|
||
_prefs->loc_share_move_idx = (uint8_t)((_prefs->loc_share_move_idx + (dir >= 0 ? 1 : NodePrefs::LOC_SHARE_MOVE_COUNT - 1)) % NodePrefs::LOC_SHARE_MOVE_COUNT);
|
||
_dirty = true; break;
|
||
case K_GAP:
|
||
_prefs->loc_share_interval_idx = (uint8_t)((_prefs->loc_share_interval_idx + (dir >= 0 ? 1 : NodePrefs::LOC_SHARE_INTERVAL_COUNT - 1)) % NodePrefs::LOC_SHARE_INTERVAL_COUNT);
|
||
_dirty = true; break;
|
||
case K_HB:
|
||
_prefs->loc_share_heartbeat_idx = (uint8_t)((_prefs->loc_share_heartbeat_idx + (dir >= 0 ? 1 : NodePrefs::LOC_SHARE_HEARTBEAT_COUNT - 1)) % NodePrefs::LOC_SHARE_HEARTBEAT_COUNT);
|
||
_dirty = true; break;
|
||
}
|
||
}
|
||
|
||
void cycleTarget(int dir) {
|
||
struct T { uint8_t type, ch; uint8_t prefix[NodePrefs::FAVOURITE_PREFIX_LEN]; } list[24];
|
||
int n = 0;
|
||
ChannelDetails ch;
|
||
for (int i = 0; i < 64 && n < 24; i++) {
|
||
if (!the_mesh.getChannel(i, ch) || ch.name[0] == '\0') continue;
|
||
list[n].type = 0; list[n].ch = (uint8_t)i; n++;
|
||
}
|
||
for (int i = 0; i < NodePrefs::FAVOURITES_COUNT && n < 24; 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; }
|
||
ContactInfo* fc = the_mesh.lookupContactByPubKey(pre, NodePrefs::FAVOURITE_PREFIX_LEN);
|
||
// Skip room servers: a [LOC] DM to a room server isn't reposted to the
|
||
// room's members, so it can't serve as a live-share broadcast target.
|
||
if (empty || !fc || fc->type == ADV_TYPE_ROOM) continue;
|
||
list[n].type = 1; list[n].ch = 0;
|
||
memcpy(list[n].prefix, pre, NodePrefs::FAVOURITE_PREFIX_LEN); n++;
|
||
}
|
||
if (n == 0) { _task->showAlert("No channels/favs", 1200); return; }
|
||
int cur = -1;
|
||
for (int i = 0; i < n; i++) {
|
||
if (list[i].type != _prefs->loc_share_target_type) continue;
|
||
if (list[i].type == 0) { if (list[i].ch == _prefs->loc_share_channel_idx) { cur = i; break; } }
|
||
else if (memcmp(list[i].prefix, _prefs->loc_share_dm_prefix, NodePrefs::FAVOURITE_PREFIX_LEN) == 0) { cur = i; break; }
|
||
}
|
||
int nx = (cur < 0) ? 0 : ((cur + (dir >= 0 ? 1 : n - 1)) % n);
|
||
_prefs->loc_share_target_type = list[nx].type;
|
||
if (list[nx].type == 0) _prefs->loc_share_channel_idx = list[nx].ch;
|
||
else memcpy(_prefs->loc_share_dm_prefix, list[nx].prefix, NodePrefs::FAVOURITE_PREFIX_LEN);
|
||
}
|
||
|
||
bool handleInput(char c) override {
|
||
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) {
|
||
_task->savePrefsIfDirty(_dirty);
|
||
_task->gotoToolsScreen();
|
||
return true;
|
||
}
|
||
if (c == KEY_UP) { moveSel(-1); return true; }
|
||
if (c == KEY_DOWN) { moveSel(+1); return true; }
|
||
if (keyIsPrev(c)) { activate(-1, false); return true; }
|
||
if (keyIsNext(c)) { activate(+1, false); return true; }
|
||
if (c == KEY_ENTER) { activate(+1, true); return true; }
|
||
return false;
|
||
}
|
||
};
|