mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-08-04 11:16:13 +00:00
fix(companion): Live Share UX + map labels
- Live Share tool: add scrollbar, drop non-clickable section headers and the one-shot "Share now" (keep Auto-share toggle; manual share stays on the Map). Ellipsize the "To" target so long names don't wrap. - Move "Track loc" (receive) from Settings into Live Share. - Map: 2-char contact labels (distinguishable); drop "press" hint from the Home Map page status line so it fits on OLED. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d78e74d4cd
commit
1d2f2beaba
@@ -1,11 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// Live location sharing config tool. Tools › Live Share.
|
// Live location sharing config tool. Tools › Live Share.
|
||||||
// Periodically broadcasts a [LOC] message to a chosen channel/contact while
|
// One place for both directions of position sharing:
|
||||||
// moving. Independent of (and able to run alongside) the 0-hop Auto-Advert.
|
// 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.
|
// Included by UITask.cpp after AutoAdvertScreen.h.
|
||||||
|
|
||||||
#include "../GeoUtils.h" // LOCATION_MSG_TAG
|
|
||||||
#include "../NodePrefs.h"
|
#include "../NodePrefs.h"
|
||||||
|
#include "icons.h" // scrollIndicatorReserve / drawScrollIndicator
|
||||||
|
|
||||||
class LiveShareScreen : public UIScreen {
|
class LiveShareScreen : public UIScreen {
|
||||||
UITask* _task;
|
UITask* _task;
|
||||||
@@ -14,43 +18,27 @@ class LiveShareScreen : public UIScreen {
|
|||||||
int _sel = 0;
|
int _sel = 0;
|
||||||
int _scroll = 0;
|
int _scroll = 0;
|
||||||
|
|
||||||
// Row model — headers are non-selectable section separators.
|
enum Kind : uint8_t { K_TRACK, K_AUTO, K_TARGET, K_MOVE, K_GAP, K_HB };
|
||||||
enum Kind : uint8_t { K_HEADER, K_TRACK, K_AUTO, K_TARGET, K_MOVE, K_GAP, K_HB, K_SHARE_NOW };
|
|
||||||
struct Row { Kind kind; const char* label; };
|
struct Row { Kind kind; const char* label; };
|
||||||
static const int ROW_COUNT = 12;
|
static const int ROW_COUNT = 6;
|
||||||
static Row rows(int i) {
|
static Row rows(int i) {
|
||||||
static const Row R[ROW_COUNT] = {
|
static const Row R[ROW_COUNT] = {
|
||||||
{ K_HEADER, "Receive" },
|
|
||||||
{ K_TRACK, "Track loc" },
|
{ K_TRACK, "Track loc" },
|
||||||
{ K_HEADER, "Send" },
|
|
||||||
{ K_AUTO, "Auto share" },
|
{ K_AUTO, "Auto share" },
|
||||||
{ K_HEADER, "Target" },
|
|
||||||
{ K_TARGET, "To" },
|
{ K_TARGET, "To" },
|
||||||
{ K_HEADER, "Triggers" },
|
|
||||||
{ K_MOVE, "Move" },
|
{ K_MOVE, "Move" },
|
||||||
{ K_GAP, "Min gap" },
|
{ K_GAP, "Min gap" },
|
||||||
{ K_HB, "Heartbeat" },
|
{ K_HB, "Heartbeat" },
|
||||||
{ K_HEADER, "" },
|
|
||||||
{ K_SHARE_NOW, "Share now" },
|
|
||||||
};
|
};
|
||||||
return R[i];
|
return R[i];
|
||||||
}
|
}
|
||||||
static bool selectable(Kind k) { return k != K_HEADER; }
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LiveShareScreen(UITask* task, NodePrefs* prefs) : _task(task), _prefs(prefs) {}
|
LiveShareScreen(UITask* task, NodePrefs* prefs) : _task(task), _prefs(prefs) {}
|
||||||
|
|
||||||
void enter() {
|
void enter() { _dirty = false; _sel = 0; _scroll = 0; }
|
||||||
_dirty = false;
|
|
||||||
if (!selectable(rows(_sel).kind)) _sel = firstSelectable();
|
|
||||||
}
|
|
||||||
|
|
||||||
int firstSelectable() const {
|
// Resolve the configured target's display name (channel name or contact name).
|
||||||
for (int i = 0; i < ROW_COUNT; i++) if (selectable(rows(i).kind)) return i;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── target helpers ─────────────────────────────────────────────────────────
|
|
||||||
void currentTargetName(char* buf, int n) {
|
void currentTargetName(char* buf, int n) {
|
||||||
if (!_prefs) { snprintf(buf, n, "none"); return; }
|
if (!_prefs) { snprintf(buf, n, "none"); return; }
|
||||||
if (_prefs->loc_share_target_type == 0) {
|
if (_prefs->loc_share_target_type == 0) {
|
||||||
@@ -66,7 +54,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── value label for a row ──────────────────────────────────────────────────
|
|
||||||
void valueLabel(Kind k, char* buf, int n) {
|
void valueLabel(Kind k, char* buf, int n) {
|
||||||
switch (k) {
|
switch (k) {
|
||||||
case K_TRACK:
|
case K_TRACK:
|
||||||
@@ -104,55 +91,39 @@ public:
|
|||||||
|
|
||||||
const int y0 = display.listStart();
|
const int y0 = display.listStart();
|
||||||
const int step = display.lineStep();
|
const int step = display.lineStep();
|
||||||
const int valx = display.width() / 2 + 6;
|
|
||||||
|
|
||||||
// Scroll window: keep the selected row visible.
|
|
||||||
int vis = (display.height() - y0) / step;
|
int vis = (display.height() - y0) / step;
|
||||||
if (vis < 1) vis = 1;
|
if (vis < 1) vis = 1;
|
||||||
if (_sel < _scroll) _scroll = _sel;
|
if (_sel < _scroll) _scroll = _sel;
|
||||||
if (_sel >= _scroll + vis) _scroll = _sel - vis + 1;
|
if (_sel >= _scroll + vis) _scroll = _sel - vis + 1;
|
||||||
if (_scroll < 0) _scroll = 0;
|
if (_scroll < 0) _scroll = 0;
|
||||||
if (_scroll > ROW_COUNT - vis) _scroll = (ROW_COUNT > vis) ? ROW_COUNT - vis : 0;
|
|
||||||
|
const int reserve = scrollIndicatorReserve(display, ROW_COUNT, vis);
|
||||||
|
const int valx = display.width() / 2 + 6;
|
||||||
|
|
||||||
for (int i = _scroll; i < ROW_COUNT && i < _scroll + vis; i++) {
|
for (int i = _scroll; i < ROW_COUNT && i < _scroll + vis; i++) {
|
||||||
int y = y0 + (i - _scroll) * step;
|
int y = y0 + (i - _scroll) * step;
|
||||||
Row r = rows(i);
|
Row r = rows(i);
|
||||||
if (r.kind == K_HEADER) {
|
|
||||||
if (r.label[0]) {
|
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
|
||||||
display.setCursor(2, y);
|
|
||||||
display.print(r.label);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
bool sel = (i == _sel);
|
bool sel = (i == _sel);
|
||||||
display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
|
display.drawSelectionRow(0, y - 1, display.width() - reserve, step - 1, sel);
|
||||||
display.setCursor(4, y);
|
display.setCursor(4, y);
|
||||||
display.print(r.label);
|
display.print(r.label);
|
||||||
char val[24];
|
char val[24];
|
||||||
valueLabel(r.kind, val, sizeof(val));
|
valueLabel(r.kind, val, sizeof(val));
|
||||||
if (val[0]) {
|
if (val[0]) display.drawTextEllipsized(valx, y, display.width() - valx - reserve, val);
|
||||||
display.setCursor(valx, y);
|
|
||||||
display.print(val);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawScrollIndicator(display, y0, vis * step, ROW_COUNT, vis, _scroll);
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
void moveSel(int dir) {
|
void moveSel(int dir) { _sel = (_sel + dir + ROW_COUNT) % ROW_COUNT; }
|
||||||
int i = _sel;
|
|
||||||
for (int step = 0; step < ROW_COUNT; step++) {
|
|
||||||
i = (i + dir + ROW_COUNT) % ROW_COUNT;
|
|
||||||
if (selectable(rows(i).kind)) { _sel = i; return; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cycle/act on the selected row. `enter` distinguishes ENTER (which opens the
|
// Cycle/act on the selected row. `enter` opens the target picker (vs LEFT/RIGHT
|
||||||
// target picker / fires Share now) from LEFT/RIGHT (value cycling only).
|
// value cycling).
|
||||||
void activate(int dir, bool enter) {
|
void activate(int dir, bool enter) {
|
||||||
if (!_prefs) return;
|
if (!_prefs) return;
|
||||||
Kind k = rows(_sel).kind;
|
switch (rows(_sel).kind) {
|
||||||
switch (k) {
|
|
||||||
case K_TRACK: _prefs->track_shared_loc ^= 1; _dirty = true; break;
|
case K_TRACK: _prefs->track_shared_loc ^= 1; _dirty = true; break;
|
||||||
case K_AUTO: _prefs->loc_share_enabled ^= 1; _dirty = true; break;
|
case K_AUTO: _prefs->loc_share_enabled ^= 1; _dirty = true; break;
|
||||||
case K_TARGET:
|
case K_TARGET:
|
||||||
@@ -168,8 +139,6 @@ public:
|
|||||||
case K_HB:
|
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);
|
_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;
|
_dirty = true; break;
|
||||||
case K_SHARE_NOW: shareNow(); break;
|
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,14 +171,6 @@ public:
|
|||||||
else memcpy(_prefs->loc_share_dm_prefix, list[nx].prefix, NodePrefs::FAVOURITE_PREFIX_LEN);
|
else memcpy(_prefs->loc_share_dm_prefix, list[nx].prefix, NodePrefs::FAVOURITE_PREFIX_LEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
void shareNow() {
|
|
||||||
int32_t lat, lon;
|
|
||||||
if (!_task->currentLocation(lat, lon)) { _task->showAlert("No GPS fix", 1000); return; }
|
|
||||||
char text[40];
|
|
||||||
snprintf(text, sizeof(text), LOCATION_MSG_TAG "%.5f,%.5f", lat / 1e6, lon / 1e6);
|
|
||||||
_task->shareToMessage(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool handleInput(char c) override {
|
bool handleInput(char c) override {
|
||||||
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) {
|
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) {
|
||||||
if (_dirty) { the_mesh.savePrefs(); _dirty = false; }
|
if (_dirty) { the_mesh.savePrefs(); _dirty = false; }
|
||||||
|
|||||||
@@ -584,7 +584,7 @@ private:
|
|||||||
if (cx < area_x) cx = area_x; else if (cx > wp_x_max) cx = wp_x_max;
|
if (cx < area_x) cx = area_x; else if (cx > wp_x_max) cx = wp_x_max;
|
||||||
if (cy < area_y) cy = area_y; else if (cy > wp_y_max) cy = wp_y_max;
|
if (cy < area_y) cy = area_y; else if (cy > wp_y_max) cy = wp_y_max;
|
||||||
drawContactMarker(display, cx, cy);
|
drawContactMarker(display, cx, cy);
|
||||||
char s2[2] = { s.name[0], 0 };
|
char s2[3] = { s.name[0], s.name[0] ? s.name[1] : (char)0, 0 };
|
||||||
drawWaypointLabel(display, s2, cx, cy, area_x, area_y, area_w, area_h);
|
drawWaypointLabel(display, s2, cx, cy, area_x, area_y, area_w, area_h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -963,8 +963,7 @@ public:
|
|||||||
int32_t la, lo;
|
int32_t la, lo;
|
||||||
bool fix = _task->currentLocation(la, lo);
|
bool fix = _task->currentLocation(la, lo);
|
||||||
int trk = _task->liveTrack().active(rtc_clock.getCurrentTime());
|
int trk = _task->liveTrack().active(rtc_clock.getCurrentTime());
|
||||||
snprintf(info, sizeof(info), "Fix:%s Track:%d %s",
|
snprintf(info, sizeof(info), "Fix:%s Track:%d", fix ? "y" : "n", trk);
|
||||||
fix ? "y" : "n", trk, PRESS_LABEL);
|
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
if (!drew)
|
if (!drew)
|
||||||
display.drawTextCentered(display.width() / 2, content_y + area_h / 2, "No GPS / no trail");
|
display.drawTextCentered(display.width() / 2, content_y + area_h / 2, "No GPS / no trail");
|
||||||
|
|||||||
Reference in New Issue
Block a user