mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 19:02:00 +00:00
feat(l2): someone's SOS is a red, pulsing dot on the maps and "SOS" in Nodes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ public:
|
|||||||
// channel (name, best-effort) one.
|
// channel (name, best-effort) one.
|
||||||
bool is_live;
|
bool is_live;
|
||||||
bool live_verified;
|
bool live_verified;
|
||||||
|
bool is_sos; // the live position came with an SOS (LiveTrackStore::Entry::sos)
|
||||||
// Favourite (ContactInfo::flags bit 0) -- only a contact carries it.
|
// Favourite (ContactInfo::flags bit 0) -- only a contact carries it.
|
||||||
bool fav;
|
bool fav;
|
||||||
};
|
};
|
||||||
@@ -130,6 +131,7 @@ public:
|
|||||||
e.is_known = true;
|
e.is_known = true;
|
||||||
e.is_live = false;
|
e.is_live = false;
|
||||||
e.live_verified = false;
|
e.live_verified = false;
|
||||||
|
e.is_sos = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeLiveTrack();
|
mergeLiveTrack();
|
||||||
@@ -161,6 +163,7 @@ public:
|
|||||||
e.contact_idx = -1;
|
e.contact_idx = -1;
|
||||||
e.is_live = false;
|
e.is_live = false;
|
||||||
e.live_verified = false;
|
e.live_verified = false;
|
||||||
|
e.is_sos = false;
|
||||||
}
|
}
|
||||||
// strongest first
|
// strongest first
|
||||||
for (int i = 0; i < _count - 1; i++) {
|
for (int i = 0; i < _count - 1; i++) {
|
||||||
@@ -230,6 +233,7 @@ protected:
|
|||||||
if (s.ts > e.lastmod) e.lastmod = s.ts;
|
if (s.ts > e.lastmod) e.lastmod = s.ts;
|
||||||
e.is_live = true;
|
e.is_live = true;
|
||||||
e.live_verified = s.verified;
|
e.live_verified = s.verified;
|
||||||
|
e.is_sos = s.sos;
|
||||||
} else if (_count < MAX_NEARBY && typeMatchesFilter(ADV_TYPE_CHAT, 0, false)) {
|
} else if (_count < MAX_NEARBY && typeMatchesFilter(ADV_TYPE_CHAT, 0, false)) {
|
||||||
// A sender we don't have as a contact: treat it as a companion (the only
|
// A sender we don't have as a contact: treat it as a companion (the only
|
||||||
// node type that shares position), so the type filter still applies.
|
// node type that shares position), so the type filter still applies.
|
||||||
@@ -253,6 +257,7 @@ protected:
|
|||||||
e.is_known = false;
|
e.is_known = false;
|
||||||
e.is_live = true;
|
e.is_live = true;
|
||||||
e.live_verified = s.verified;
|
e.live_verified = s.verified;
|
||||||
|
e.is_sos = s.sos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,6 +292,7 @@ protected:
|
|||||||
e.contact_idx = -1;
|
e.contact_idx = -1;
|
||||||
e.is_known = false;
|
e.is_known = false;
|
||||||
e.is_live = false;
|
e.is_live = false;
|
||||||
|
e.is_sos = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ static lv_obj_t* s_imgs[COLS * ROWS];
|
|||||||
static lv_obj_t* s_hint = nullptr;
|
static lv_obj_t* s_hint = nullptr;
|
||||||
static lv_obj_t* s_caption = nullptr;
|
static lv_obj_t* s_caption = nullptr;
|
||||||
static const int MAX_PTS = LiveTrackStore::CAPACITY + 2;
|
static const int MAX_PTS = LiveTrackStore::CAPACITY + 2;
|
||||||
enum : uint8_t { P_ME, P_LAST, P_TARGET, P_LIVE }; // how a point's dot looks
|
enum : uint8_t { P_ME, P_LAST, P_TARGET, P_LIVE, P_SOS }; // how a point's dot looks
|
||||||
struct Pt { int32_t lat, lon; uint8_t kind; lv_obj_t* obj; };
|
struct Pt { int32_t lat, lon; uint8_t kind; lv_obj_t* obj; };
|
||||||
static PSRAM_BSS Pt s_pts[MAX_PTS]; // [0] you, then the target, then live shares
|
static PSRAM_BSS Pt s_pts[MAX_PTS]; // [0] you, then the target, then live shares
|
||||||
static int s_npts = 0;
|
static int s_npts = 0;
|
||||||
@@ -160,6 +160,7 @@ static lv_obj_t* dot(uint8_t kind) {
|
|||||||
const struct { uint8_t d; uint32_t col, border; } LOOK[] = {
|
const struct { uint8_t d; uint32_t col, border; } LOOK[] = {
|
||||||
{ 14, theme::ACCENT, theme::BG }, { 14, theme::BG, theme::ACCENT }, // you; no fix: the last position, hollow
|
{ 14, theme::ACCENT, theme::BG }, { 14, theme::BG, theme::ACCENT }, // you; no fix: the last position, hollow
|
||||||
{ 12, theme::BG, theme::ACCENT }, { 12, theme::OK, theme::BG }, // the target; live shares
|
{ 12, theme::BG, theme::ACCENT }, { 12, theme::OK, theme::BG }, // the target; live shares
|
||||||
|
{ 12, theme::FAIL, theme::BG }, // someone's SOS, pulsing
|
||||||
};
|
};
|
||||||
int d = LOOK[kind].d;
|
int d = LOOK[kind].d;
|
||||||
uint32_t col = LOOK[kind].col, border = LOOK[kind].border;
|
uint32_t col = LOOK[kind].col, border = LOOK[kind].border;
|
||||||
@@ -172,6 +173,7 @@ static lv_obj_t* dot(uint8_t kind) {
|
|||||||
lv_obj_set_style_border_color(o, lv_color_hex(border), 0);
|
lv_obj_set_style_border_color(o, lv_color_hex(border), 0);
|
||||||
lv_obj_set_style_border_width(o, 2, 0);
|
lv_obj_set_style_border_width(o, 2, 0);
|
||||||
lv_obj_remove_flag(o, LV_OBJ_FLAG_CLICKABLE);
|
lv_obj_remove_flag(o, LV_OBJ_FLAG_CLICKABLE);
|
||||||
|
if (kind == P_SOS) mapview::sosPulse(o);
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
} // namespace mini
|
} // namespace mini
|
||||||
@@ -708,7 +710,7 @@ bool UITask::homeMapFit() {
|
|||||||
for (int i = 0; i < LiveTrackStore::CAPACITY && np < MAX_PTS; i++) {
|
for (int i = 0; i < LiveTrackStore::CAPACITY && np < MAX_PTS; i++) {
|
||||||
if (!lt.isActive(i, now)) continue;
|
if (!lt.isActive(i, now)) continue;
|
||||||
const LiveTrackStore::Entry& e = lt.slotAt(i);
|
const LiveTrackStore::Entry& e = lt.slotAt(i);
|
||||||
pts[np++] = { e.lat_1e6, e.lon_1e6, P_LIVE, nullptr };
|
pts[np++] = { e.lat_1e6, e.lon_1e6, e.sos ? P_SOS : P_LIVE, nullptr };
|
||||||
live++;
|
live++;
|
||||||
}
|
}
|
||||||
bool same = np == s_npts;
|
bool same = np == s_npts;
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ struct Mark {
|
|||||||
int16_t lx, ly, lw, lh; // name pill, layer px (lw 0: no room for it)
|
int16_t lx, ly, lw, lh; // name pill, layer px (lw 0: no room for it)
|
||||||
uint8_t order; // 1.. while it is a hop of the route being picked, else 0
|
uint8_t order; // 1.. while it is a hop of the route being picked, else 0
|
||||||
bool vis; // inside the view
|
bool vis; // inside the view
|
||||||
|
bool sos; // someone's SOS: a pulsing ring around the dot (s_sos_rings)
|
||||||
};
|
};
|
||||||
// What was drawn for a slot last time, to repaint only what changed.
|
// What was drawn for a slot last time, to repaint only what changed.
|
||||||
struct MarkSig { int16_t x, y, lx, ly, lw; uint32_t col, hash; uint8_t order; bool vis; };
|
struct MarkSig { int16_t x, y, lx, ly, lw; uint32_t col, hash; uint8_t order; bool vis; };
|
||||||
@@ -198,6 +199,10 @@ static Mark* s_marks = psramBuf<Mark>(MAX_MARKS);
|
|||||||
static MarkSig* s_sigs = psramBuf<MarkSig>(MAX_MARKS);
|
static MarkSig* s_sigs = psramBuf<MarkSig>(MAX_MARKS);
|
||||||
static int s_sig_n = 0;
|
static int s_sig_n = 0;
|
||||||
static lv_obj_t* s_mark_layer = nullptr;
|
static lv_obj_t* s_mark_layer = nullptr;
|
||||||
|
// Rings pulsing out of the SOS markers in view: objects over the drawn layer,
|
||||||
|
// so only their small area is repainted each frame, not the layer.
|
||||||
|
static const int SOS_RINGS = 4;
|
||||||
|
static lv_obj_t* s_sos_rings[SOS_RINGS];
|
||||||
// The route being picked (Route > Map): its points as lat/lon, and in layer px
|
// The route being picked (Route > Map): its points as lat/lon, and in layer px
|
||||||
// from the last layout, drawn as a line under the markers.
|
// from the last layout, drawn as a line under the markers.
|
||||||
static const int ROUTE_PTS = HopRoute::MAX_PTS; // a picked route has 10 at most
|
static const int ROUTE_PTS = HopRoute::MAX_PTS; // a picked route has 10 at most
|
||||||
@@ -505,6 +510,27 @@ static void drawFlows(lv_layer_t* layer, const lv_area_t& a) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const int MARK_D = 12; // marker dot diameter
|
static const int MARK_D = 12; // marker dot diameter
|
||||||
|
|
||||||
|
// An SOS marker's ring: an outline round `o` (the dot's size) that grows out
|
||||||
|
// of it and fades, over and over. Home's mini map uses it too.
|
||||||
|
static void setSosPulse(void* o, int32_t v) {
|
||||||
|
lv_obj_set_style_outline_pad((lv_obj_t*)o, (int32_t)(v * 14 / 1000), 0);
|
||||||
|
lv_obj_set_style_outline_opa((lv_obj_t*)o, (lv_opa_t)(LV_OPA_COVER - v * LV_OPA_COVER / 1000), 0);
|
||||||
|
}
|
||||||
|
static void sosPulse(lv_obj_t* o) {
|
||||||
|
lv_obj_set_style_outline_color(o, lv_color_hex(theme::FAIL), 0);
|
||||||
|
lv_obj_set_style_outline_width(o, 2, 0);
|
||||||
|
lv_anim_t a;
|
||||||
|
lv_anim_init(&a);
|
||||||
|
lv_anim_set_var(&a, o);
|
||||||
|
lv_anim_set_exec_cb(&a, setSosPulse);
|
||||||
|
lv_anim_set_values(&a, 0, 1000);
|
||||||
|
lv_anim_set_duration(&a, 1200);
|
||||||
|
lv_anim_set_repeat_delay(&a, 300);
|
||||||
|
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
|
||||||
|
lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
|
||||||
|
lv_anim_start(&a);
|
||||||
|
}
|
||||||
static const int MARK_LABELS_MAX = 20; // names shown at once: the rest stay dots
|
static const int MARK_LABELS_MAX = 20; // names shown at once: the rest stay dots
|
||||||
|
|
||||||
// LV_EVENT_DRAW_MAIN of the marker layer: the name pills, then the dots over them.
|
// LV_EVENT_DRAW_MAIN of the marker layer: the name pills, then the dots over them.
|
||||||
@@ -830,6 +856,15 @@ void UITask::buildMap() {
|
|||||||
lv_obj_remove_flag(mapview::s_mark_layer, LV_OBJ_FLAG_CLICKABLE);
|
lv_obj_remove_flag(mapview::s_mark_layer, LV_OBJ_FLAG_CLICKABLE);
|
||||||
lv_obj_remove_flag(mapview::s_mark_layer, LV_OBJ_FLAG_SCROLLABLE);
|
lv_obj_remove_flag(mapview::s_mark_layer, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
lv_obj_add_event_cb(mapview::s_mark_layer, mapview::drawMarks, LV_EVENT_DRAW_MAIN, NULL);
|
lv_obj_add_event_cb(mapview::s_mark_layer, mapview::drawMarks, LV_EVENT_DRAW_MAIN, NULL);
|
||||||
|
for (lv_obj_t*& r : mapview::s_sos_rings) {
|
||||||
|
r = lv_obj_create(mapview::s_mark_layer);
|
||||||
|
lv_obj_remove_style_all(r);
|
||||||
|
lv_obj_set_size(r, mapview::MARK_D + 1, mapview::MARK_D + 1);
|
||||||
|
lv_obj_set_style_radius(r, LV_RADIUS_CIRCLE, 0);
|
||||||
|
lv_obj_remove_flag(r, LV_OBJ_FLAG_CLICKABLE);
|
||||||
|
lv_obj_add_flag(r, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
mapview::sosPulse(r);
|
||||||
|
}
|
||||||
mapview::s_flow = &_core->flow;
|
mapview::s_flow = &_core->flow;
|
||||||
mapview::s_view_ok = false;
|
mapview::s_view_ok = false;
|
||||||
mapview::s_flow_area_on = false;
|
mapview::s_flow_area_on = false;
|
||||||
@@ -1120,15 +1155,15 @@ void UITask::rebuildNodeMarkers() {
|
|||||||
const NearbyModel::Entry& e = _nearby->at(i);
|
const NearbyModel::Entry& e = _nearby->at(i);
|
||||||
if (e.lat_e6 == 0 && e.lon_e6 == 0) continue;
|
if (e.lat_e6 == 0 && e.lon_e6 == 0) continue;
|
||||||
if (e.is_live && _map_nav) continue; // the live share's own marker shows it
|
if (e.is_live && _map_nav) continue; // the live share's own marker shows it
|
||||||
uint32_t col = e.is_live ? theme::OK : e.type == ADV_TYPE_CHAT ? theme::TEXT : theme::TEXT_MUTED;
|
uint32_t col = e.is_sos ? theme::FAIL : e.is_live ? theme::OK : e.type == ADV_TYPE_CHAT ? theme::TEXT : theme::TEXT_MUTED;
|
||||||
addMapMark(mapview::MK_NODE, i, e.lat_e6, e.lon_e6, col, e.name[0] ? e.name : "?");
|
addMapMark(mapview::MK_NODE, i, e.lat_e6, e.lon_e6, col, e.name[0] ? e.name : "?", e.is_sos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A marker is a record: where it is, its colour and name. Placement and
|
// A marker is a record: where it is, its colour and name. Placement and
|
||||||
// painting are layoutMapMarks() and mapview::drawMarks(). The name's width is
|
// painting are layoutMapMarks() and mapview::drawMarks(). The name's width is
|
||||||
// measured here, not per pan: a slot whose name is unchanged keeps it.
|
// measured here, not per pan: a slot whose name is unchanged keeps it.
|
||||||
void UITask::addMapMark(uint8_t kind, int idx, int32_t lat_e6, int32_t lon_e6, uint32_t col, const char* text) {
|
void UITask::addMapMark(uint8_t kind, int idx, int32_t lat_e6, int32_t lon_e6, uint32_t col, const char* text, bool sos) {
|
||||||
if (!_map_marks || mapview::s_mark_count >= mapview::MAX_MARKS) return;
|
if (!_map_marks || mapview::s_mark_count >= mapview::MAX_MARKS) return;
|
||||||
uint32_t h = 2166136261u; // FNV-1a of the name
|
uint32_t h = 2166136261u; // FNV-1a of the name
|
||||||
for (const char* c = text; *c; c++) h = (h ^ (uint8_t)*c) * 16777619u;
|
for (const char* c = text; *c; c++) h = (h ^ (uint8_t)*c) * 16777619u;
|
||||||
@@ -1136,7 +1171,7 @@ void UITask::addMapMark(uint8_t kind, int idx, int32_t lat_e6, int32_t lon_e6, u
|
|||||||
mapview::Mark& mk = mapview::s_marks[k];
|
mapview::Mark& mk = mapview::s_marks[k];
|
||||||
bool same_name = k < mapview::s_mark_prev && mk.text_hash == h;
|
bool same_name = k < mapview::s_mark_prev && mk.text_hash == h;
|
||||||
mk.lat_e6 = lat_e6; mk.lon_e6 = lon_e6; mk.idx = idx; mk.kind = kind; mk.col = col; mk.text_hash = h;
|
mk.lat_e6 = lat_e6; mk.lon_e6 = lon_e6; mk.idx = idx; mk.kind = kind; mk.col = col; mk.text_hash = h;
|
||||||
mk.vis = false; mk.lw = 0; mk.order = 0;
|
mk.vis = false; mk.lw = 0; mk.order = 0; mk.sos = sos;
|
||||||
if (same_name) return;
|
if (same_name) return;
|
||||||
size_t n = strlen(text);
|
size_t n = strlen(text);
|
||||||
if (n > (size_t)mapview::MARK_NAME) { // cut at a character boundary
|
if (n > (size_t)mapview::MARK_NAME) { // cut at a character boundary
|
||||||
@@ -1268,6 +1303,14 @@ void UITask::layoutMapMarks(double left, double top, int w, int h) {
|
|||||||
const Mark& m = s_marks[k];
|
const Mark& m = s_marks[k];
|
||||||
s_sigs[k] = { m.x, m.y, m.lx, m.ly, m.lw, m.col, m.text_hash, m.order, m.vis };
|
s_sigs[k] = { m.x, m.y, m.lx, m.ly, m.lw, m.col, m.text_hash, m.order, m.vis };
|
||||||
}
|
}
|
||||||
|
int rings = 0;
|
||||||
|
for (int k = 0; k < s_mark_count && rings < SOS_RINGS; k++) {
|
||||||
|
const Mark& m = s_marks[k];
|
||||||
|
if (!m.vis || !m.sos || !s_sos_rings[rings]) continue;
|
||||||
|
lv_obj_set_pos(s_sos_rings[rings], m.x - half, m.y - half);
|
||||||
|
lv_obj_remove_flag(s_sos_rings[rings++], LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
for (; rings < SOS_RINGS; rings++) if (s_sos_rings[rings]) lv_obj_add_flag(s_sos_rings[rings], LV_OBJ_FLAG_HIDDEN);
|
||||||
s_sig_n = s_mark_count;
|
s_sig_n = s_mark_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ void UITask::rebuildNavMarkers() {
|
|||||||
char age[8], t[LiveTrackStore::NAME_LEN + 12];
|
char age[8], t[LiveTrackStore::NAME_LEN + 12];
|
||||||
geo::fmtAgeShort(age, sizeof(age), now, e.ts);
|
geo::fmtAgeShort(age, sizeof(age), now, e.ts);
|
||||||
snprintf(t, sizeof(t), "%s%s%s", e.name, age[0] ? " " : "", age);
|
snprintf(t, sizeof(t), "%s%s%s", e.name, age[0] ? " " : "", age);
|
||||||
addMapMark(mapview::MK_LIVE, i, e.lat_1e6, e.lon_1e6, theme::OK, t);
|
addMapMark(mapview::MK_LIVE, i, e.lat_1e6, e.lon_1e6, e.sos ? theme::FAIL : theme::OK, t, e.sos);
|
||||||
}
|
}
|
||||||
|
|
||||||
navTrailSync();
|
navTrailSync();
|
||||||
@@ -1550,10 +1550,10 @@ void UITask::navTargetsPopup() {
|
|||||||
const LiveTrackStore::Entry& e = lt.slotAt(i);
|
const LiveTrackStore::Entry& e = lt.slotAt(i);
|
||||||
char age[8], sub[40], d[12];
|
char age[8], sub[40], d[12];
|
||||||
geo::fmtAgeShort(age, sizeof(age), now, e.ts);
|
geo::fmtAgeShort(age, sizeof(age), now, e.ts);
|
||||||
snprintf(sub, sizeof(sub), "%s - %s ago", e.verified ? "direct" : "channel", age[0] ? age : "0s");
|
snprintf(sub, sizeof(sub), "%s%s - %s ago", e.sos ? "SOS, " : "", e.verified ? "direct" : "channel", age[0] ? age : "0s");
|
||||||
lv_obj_t* row = listRow(list, e.name, sub, onNavPick, (void*)(uintptr_t)navmap::code(navmap::T_LIVE, i));
|
lv_obj_t* row = listRow(list, e.name, sub, onNavPick, (void*)(uintptr_t)navmap::code(navmap::T_LIVE, i));
|
||||||
dist(e.lat_1e6, e.lon_1e6, d, sizeof(d));
|
dist(e.lat_1e6, e.lon_1e6, d, sizeof(d));
|
||||||
if (d[0]) navRowRight(row, d, theme::OK, theme::PAD);
|
if (d[0]) navRowRight(row, d, e.sos ? theme::FAIL : theme::OK, theme::PAD);
|
||||||
live++;
|
live++;
|
||||||
}
|
}
|
||||||
if (live == 0) label(list, "Nobody is sharing their position.", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
if (live == 0) label(list, "Nobody is sharing their position.", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ uint32_t UITask::nearbySignature() const {
|
|||||||
for (int i = 0; i < _nearby->count(); i++) {
|
for (int i = 0; i < _nearby->count(); i++) {
|
||||||
const NearbyModel::Entry& e = _nearby->at(i);
|
const NearbyModel::Entry& e = _nearby->at(i);
|
||||||
sig = sig * 31 + e.contact_idx + (uint32_t)e.lastmod + (uint32_t)(e.dist_km * 100) + e.rssi + e.fav
|
sig = sig * 31 + e.contact_idx + (uint32_t)e.lastmod + (uint32_t)(e.dist_km * 100) + e.rssi + e.fav
|
||||||
+ e.type * 5 + (e.is_live ? 3 : 0);
|
+ e.type * 5 + (e.is_live ? 3 : 0) + (e.is_sos ? 17 : 0);
|
||||||
for (const char* p = e.name; *p; p++) sig = sig * 31 + (uint8_t)*p;
|
for (const char* p = e.name; *p; p++) sig = sig * 31 + (uint8_t)*p;
|
||||||
}
|
}
|
||||||
// Ages are shown in minutes, so let the list re-render once a minute anyway.
|
// Ages are shown in minutes, so let the list re-render once a minute anyway.
|
||||||
@@ -201,9 +201,9 @@ static void nearbyText(const NearbyModel::Entry& e, uint32_t now, bool imperial,
|
|||||||
if (e.dist_km >= 0.0f) geo::fmtDist(t.right, sizeof(t.right), e.dist_km, imperial);
|
if (e.dist_km >= 0.0f) geo::fmtDist(t.right, sizeof(t.right), e.dist_km, imperial);
|
||||||
else if (age[0]) snprintf(t.right, sizeof(t.right), "%s", age);
|
else if (age[0]) snprintf(t.right, sizeof(t.right), "%s", age);
|
||||||
const char* kind = e.contact_idx >= 0 ? NearbyModel::typeName(e.type) : "not a contact";
|
const char* kind = e.contact_idx >= 0 ? NearbyModel::typeName(e.type) : "not a contact";
|
||||||
snprintf(t.sub, sizeof(t.sub), "%s%s%s%s", kind, e.is_live ? " - live" : "",
|
snprintf(t.sub, sizeof(t.sub), "%s%s%s%s", kind, e.is_sos ? " - SOS" : e.is_live ? " - live" : "",
|
||||||
(e.dist_km >= 0.0f && age[0]) ? " - " : "", (e.dist_km >= 0.0f && age[0]) ? age : "");
|
(e.dist_km >= 0.0f && age[0]) ? " - " : "", (e.dist_km >= 0.0f && age[0]) ? age : "");
|
||||||
t.right_col = e.is_live ? theme::OK : theme::TEXT_MUTED;
|
t.right_col = e.is_sos ? theme::FAIL : e.is_live ? theme::OK : theme::TEXT_MUTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A small arrow towards the node, drawn in the value label's left padding:
|
// A small arrow towards the node, drawn in the value label's left padding:
|
||||||
@@ -391,7 +391,8 @@ void UITask::refreshNode() {
|
|||||||
int o = 0;
|
int o = 0;
|
||||||
auto tag = [&](const char* t) { o += snprintf(status + o, sizeof(status) - o, "%s%s", o ? ", " : "", t); };
|
auto tag = [&](const char* t) { o += snprintf(status + o, sizeof(status) - o, "%s%s", o ? ", " : "", t); };
|
||||||
if (!known) tag("not a contact");
|
if (!known) tag("not a contact");
|
||||||
if (e.is_live) tag(e.live_verified ? "live position" : "live position (channel)");
|
if (e.is_sos) tag(e.live_verified ? "SOS position" : "SOS position (channel)");
|
||||||
|
else if (e.is_live) tag(e.live_verified ? "live position" : "live position (channel)");
|
||||||
if (e.fav) tag("favourite");
|
if (e.fav) tag("favourite");
|
||||||
if (status[0] >= 'a' && status[0] <= 'z') status[0] -= 'a' - 'A';
|
if (status[0] >= 'a' && status[0] <= 'z') status[0] -= 'a' - 'A';
|
||||||
int32_t lat, lon;
|
int32_t lat, lon;
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ private:
|
|||||||
void mapLoop();
|
void mapLoop();
|
||||||
void rebuildMapMarkers();
|
void rebuildMapMarkers();
|
||||||
void rebuildNodeMarkers();
|
void rebuildNodeMarkers();
|
||||||
void addMapMark(uint8_t kind, int idx, int32_t lat_e6, int32_t lon_e6, uint32_t col, const char* text);
|
void addMapMark(uint8_t kind, int idx, int32_t lat_e6, int32_t lon_e6, uint32_t col, const char* text, bool sos = false);
|
||||||
void mapPointToLatLon(int x, int y, int32_t& lat_e6, int32_t& lon_e6) const;
|
void mapPointToLatLon(int x, int y, int32_t& lat_e6, int32_t& lon_e6) const;
|
||||||
double mapCenterBias() const;
|
double mapCenterBias() const;
|
||||||
int mapBottomH() const;
|
int mapBottomH() const;
|
||||||
|
|||||||
Reference in New Issue
Block a user