mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-09 11:16:39 +00:00
refactor(ui-core): UiCore becomes MyMesh's Listener for ui-new
ui-new hands MyMesh the UI Core (UITaskBase::meshListener()). The Core applies the display filter, labels room posts, files history, keeps DM / channel / room unread and the queue count, and reports incoming messages and adverts as MessageArrived / AdvertHeard events. UITask now derives UITaskBase + UiCoreHost: it answers "is this conversation on screen" and keeps the selection after an insert, forwards not-yet-extracted parts (room login / admin, bot device actions, removal cleanup, shutdown), and turns events into alert / wake / sound, drained at the start and end of its loop(). Mesh callbacks no longer touch the display or buzzer. AbstractUITask is split into UITaskBase (lifecycle, link state, helpers) and the Listener glue ui-orig / ui-tiny keep using unchanged. Screenshot handler reaches ui_task directly instead of casting the Listener. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,7 @@ interleaved. Classified:
|
||||
|
||||
**Engines — logic living inside `UITask.cpp` / `UITask.h`**
|
||||
- Unread tracking — DM unread table ✅ (`ui-core/DmUnreadTable.h`), room unread (still in `UITask`).
|
||||
- Notifications — `showAlert`, `notify`, `SoundNotifier`, LED (`userLedHandler`), wake-on-message (`checkDisplayOn`, auto-off).
|
||||
- Notifications ✅ split: the Core decides *what* happened (`MessageArrived` with kind / DM sender / channel slot, `AdvertHeard`); the frontend decides *how* to show it (`showAlert`, `SoundNotifier`, vibration, LED, wake-on-message) — those are platform services.
|
||||
- Live share ✅ `ui-core/LiveShareEngine.h` — session timer, movement/heartbeat gate, send + scope guard, peers' `LiveTrackStore` + expiry.
|
||||
- Locator ✅ `ui-core/LocatorEngine.h` — active target, geofence state machine, proximity beeper.
|
||||
- Trail ✅ `ui-core/TrailEngine.h` — store, sampling, auto-pause, low-battery auto-save.
|
||||
@@ -70,11 +70,16 @@ fork's extensions (own-send mirroring, relay echoes, room login/admin replies,
|
||||
`[LOC]` shares, contact/channel removal, bot device actions,
|
||||
`requestShutdown`), each defaulting to a no-op.
|
||||
|
||||
Today `AbstractUITask` is the Listener and carries the glue `MyMesh` used to
|
||||
run for the UI (display filter, room-post labelling, notifications). In the
|
||||
target design `UiCore` becomes the Listener instead: it updates models/engines
|
||||
and emits events, and the frontends no longer implement it at all. This is the
|
||||
single point that makes both frontends receive identical behaviour.
|
||||
For `ui-new`, `UiCore` is the Listener (`UITaskBase::meshListener()` hands it
|
||||
to `MyMesh::setListener()`): it applies the display filter, labels room posts,
|
||||
files history, keeps unread counters, runs the engines and emits events. The
|
||||
frontend implements `UiCoreHost` (`ui-core/UiCoreHost.h`) for what the Core
|
||||
still asks of it synchronously: "is this conversation on screen", "keep the
|
||||
selection after an insert", and forwarding for not-yet-extracted parts (room
|
||||
login / admin sessions, bot device actions, prefs cleanup on contact/channel
|
||||
removal, shutdown). `ui-orig` / `ui-tiny` keep `AbstractUITask`, which is
|
||||
`UITaskBase` + the Listener glue, unchanged. This is the single point that
|
||||
makes both frontends receive identical behaviour.
|
||||
|
||||
### 2. Engines
|
||||
|
||||
@@ -151,8 +156,8 @@ checked in the sim plus on L1 hardware before the next one.
|
||||
|
||||
0. **Listener boundary** ✅ (upstream `MyMesh::Listener` ported; `MyMesh` has no UI calls left).
|
||||
1. **Skeleton** ✅. `examples/companion_radio/ui-core/` with `UiCore.h` (facade), `MessageHistory.h`, `DmUnreadTable.h`. Header-only for now, reached from `ui-new/UITask.cpp` by relative include, so none of the 66 variant `platformio.ini` files that build `ui-new` change. `UITask` heap-allocates one `UiCore` in `begin()` (before the screens, as `MessagesScreen` used to own the history on the heap); `MessagesScreen` binds to `core().history` by reference. When the Core grows real `.cpp` files, compile them through a unity `.cpp` inside each frontend directory.
|
||||
2. **Engines, one per commit.** Clock tools ✅ (also introduced `ui-core/UiEvents.h`, the Core → frontend event queue; `UITask::tickCore()` runs `UiCore::loop()` and drains it) → ping ✅ → course-over-ground ✅ → live share ✅ → locator ✅ → trail ✅ → notifications. `UITask` shrinks to screen management + drawing.
|
||||
3. **Flip the interface.** `UiCore` implements `AbstractUITask`; `ui-new`'s `UITask` becomes a frontend fed by events.
|
||||
2. **Engines, one per commit.** Clock tools ✅ (also introduced `ui-core/UiEvents.h`, the Core → frontend event queue; `UITask::tickCore()` runs `UiCore::loop()` and drains it) → ping ✅ → course-over-ground ✅ → live share ✅ → locator ✅ → trail ✅ → notifications ✅ (with step 3). `UITask` shrinks to screen management + drawing.
|
||||
3. **Flip the interface** ✅. `UiCore` is `MyMesh::Listener`; `ui-new`'s `UITask` derives `UITaskBase` + `UiCoreHost` and is fed by events, drained at the start (mesh-originated) and end (engines) of its `loop()`. Mesh callbacks no longer touch the display or buzzer directly.
|
||||
4. **Settings schema.** Convert `SettingsScreen` section by section.
|
||||
5. **`ui-lvgl` skeleton** for L2 + sim target: boot, home, message list/conversation, keyboard. Then screens by priority.
|
||||
6. Contacts/Nearby, Admin, Bot logic extraction as the LVGL screens for them are built.
|
||||
|
||||
@@ -26,19 +26,16 @@ enum class UIEventType {
|
||||
ack
|
||||
};
|
||||
|
||||
// Every on-device UI (ui-new / ui-orig / ui-tiny) is MyMesh's Listener. The
|
||||
// glue MyMesh used to run for the UI itself -- which message types show on
|
||||
// screen, how a room post is labelled, which notification plays -- lives here
|
||||
// now, so each UI variant keeps receiving the same newMsg()/addDMMsg()/
|
||||
// notify() calls it always did.
|
||||
class AbstractUITask : public MyMesh::Listener {
|
||||
// What every on-device UI (ui-new / ui-orig / ui-tiny) offers main.cpp and
|
||||
// MyMesh: lifecycle, the companion-link state, board/serial helpers, and the
|
||||
// object MyMesh should talk to (meshListener()).
|
||||
class UITaskBase {
|
||||
protected:
|
||||
mesh::MainBoard* _board;
|
||||
BaseSerialInterface* _serial;
|
||||
bool _connected;
|
||||
int _queue_len = 0; // last onQueueSizeChanged() -- newMsg()'s msgcount
|
||||
|
||||
AbstractUITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial) {
|
||||
UITaskBase(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial) {
|
||||
_connected = false;
|
||||
}
|
||||
|
||||
@@ -58,9 +55,6 @@ public:
|
||||
}
|
||||
bool hasConnection() const { return _connected; }
|
||||
virtual void onBLEDisconnected() {}
|
||||
// An end-to-end ACK (CRC) arrived for one of our sent messages — drives the
|
||||
// DM delivery-status marker. Default no-op for UIs that don't track it.
|
||||
virtual void onMsgAck(uint32_t ack_crc) { (void)ack_crc; }
|
||||
// True only when a BLE central is actually bonded/connected. On a dual
|
||||
// (BLE+USB) interface hasConnection() is always true (USB counts), so use
|
||||
// this for BLE-specific UI like the pairing-PIN prompt.
|
||||
@@ -72,8 +66,6 @@ public:
|
||||
bool isSerialEnabled() const { return _serial->isEnabled(); }
|
||||
void enableSerial() { _serial->enable(); }
|
||||
void disableSerial() { _serial->disable(); }
|
||||
virtual void msgRead(int msgcount) = 0;
|
||||
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type = 0, const uint8_t* pub_key = nullptr) = 0;
|
||||
virtual void notify(UIEventType t = UIEventType::none) = 0;
|
||||
// Single choke point for every controlled power-down (low-battery auto-off,
|
||||
// long-press power-off, and every board.reboot() caller too): flush
|
||||
@@ -84,6 +76,31 @@ public:
|
||||
virtual void shutdown(bool restart = false) = 0;
|
||||
virtual void loop() = 0;
|
||||
|
||||
// What main.cpp hands to MyMesh::setListener(): the UI itself (ui-orig /
|
||||
// ui-tiny, via AbstractUITask below) or the object it delegates mesh events
|
||||
// to (ui-new: the UI Core).
|
||||
virtual MyMesh::Listener* meshListener() = 0;
|
||||
};
|
||||
|
||||
// A UI that is MyMesh's Listener itself (ui-orig / ui-tiny). The glue MyMesh
|
||||
// used to run for the UI -- which message types show on screen, how a room post
|
||||
// is labelled, which notification plays -- lives here, so each of those UIs
|
||||
// keeps receiving the same newMsg()/addDMMsg()/notify() calls it always did.
|
||||
// (ui-new instead hands MyMesh the UI Core, which does the same job.)
|
||||
class AbstractUITask : public MyMesh::Listener, public UITaskBase {
|
||||
protected:
|
||||
int _queue_len = 0; // last onQueueSizeChanged() -- newMsg()'s msgcount
|
||||
|
||||
AbstractUITask(mesh::MainBoard* board, BaseSerialInterface* serial) : UITaskBase(board, serial) { }
|
||||
|
||||
public:
|
||||
// An end-to-end ACK (CRC) arrived for one of our sent messages — drives the
|
||||
// DM delivery-status marker. Default no-op for UIs that don't track it.
|
||||
virtual void onMsgAck(uint32_t ack_crc) { (void)ack_crc; }
|
||||
virtual void msgRead(int msgcount) = 0;
|
||||
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type = 0, const uint8_t* pub_key = nullptr) = 0;
|
||||
MyMesh::Listener* meshListener() override { return this; }
|
||||
|
||||
// ---- MyMesh::Listener ----
|
||||
void onQueueSizeChanged(int msgcount) override {
|
||||
_queue_len = msgcount;
|
||||
|
||||
@@ -3124,13 +3124,13 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
#ifdef ENABLE_SCREENSHOT
|
||||
void MyMesh::handleScreenshotRequest() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
UITask* ui_task = static_cast<UITask*>(getListener());
|
||||
if (!ui_task || !ui_task->hasDisplay()) {
|
||||
extern UITask ui_task; // main.cpp -- the Listener may be the UI Core, not UITask
|
||||
if (!ui_task.hasDisplay()) {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayDriver* display = ui_task->getDisplay();
|
||||
DisplayDriver* display = ui_task.getDisplay();
|
||||
if (!display) {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
return;
|
||||
|
||||
@@ -326,7 +326,7 @@ void setup() {
|
||||
if (disp && the_mesh.getNodePrefs())
|
||||
disp->setBrightness(the_mesh.getNodePrefs()->display_brightness);
|
||||
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
|
||||
the_mesh.setListener(&ui_task);
|
||||
the_mesh.setListener(ui_task.meshListener());
|
||||
#ifdef DISPLAY_HAS_BUSY_PUMP
|
||||
if (disp) disp->setBusyPumpFn(pumpRadioDuringDisplayBusyWait, nullptr);
|
||||
#endif
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
// UI Core: hardware-independent UI state and logic shared by every frontend
|
||||
// (ui-new today, ui-lvgl later). See docs/development/ui-core.md.
|
||||
//
|
||||
// The Core is MyMesh's Listener: it files incoming/outgoing messages into the
|
||||
// history, keeps the unread counters, runs the engines, and tells the frontend
|
||||
// what happened through `events` (drained from the frontend's loop()). The
|
||||
// frontend implements UiCoreHost for the few things the Core still asks of it.
|
||||
//
|
||||
// For now the Core is header-only and compiled as part of the frontend's single
|
||||
// translation unit (ui-new/UITask.cpp includes this file), so no platformio.ini
|
||||
// needs a new source filter or include path. It relies on the same globals the
|
||||
@@ -10,6 +15,7 @@
|
||||
#include "MessageHistory.h"
|
||||
#include "DmUnreadTable.h"
|
||||
#include "UiEvents.h"
|
||||
#include "UiCoreHost.h"
|
||||
#include "ClockEngine.h"
|
||||
#include "PingEngine.h"
|
||||
#include "CourseEngine.h"
|
||||
@@ -17,9 +23,10 @@
|
||||
#include "LocatorEngine.h"
|
||||
#include "TrailEngine.h"
|
||||
|
||||
class UiCore {
|
||||
class UiCore : public MyMesh::Listener {
|
||||
public:
|
||||
void begin(NodePrefs* prefs, SensorManager* sensors) {
|
||||
void begin(NodePrefs* prefs, SensorManager* sensors, UiCoreHost* host) {
|
||||
_host = host;
|
||||
clock.begin(prefs, &events);
|
||||
ping.begin(prefs);
|
||||
course.begin(sensors);
|
||||
@@ -51,9 +58,12 @@ public:
|
||||
MessageHistory history; // channel + DM rings, delivery state, channel unread
|
||||
DmUnreadTable dm_unread; // per-contact DM unread counters
|
||||
|
||||
// ── DM unread (clamped to what the DM ring still holds) ──────────────────
|
||||
void noteIncomingDM(const uint8_t* pub_key) { dm_unread.onIncoming(pub_key); }
|
||||
void afterDMInsert() { dm_unread.afterInsert(history); }
|
||||
// ── Unread ────────────────────────────────────────────────────────────────
|
||||
// Messages waiting in the companion-app offline queue (0 = the app synced).
|
||||
int msgCount() const { return _queue_len; }
|
||||
int roomUnread() const { return _room_unread; }
|
||||
void clearRoomUnread() { _room_unread = 0; }
|
||||
// DM unread, clamped to what the DM ring still holds.
|
||||
int dmUnreadTotal() const { return dm_unread.total(history); }
|
||||
uint8_t dmUnread(const uint8_t* pub_key) const { return dm_unread.get(history, pub_key); }
|
||||
bool dmUnreadOverflow(const uint8_t* pub_key) const { return dm_unread.overflow(pub_key); }
|
||||
@@ -62,10 +72,122 @@ public:
|
||||
void clearAllDMUnread() { dm_unread.clearAll(); }
|
||||
void reconcileDMUnread() { dm_unread.reconcile(history); }
|
||||
|
||||
// All messages synced to the app: nothing is unread any more (room unread
|
||||
// still lives in the frontend).
|
||||
void clearAllUnread() {
|
||||
dm_unread.clearAll();
|
||||
history.clearAllChannelUnread();
|
||||
// ════ MyMesh::Listener ════════════════════════════════════════════════════
|
||||
|
||||
void onQueueSizeChanged(int msgcount) override {
|
||||
_queue_len = msgcount;
|
||||
if (msgcount == 0) { // the app drained the queue: nothing is unread any more
|
||||
_room_unread = 0;
|
||||
dm_unread.clearAll();
|
||||
history.clearAllChannelUnread();
|
||||
}
|
||||
}
|
||||
|
||||
void onACKRecv(uint32_t ack_crc) override { history.markDmDelivered(ack_crc); }
|
||||
void onAdvertHeard(bool was_flood) override { events.push(UiEventType::AdvertHeard, nullptr, was_flood); }
|
||||
bool requestShutdown(bool restart) override { _host->shutdown(restart); return true; }
|
||||
|
||||
void onMessageRecv(mesh::Packet *pkt, const ContactInfo &from, uint8_t txt_type, uint32_t sender_timestamp,
|
||||
const char* text) override {
|
||||
onMessageRecvEx(pkt, from, txt_type, sender_timestamp, nullptr, 0, text);
|
||||
}
|
||||
void onMessageRecvEx(mesh::Packet *pkt, const ContactInfo &from, uint8_t txt_type, uint32_t sender_timestamp,
|
||||
const uint8_t* extra, int extra_len, const char* text) override {
|
||||
// we only want to show text messages on display, not cli data
|
||||
if (!(txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN)) return;
|
||||
if (from.type == ADV_TYPE_ROOM && _room_unread < _queue_len) _room_unread++;
|
||||
if (from.type == ADV_TYPE_CHAT) dm_unread.onIncoming(from.id.pub_key); // before the ring insert below
|
||||
UiEvent& ev = pushMessageArrived(from.type == ADV_TYPE_ROOM ? UIEventType::roomMessage : UIEventType::contactMessage,
|
||||
from.name, -1);
|
||||
if (from.type == ADV_TYPE_CHAT) { memcpy(ev.key, from.id.pub_key, 4); ev.flag = true; }
|
||||
// Add to the on-device conversation history. Room servers (ADV_TYPE_ROOM) are
|
||||
// viewed through the same history list as chat contacts (keyed by the server's
|
||||
// pubkey), so their posts must be stored too — otherwise an incoming room
|
||||
// message fires the notification and reaches the app via the offline queue but
|
||||
// never shows when the room is opened directly on the device.
|
||||
if (from.type == ADV_TYPE_CHAT) {
|
||||
addDMMsg(from.id.pub_key, false, text, sender_timestamp, 0, 0, 0, pkt->path, (uint8_t)pkt->path_len);
|
||||
} else if (from.type == ADV_TYPE_ROOM) {
|
||||
// A room carries many guests, so prefix the post with its author so the UI
|
||||
// can attribute each line. The signed message's `extra` holds the sender's
|
||||
// pubkey prefix; resolve it to a contact name, falling back to a short hex.
|
||||
char labeled[MAX_TEXT_LEN + 40]; // room text + "Sender: " (history store truncates)
|
||||
if (extra && extra_len >= 4) {
|
||||
ContactInfo* sc = the_mesh.lookupContactByPubKey(extra, extra_len);
|
||||
if (sc && sc->name[0])
|
||||
snprintf(labeled, sizeof(labeled), "%s: %s", sc->name, text);
|
||||
else
|
||||
snprintf(labeled, sizeof(labeled), "%02X%02X: %s", extra[0], extra[1], text);
|
||||
} else {
|
||||
snprintf(labeled, sizeof(labeled), "%s", text);
|
||||
}
|
||||
addDMMsg(from.id.pub_key, false, labeled, sender_timestamp, 0, 0, 0, pkt->path, (uint8_t)pkt->path_len);
|
||||
}
|
||||
}
|
||||
|
||||
// Upstream-shaped entry point without the channel slot: MyMesh itself always
|
||||
// calls the Ex variant below, so this only serves a caller that doesn't know
|
||||
// the slot -- notify, but there's no history ring to file it under.
|
||||
void onChannelMessageRecv(mesh::Packet *pkt, ChannelDetails& channel_details, const char* text) override {
|
||||
pushMessageArrived(UIEventType::channelMessage, channel_details.name, -1);
|
||||
}
|
||||
void onChannelMessageRecvEx(mesh::Packet *pkt, uint8_t channel_idx, ChannelDetails& channel_details,
|
||||
uint32_t timestamp, const char* text) override {
|
||||
addChannelMsg(channel_idx, text, timestamp, pkt->path, (uint8_t)pkt->path_len);
|
||||
pushMessageArrived(UIEventType::channelMessage, channel_details.name, channel_idx);
|
||||
}
|
||||
|
||||
// Also the entry point for our own sends mirrored from the app / the bot
|
||||
// (own_message) and for on-device composes. Returns the ring position.
|
||||
int addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0,
|
||||
const uint8_t* path = nullptr, uint8_t path_len = 0,
|
||||
bool own_message = false) override {
|
||||
bool viewing = _host->isViewingChannel(channel_idx);
|
||||
int pos = history.addChannelMsg(channel_idx, text, viewing, timestamp, path, path_len, own_message);
|
||||
if (viewing && pos >= 0) _host->onViewedHistoryGrew(true);
|
||||
return pos;
|
||||
}
|
||||
void armChannelRelay(int pos, uint32_t seq) override { history.armChannelRelay(pos, seq); }
|
||||
void onChannelRelayed(uint32_t seq, const uint8_t* repeater_hash = nullptr, uint8_t hash_size = 0) override {
|
||||
history.markChannelRelayed(seq, repeater_hash, hash_size);
|
||||
}
|
||||
|
||||
void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0,
|
||||
uint32_t ack_tag = 0, uint32_t ack_deadline_ms = 0, uint8_t resends = 0,
|
||||
const uint8_t* path = nullptr, uint8_t path_len = 0) override {
|
||||
bool viewing = _host->isViewingDM(pub_key);
|
||||
history.addDMMsg(pub_key, outgoing, text, sender_timestamp, ack_tag, ack_deadline_ms, resends, path, path_len);
|
||||
if (viewing) _host->onViewedHistoryGrew(false);
|
||||
dm_unread.afterInsert(history);
|
||||
}
|
||||
|
||||
void onSharedLocation(const uint8_t* pub_key, const char* name, int32_t lat_1e6, int32_t lon_1e6,
|
||||
uint32_t ts, bool verified) override {
|
||||
live_share.onSharedLocation(pub_key, name, lat_1e6, lon_1e6, ts, verified);
|
||||
}
|
||||
|
||||
// Not yet extracted -- the frontend still owns these.
|
||||
void onRoomLoginResult(const uint8_t* pub_key, bool success, uint8_t permissions) override {
|
||||
_host->onRoomLoginResult(pub_key, success, permissions);
|
||||
}
|
||||
void onAdminReply(const uint8_t* pub_key, const char* text) override { _host->onAdminReply(pub_key, text); }
|
||||
void onContactRemoved(const uint8_t* pub_key) override { _host->onContactRemoved(pub_key); }
|
||||
void onChannelRemoved(uint8_t channel_idx) override { _host->onChannelRemoved(channel_idx); }
|
||||
void botSetGPS(bool on) override { _host->botSetGPS(on); }
|
||||
void botBuzz(int seconds) override { _host->botBuzz(seconds); }
|
||||
bool botSetGPIO(int idx, bool on) override { return _host->botSetGPIO(idx, on); }
|
||||
bool botGetGPIO(int idx, bool& is_output, bool& value) override { return _host->botGetGPIO(idx, is_output, value); }
|
||||
bool botGetGPIOAnalog(int idx, int& millivolts) override { return _host->botGetGPIOAnalog(idx, millivolts); }
|
||||
|
||||
private:
|
||||
UiEvent& pushMessageArrived(UIEventType kind, const char* name, int channel_idx) {
|
||||
UiEvent& ev = events.push(UiEventType::MessageArrived, name);
|
||||
ev.kind = kind;
|
||||
ev.idx = (int16_t)channel_idx;
|
||||
return ev;
|
||||
}
|
||||
|
||||
UiCoreHost* _host = nullptr;
|
||||
int _queue_len = 0; // last onQueueSizeChanged()
|
||||
int _room_unread = 0;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
// What the UI Core needs from the frontend that hosts it. The Core is MyMesh's
|
||||
// Listener; everything it can decide on its own (history, unread, engines) it
|
||||
// does, and reports to the frontend through UiEventQueue. This interface is the
|
||||
// remainder -- calls that must stay synchronous with mesh processing, or whose
|
||||
// logic still lives in the frontend until its extraction step
|
||||
// (docs/development/ui-core.md). None of these may draw or block.
|
||||
|
||||
class UiCoreHost {
|
||||
public:
|
||||
virtual ~UiCoreHost() {}
|
||||
|
||||
// ── View state consulted while filing an incoming message ────────────────
|
||||
// "Viewing" = that conversation is open on screen, so the message isn't
|
||||
// counted unread. After a message is filed into a conversation being viewed,
|
||||
// onViewedHistoryGrew() lets the view keep its selection on the same entry
|
||||
// (ring entries are numbered newest-first).
|
||||
virtual bool isViewingChannel(uint8_t channel_idx) { (void)channel_idx; return false; }
|
||||
virtual bool isViewingDM(const uint8_t* pub_key) { (void)pub_key; return false; }
|
||||
virtual void onViewedHistoryGrew(bool channel) { (void)channel; }
|
||||
|
||||
// ── Not yet extracted (screen sessions, device controls, prefs cleanup) ──
|
||||
virtual void onRoomLoginResult(const uint8_t* pub_key, bool success, uint8_t permissions) {}
|
||||
virtual void onAdminReply(const uint8_t* pub_key, const char* text) {}
|
||||
virtual void onContactRemoved(const uint8_t* pub_key) {}
|
||||
virtual void onChannelRemoved(uint8_t channel_idx) {}
|
||||
virtual void botSetGPS(bool on) {}
|
||||
virtual void botBuzz(int seconds) {}
|
||||
virtual bool botSetGPIO(int idx, bool on) { return false; }
|
||||
virtual bool botGetGPIO(int idx, bool& is_output, bool& value) { return false; }
|
||||
virtual bool botGetGPIOAnalog(int idx, int& millivolts) { return false; }
|
||||
|
||||
// Controlled power-down / restart (flush state first). See
|
||||
// AbstractUITask::shutdown().
|
||||
virtual void shutdown(bool restart) = 0;
|
||||
};
|
||||
@@ -14,32 +14,44 @@ enum class UiEventType : uint8_t {
|
||||
LiveShareEnded, // live-share session reached its duration and switched itself off
|
||||
LocatorCrossed, // geofence crossing: show `text`; flag = arrived (else left)
|
||||
LocatorBeep, // one proximity-beeper tick
|
||||
MessageArrived, // incoming text: text = sender / channel name; `kind` = contact /
|
||||
// room / channel message; `key` (flag = valid) = DM sender prefix
|
||||
// for per-contact sounds; `idx` = channel slot (-1 = unknown)
|
||||
AdvertHeard, // an advert was heard; flag = flood (else zero-hop)
|
||||
};
|
||||
|
||||
struct UiEvent {
|
||||
UiEventType type;
|
||||
bool flag;
|
||||
UIEventType kind; // MessageArrived: which notification (AbstractUITask.h)
|
||||
int16_t idx;
|
||||
uint8_t key[4];
|
||||
char text[24];
|
||||
};
|
||||
|
||||
class UiEventQueue {
|
||||
public:
|
||||
static const int SIZE = 8;
|
||||
static const int SIZE = 12;
|
||||
|
||||
UiEventQueue() : _head(0), _count(0) {}
|
||||
|
||||
void push(UiEventType type, const char* text = nullptr, bool flag = false) {
|
||||
// Returns the queued event so a caller can fill the optional fields.
|
||||
UiEvent& push(UiEventType type, const char* text = nullptr, bool flag = false) {
|
||||
int pos;
|
||||
if (_count < SIZE) { pos = (_head + _count) % SIZE; _count++; }
|
||||
else { pos = _head; _head = (_head + 1) % SIZE; } // drop oldest
|
||||
_q[pos].type = type;
|
||||
_q[pos].flag = flag;
|
||||
_q[pos].kind = UIEventType::none;
|
||||
_q[pos].idx = -1;
|
||||
memset(_q[pos].key, 0, sizeof(_q[pos].key));
|
||||
if (text) {
|
||||
strncpy(_q[pos].text, text, sizeof(_q[pos].text) - 1);
|
||||
_q[pos].text[sizeof(_q[pos].text) - 1] = '\0';
|
||||
} else {
|
||||
_q[pos].text[0] = '\0';
|
||||
}
|
||||
return _q[pos];
|
||||
}
|
||||
|
||||
bool pop(UiEvent& out) {
|
||||
|
||||
@@ -553,10 +553,10 @@ class MessagesScreen : public UIScreen {
|
||||
if (ok && _sending_to_channel) {
|
||||
_hist_sel = 0;
|
||||
_hist_scroll = 0;
|
||||
_phase = CHANNEL_HIST; // set before addChannelMsg so viewing=true, no unread bump
|
||||
_phase = CHANNEL_HIST; // set before filing so the Core sees viewing=true, no unread bump
|
||||
char entry[sizeof(ChHistEntry::text)];
|
||||
snprintf(entry, sizeof(entry), "Me: %s", msg);
|
||||
int pos = addChannelMsg(_sel_channel_idx, entry);
|
||||
int pos = _task->core().addChannelMsg(_sel_channel_idx, entry);
|
||||
// Arm the "relayed into mesh" marker on this exact entry — MyMesh tracked
|
||||
// the flood it just originated and reports a heard repeater echo by seq.
|
||||
if (pos >= 0) _history.armChannelRelay(pos, the_mesh.lastChannelRelaySeq());
|
||||
@@ -967,37 +967,24 @@ public:
|
||||
// (suppressed while picking a channel for the bot).
|
||||
int channelPickTotal() const { return _num_channels + (_pick_bot_channel ? 0 : 1); }
|
||||
|
||||
// Public entry points (routed from MyMesh / the bot via UITask) — thin
|
||||
// forwarders to the history store. addChannelMsg computes the "viewing" flag
|
||||
// (a phase-machine fact the store can't see) and returns the ring position so
|
||||
// the outgoing path can attach a relay seq to that exact entry.
|
||||
int addChannelMsg(uint8_t ch_idx, const char* text, uint32_t timestamp = 0,
|
||||
const uint8_t* path = nullptr, uint8_t path_len = 0,
|
||||
bool own_message = false) {
|
||||
bool viewing = (_phase == CHANNEL_HIST && _sel_channel_idx == (int)ch_idx);
|
||||
int pos = _history.addChannelMsg(ch_idx, text, viewing, timestamp, path, path_len, own_message);
|
||||
// Ring entries are numbered newest-first (0 == newest), so a new insert
|
||||
// shifts every older message's index up by one. If the user has scrolled
|
||||
// up to an older message (_hist_sel > 0), re-point the selection at that
|
||||
// same message instead of silently relabeling a different one in under
|
||||
// them. At _hist_sel <= 0 (already at newest, or -1 == compose button
|
||||
// focused) there's nothing to preserve.
|
||||
if (viewing && _hist_sel > 0) { _hist_sel++; _hist_scroll++; }
|
||||
return pos;
|
||||
// View state the UI Core consults while filing a message (via UiCoreHost):
|
||||
// a conversation open on screen doesn't count the message unread, and after
|
||||
// the insert the selection is kept on the same message. Ring entries are
|
||||
// numbered newest-first (0 == newest), so a new insert shifts every older
|
||||
// message's index up by one; if the user has scrolled up to an older message
|
||||
// (sel > 0), re-point the selection at that same message instead of silently
|
||||
// relabeling a different one in under them. At sel <= 0 (already at newest,
|
||||
// or -1 == compose button focused) there's nothing to preserve.
|
||||
bool isViewingChannel(uint8_t ch_idx) const {
|
||||
return _phase == CHANNEL_HIST && _sel_channel_idx == (int)ch_idx;
|
||||
}
|
||||
void markChannelRelayed(uint32_t seq, const uint8_t* repeater_hash = nullptr, uint8_t hash_size = 0) {
|
||||
_history.markChannelRelayed(seq, repeater_hash, hash_size);
|
||||
bool isViewingDM(const uint8_t* pub_key) const {
|
||||
return _phase == DM_HIST && memcmp(_sel_contact.id.pub_key, pub_key, 4) == 0;
|
||||
}
|
||||
void armChannelRelay(int pos, uint32_t seq) { _history.armChannelRelay(pos, seq); }
|
||||
void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text,
|
||||
uint32_t sender_timestamp = 0, uint32_t ack_tag = 0,
|
||||
uint32_t ack_deadline_ms = 0, uint8_t resends = 0,
|
||||
const uint8_t* path = nullptr, uint8_t path_len = 0) {
|
||||
bool viewing = (_phase == DM_HIST && memcmp(_sel_contact.id.pub_key, pub_key, 4) == 0);
|
||||
_history.addDMMsg(pub_key, outgoing, text, sender_timestamp, ack_tag, ack_deadline_ms, resends, path, path_len);
|
||||
if (viewing && _dm_hist_sel > 0) { _dm_hist_sel++; _dm_hist_scroll++; } // see addChannelMsg
|
||||
void onViewedHistoryGrew(bool channel) {
|
||||
if (channel) { if (_hist_sel > 0) { _hist_sel++; _hist_scroll++; } }
|
||||
else { if (_dm_hist_sel > 0) { _dm_hist_sel++; _dm_hist_scroll++; } }
|
||||
}
|
||||
void markDmDelivered(uint32_t ack_crc) { _history.markDmDelivered(ack_crc); }
|
||||
|
||||
// Rooms successfully logged in to this power-on session. RAM-only — the
|
||||
// server's ACL (see ClientACL) is the real permission store and survives
|
||||
@@ -1066,7 +1053,7 @@ public:
|
||||
}
|
||||
|
||||
// Result of an on-device sendRoomLogin() (MyMesh::onContactResponse(), routed
|
||||
// via AbstractUITask::onRoomLoginResult()). Surfaces as a transient alert.
|
||||
// via the UI Core and UITask::onRoomLoginResult()). Surfaces as a transient alert.
|
||||
void onRoomLoginResult(const uint8_t* pub_key, bool success, uint8_t permissions) {
|
||||
(void)permissions;
|
||||
if (success) {
|
||||
|
||||
@@ -1635,7 +1635,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
|
||||
ui_started_at = millis();
|
||||
_alert_expiry = 0;
|
||||
_batt_mv = AbstractUITask::getBattMilliVolts(); // seed EMA with first reading
|
||||
_batt_mv = UITaskBase::getBattMilliVolts(); // seed EMA with first reading
|
||||
|
||||
// Load persisted waypoints (table survives reboots, unlike the RAM trail).
|
||||
{
|
||||
@@ -1647,7 +1647,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
}
|
||||
|
||||
_core = new UiCore(); // before any screen -- MessagesScreen binds to its history
|
||||
_core->begin(node_prefs, sensors);
|
||||
_core->begin(node_prefs, sensors, this);
|
||||
splash = new SplashScreen(this);
|
||||
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs);
|
||||
syncLockToHome(); // booted locked (e.g. cover closed) → home starts on the LOCK page
|
||||
@@ -1730,6 +1730,12 @@ void UITask::dismissRing() { stopMelody(); _core->clock.dismi
|
||||
|
||||
void UITask::tickCore() {
|
||||
_core->loop();
|
||||
drainCoreEvents();
|
||||
// Repeat the ring melody until dismissed or the ring window elapses.
|
||||
if (_core->clock.isRinging() && !isMelodyPlaying()) playMelody(CLOCK_ALARM_MELODY);
|
||||
}
|
||||
|
||||
void UITask::drainCoreEvents() {
|
||||
UiEvent ev;
|
||||
while (_core->events.pop(ev)) {
|
||||
switch (ev.type) {
|
||||
@@ -1753,12 +1759,16 @@ void UITask::tickCore() {
|
||||
case UiEventType::LocatorBeep:
|
||||
playMelody("locp:d=32,o=7,b=200:c");
|
||||
break;
|
||||
case UiEventType::MessageArrived:
|
||||
onMessageArrived(ev);
|
||||
break;
|
||||
case UiEventType::AdvertHeard:
|
||||
notify(ev.flag ? UIEventType::advertReceivedFlood : UIEventType::advertReceivedZeroHop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Repeat the ring melody until dismissed or the ring window elapses.
|
||||
if (_core->clock.isRinging() && !isMelodyPlaying()) playMelody(CLOCK_ALARM_MELODY);
|
||||
}
|
||||
|
||||
// Ringtone takes a slot argument that onShow() can't carry — pass it after the
|
||||
@@ -1857,15 +1867,22 @@ void UITask::pickBotRoomTarget() {
|
||||
setCurrScreen(messages_screen);
|
||||
}
|
||||
|
||||
int UITask::addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp,
|
||||
const uint8_t* path, uint8_t path_len, bool own_message) {
|
||||
_last_notif_ch_idx = (int)channel_idx;
|
||||
return ((MessagesScreen*)messages_screen)->addChannelMsg(channel_idx, text, timestamp, path, path_len, own_message);
|
||||
// ── UI Core wiring ──────────────────────────────────────────────────────────
|
||||
MyMesh::Listener* UITask::meshListener() { return _core; }
|
||||
|
||||
bool UITask::isViewingChannel(uint8_t channel_idx) {
|
||||
return ((MessagesScreen*)messages_screen)->isViewingChannel(channel_idx);
|
||||
}
|
||||
bool UITask::isViewingDM(const uint8_t* pub_key) {
|
||||
return ((MessagesScreen*)messages_screen)->isViewingDM(pub_key);
|
||||
}
|
||||
void UITask::onViewedHistoryGrew(bool channel) {
|
||||
((MessagesScreen*)messages_screen)->onViewedHistoryGrew(channel);
|
||||
}
|
||||
|
||||
void UITask::armChannelRelay(int pos, uint32_t seq) {
|
||||
((MessagesScreen*)messages_screen)->armChannelRelay(pos, seq);
|
||||
}
|
||||
int UITask::getMsgCount() const { return _core->msgCount(); }
|
||||
int UITask::getRoomUnreadCount() const { return _core->roomUnread(); }
|
||||
void UITask::clearRoomUnread() { _core->clearRoomUnread(); }
|
||||
|
||||
int UITask::getChannelUnreadCount() const {
|
||||
return ((MessagesScreen*)messages_screen)->getTotalChannelUnread();
|
||||
@@ -1887,14 +1904,6 @@ bool UITask::getAnyUnreadOverflow() const {
|
||||
return getAnyChannelUnreadOverflow() || getAnyDMUnreadOverflow();
|
||||
}
|
||||
|
||||
void UITask::onMsgAck(uint32_t ack_crc) {
|
||||
((MessagesScreen*)messages_screen)->markDmDelivered(ack_crc);
|
||||
}
|
||||
|
||||
void UITask::onChannelRelayed(uint32_t seq, const uint8_t* repeater_hash, uint8_t hash_size) {
|
||||
((MessagesScreen*)messages_screen)->markChannelRelayed(seq, repeater_hash, hash_size);
|
||||
}
|
||||
|
||||
void UITask::onRoomLoginResult(const uint8_t* pub_key, bool success, uint8_t permissions) {
|
||||
// Only one on-device login can be in flight at a time (MyMesh::ui_pending_login
|
||||
// is a single slot) -- route the result to whichever of the two screens that
|
||||
@@ -1913,13 +1922,6 @@ void UITask::onAdminReply(const uint8_t* pub_key, const char* text) {
|
||||
_next_refresh = 0; // same reasoning as onRoomLoginResult above
|
||||
}
|
||||
|
||||
void UITask::addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp,
|
||||
uint32_t ack_tag, uint32_t ack_deadline_ms, uint8_t resends,
|
||||
const uint8_t* path, uint8_t path_len) {
|
||||
((MessagesScreen*)messages_screen)->addDMMsg(pub_key, outgoing, text, sender_timestamp, ack_tag, ack_deadline_ms, resends, path, path_len);
|
||||
_core->afterDMInsert();
|
||||
}
|
||||
|
||||
int UITask::getDMUnreadTotal() const { return _core->dmUnreadTotal(); }
|
||||
uint8_t UITask::getDMUnread(const uint8_t* pub_key) const { return _core->dmUnread(pub_key); }
|
||||
bool UITask::getDMUnreadOverflow(const uint8_t* pub_key) const { return _core->dmUnreadOverflow(pub_key); }
|
||||
@@ -1973,25 +1975,17 @@ void UITask::notify(UIEventType t) {
|
||||
}
|
||||
|
||||
|
||||
void UITask::msgRead(int msgcount) {
|
||||
_msgcount = msgcount;
|
||||
if (msgcount == 0) {
|
||||
_room_unread = 0;
|
||||
_core->clearAllUnread();
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type, const uint8_t* pub_key) {
|
||||
_msgcount = msgcount;
|
||||
if (contact_type == ADV_TYPE_ROOM && _room_unread < _msgcount) _room_unread++;
|
||||
if (contact_type == ADV_TYPE_CHAT && pub_key != nullptr) {
|
||||
memcpy(_last_notif_dm_prefix, pub_key, 4);
|
||||
// Incoming message (UiEventType::MessageArrived, filed by the Core already):
|
||||
// alert overlay, wake the display, and the per-contact / per-channel sound.
|
||||
void UITask::onMessageArrived(const UiEvent& ev) {
|
||||
if (ev.kind == UIEventType::contactMessage && ev.flag) {
|
||||
memcpy(_last_notif_dm_prefix, ev.key, 4);
|
||||
_last_notif_dm_valid = true;
|
||||
_core->noteIncomingDM(pub_key); // eviction/overflow is checked in addDMMsg(), after the ring insert
|
||||
}
|
||||
if (ev.kind == UIEventType::channelMessage) _last_notif_ch_idx = ev.idx;
|
||||
|
||||
char alert_buf[80];
|
||||
snprintf(alert_buf, sizeof(alert_buf), "Msg: %.20s", from_name);
|
||||
snprintf(alert_buf, sizeof(alert_buf), "Msg: %.20s", ev.text);
|
||||
showAlert(alert_buf, 3000);
|
||||
|
||||
if (_display != NULL && !_locked) {
|
||||
@@ -2005,6 +1999,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
_next_refresh = 100;
|
||||
}
|
||||
}
|
||||
notify(ev.kind);
|
||||
}
|
||||
|
||||
void UITask::userLedHandler() {
|
||||
@@ -2013,7 +2008,7 @@ void UITask::userLedHandler() {
|
||||
if (cur_time > next_led_change) {
|
||||
if (led_state == 0) {
|
||||
led_state = 1;
|
||||
if (_msgcount > 0) {
|
||||
if (_core->msgCount() > 0) {
|
||||
last_led_increment = LED_ON_MSG_MILLIS;
|
||||
} else {
|
||||
last_led_increment = LED_ON_MILLIS;
|
||||
@@ -2488,7 +2483,8 @@ void UITask::pollHallSensor() {
|
||||
}
|
||||
|
||||
void UITask::loop() {
|
||||
pollConnection(); // BLE link state -> hasConnection() (see AbstractUITask)
|
||||
pollConnection(); // BLE link state -> hasConnection() (see UITaskBase)
|
||||
drainCoreEvents(); // react to what the Core filed during mesh processing (alerts, wake, sounds)
|
||||
// Background delivery: resend pending on-device DMs whose ACK timed out, and
|
||||
// finalise the ✗ marker — runs regardless of which screen is active.
|
||||
((MessagesScreen*)messages_screen)->tickDmResends();
|
||||
@@ -2807,7 +2803,7 @@ void UITask::loop() {
|
||||
#endif
|
||||
|
||||
if ((int32_t)(millis() - next_batt_chck) >= 0) {
|
||||
uint16_t raw = AbstractUITask::getBattMilliVolts();
|
||||
uint16_t raw = UITaskBase::getBattMilliVolts();
|
||||
if (raw > 0) {
|
||||
#ifdef SIM_PLATFORM
|
||||
// SimMainBoard::getBattMilliVolts() returns exactly whatever value the
|
||||
@@ -3017,12 +3013,6 @@ TrailStore& UITask::trail() { return _core->trail.store(); }
|
||||
bool UITask::currentCourse(int& deg_out) const { return _core->course.currentCourse(deg_out); }
|
||||
bool UITask::currentLocation(int32_t& lat, int32_t& lon) const { return _core->course.currentLocation(lat, lon); }
|
||||
|
||||
void UITask::onSharedLocation(const uint8_t* pub_key, const char* name,
|
||||
int32_t lat_1e6, int32_t lon_1e6,
|
||||
uint32_t ts, bool verified) {
|
||||
_core->live_share.onSharedLocation(pub_key, name, lat_1e6, lon_1e6, ts, verified);
|
||||
}
|
||||
|
||||
bool UITask::sendLocationShare(int32_t lat, int32_t lon) { return _core->live_share.send(lat, lon); }
|
||||
void UITask::restartLocShareSession() { _core->live_share.restartSession(); }
|
||||
void UITask::restartLocShareClock() { _core->live_share.restartClock(); }
|
||||
|
||||
@@ -41,10 +41,14 @@
|
||||
#include "../Waypoint.h"
|
||||
#include "../LiveTrack.h"
|
||||
#include "KeyboardWidget.h"
|
||||
#include "../ui-core/UiCoreHost.h"
|
||||
|
||||
class UiCore;
|
||||
struct UiEvent;
|
||||
|
||||
class UITask : public AbstractUITask {
|
||||
// ui-new is the lite frontend of the UI Core: MyMesh talks to the Core (see
|
||||
// meshListener()), the Core calls back through UiCoreHost and UiEventQueue.
|
||||
class UITask : public UITaskBase, public UiCoreHost {
|
||||
DisplayDriver* _display;
|
||||
SensorManager* _sensors;
|
||||
#ifdef PIN_BUZZER
|
||||
@@ -68,8 +72,6 @@ class UITask : public AbstractUITask {
|
||||
char _bot_buzz_buf[400];
|
||||
KeyboardWidget _kb; // shared across all screens — only one active at a time
|
||||
unsigned long _alert_expiry;
|
||||
int _msgcount;
|
||||
int _room_unread;
|
||||
int _last_notif_ch_idx;
|
||||
uint8_t _last_notif_dm_prefix[4];
|
||||
bool _last_notif_dm_valid;
|
||||
@@ -121,6 +123,8 @@ class UITask : public AbstractUITask {
|
||||
// Runs the UI Core engines and reacts to their events (alert overlay, buzzer,
|
||||
// display wake). Driven from loop() regardless of the current screen.
|
||||
void tickCore();
|
||||
void drainCoreEvents();
|
||||
void onMessageArrived(const UiEvent& ev); // alert + wake + sound for an incoming message
|
||||
|
||||
|
||||
|
||||
@@ -228,11 +232,10 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL), _node_prefs(NULL) {
|
||||
UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : UITaskBase(board, serial), _display(NULL), _sensors(NULL), _node_prefs(NULL) {
|
||||
next_batt_chck = _next_refresh = 0;
|
||||
ui_started_at = 0;
|
||||
_batt_mv = 0;
|
||||
_msgcount = _room_unread = 0;
|
||||
_locked = false;
|
||||
_lock_wake_until = 0;
|
||||
_lock_seq_count = 0; _lock_seq_ms = 0; _lock_seq_used = false;
|
||||
@@ -247,7 +250,7 @@ public:
|
||||
NodePrefs* getNodePrefs() const { return _node_prefs; }
|
||||
// Global metric/imperial preference for distance/speed display.
|
||||
bool useImperial() const { return _node_prefs && _node_prefs->units_imperial; }
|
||||
uint16_t getBattMilliVolts() const { return _batt_mv > 0 ? _batt_mv : AbstractUITask::getBattMilliVolts(); }
|
||||
uint16_t getBattMilliVolts() const { return _batt_mv > 0 ? _batt_mv : UITaskBase::getBattMilliVolts(); }
|
||||
void gotoHomeScreen() { setCurrScreen(home); }
|
||||
void gotoSettingsScreen();
|
||||
void gotoMessagesScreen();
|
||||
@@ -357,25 +360,21 @@ public:
|
||||
void stopMelody();
|
||||
bool isMelodyPlaying();
|
||||
void showAlert(const char* text, int duration_millis);
|
||||
int addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0,
|
||||
const uint8_t* path = nullptr, uint8_t path_len = 0,
|
||||
bool own_message = false) override;
|
||||
void armChannelRelay(int pos, uint32_t seq) override;
|
||||
void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0,
|
||||
uint32_t ack_tag = 0, uint32_t ack_deadline_ms = 0, uint8_t resends = 0,
|
||||
const uint8_t* path = nullptr, uint8_t path_len = 0) override;
|
||||
void onMsgAck(uint32_t ack_crc) override;
|
||||
void onChannelRelayed(uint32_t seq, const uint8_t* repeater_hash = nullptr, uint8_t hash_size = 0) override;
|
||||
MyMesh::Listener* meshListener() override; // MyMesh talks to the UI Core, not to UITask
|
||||
// UiCoreHost
|
||||
bool isViewingChannel(uint8_t channel_idx) override;
|
||||
bool isViewingDM(const uint8_t* pub_key) override;
|
||||
void onViewedHistoryGrew(bool channel) override;
|
||||
void onRoomLoginResult(const uint8_t* pub_key, bool success, uint8_t permissions) override;
|
||||
void onAdminReply(const uint8_t* pub_key, const char* text) override;
|
||||
int getDMUnreadTotal() const;
|
||||
int getMsgCount() const { return _msgcount; }
|
||||
int getMsgCount() const;
|
||||
int getChannelUnreadCount() const;
|
||||
uint8_t getChannelUnread(uint8_t channel_idx) const;
|
||||
bool getChannelUnreadOverflow(uint8_t channel_idx) const;
|
||||
bool getAnyChannelUnreadOverflow() const;
|
||||
int getRoomUnreadCount() const { return _room_unread; }
|
||||
void clearRoomUnread() { _room_unread = 0; }
|
||||
int getRoomUnreadCount() const;
|
||||
void clearRoomUnread();
|
||||
// Clamped to the DM ring's actual occupancy for this contact (UiCore).
|
||||
uint8_t getDMUnread(const uint8_t* pub_key) const;
|
||||
bool getDMUnreadOverflow(const uint8_t* pub_key) const;
|
||||
@@ -512,13 +511,8 @@ public:
|
||||
}
|
||||
|
||||
|
||||
// from AbstractUITask
|
||||
void msgRead(int msgcount) override;
|
||||
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type = 0, const uint8_t* pub_key = nullptr) override;
|
||||
// from UITaskBase
|
||||
void notify(UIEventType t = UIEventType::none) override;
|
||||
void onSharedLocation(const uint8_t* pub_key, const char* name,
|
||||
int32_t lat_1e6, int32_t lon_1e6,
|
||||
uint32_t ts, bool verified) override;
|
||||
void loop() override;
|
||||
// Send one [LOC] message to the configured live-share target. Returns false
|
||||
// if the target can't be resolved (no such channel / contact).
|
||||
|
||||
Reference in New Issue
Block a user