From ba3c3cc91f14feed3c0ee837ca316fd20a236ced Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sun, 30 Aug 2026 20:26:57 +0200 Subject: [PATCH] fix(mesh): don't mark our own channel post as unread An app-originated channel send (mirrored into the on-device history) bumped that channel's unread badge whenever the device's own UI wasn't already showing that exact channel -- unlike an on-device compose, which sidesteps this by forcing itself into that channel's view right before sending. Adds an explicit own_message flag through addChannelMsg (MessageHistory -> AbstractUITask -> UITask -> MessagesScreen) so an own post is never counted unread regardless of what's on screen when it's sent. Found the same bug in MyMeshBot.h's three auto-reply-into-channel call sites (Remote Bot's own reply showing as unread on itself) and fixed those with the same mechanism. Co-Authored-By: Claude Sonnet 5 --- examples/companion_radio/AbstractUITask.h | 7 +++++-- examples/companion_radio/MyMesh.cpp | 6 +++++- examples/companion_radio/MyMeshBot.h | 6 +++--- examples/companion_radio/ui-new/MessageHistory.h | 11 +++++++++-- examples/companion_radio/ui-new/MessagesScreen.h | 5 +++-- examples/companion_radio/ui-new/UITask.cpp | 4 ++-- examples/companion_radio/ui-new/UITask.h | 3 ++- 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index f70a11bb..301831a4 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -95,9 +95,12 @@ public: // that instead of assuming a valid position. path/path_len (packed // (hash_size-1)<<6|hop_count, same as mesh::Packet::path_len) is the hop // route this incoming post actually took -- nullptr/0 when not known (e.g. - // this is our own outgoing post). + // this is our own outgoing post). own_message: our own post (e.g. mirrored + // from an app-originated send) -- never counted unread, unlike a genuinely + // incoming post the device's own UI doesn't happen to be showing right now. virtual int addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0, - const uint8_t* path = nullptr, uint8_t path_len = 0) { return -1; } + const uint8_t* path = nullptr, uint8_t path_len = 0, + bool own_message = false) { return -1; } // Arms the "relayed into mesh" tracker (a heard repeater rebroadcast) on the // entry at ring position pos, e.g. right after addChannelMsg for a channel // send this device just originated. seq: MyMesh::lastChannelRelaySeq(). diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9107297e..6fbb1d10 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2033,7 +2033,11 @@ void MyMesh::handleCmdFrame(size_t len) { int tlen = len - i; if (tlen > MAX_TEXT_LEN) tlen = MAX_TEXT_LEN; snprintf(entry, sizeof(entry), "Me: %.*s", tlen, text); - int pos = _ui->addChannelMsg(channel_idx, entry, msg_timestamp); + // own_message=true: this is our own post, so it must never bump the + // channel's unread badge even though the device's own UI isn't + // necessarily showing this channel right now (unlike an on-device + // compose, which is always looking at the channel it just sent to). + int pos = _ui->addChannelMsg(channel_idx, entry, msg_timestamp, nullptr, 0, true); // Same "relayed into mesh" marker an on-device channel send arms (see // MessagesScreen::afterSend): sendGroupMessage above already went // through sendFloodScoped(GroupChannel&, ...), which calls diff --git a/examples/companion_radio/MyMeshBot.h b/examples/companion_radio/MyMeshBot.h index be4b9e03..bb3d4e78 100644 --- a/examples/companion_radio/MyMeshBot.h +++ b/examples/companion_radio/MyMeshBot.h @@ -202,7 +202,7 @@ void MyMesh::tryBotReplyChannel(uint8_t channel_idx, const char* text, uint8_t h if (_ui) { char with_sender[240]; // node_name(32) + ": "(2) + expanded(200) + margin snprintf(with_sender, sizeof(with_sender), "%s: %s", _prefs.node_name, expanded); - _ui->addChannelMsg(channel_idx, with_sender); + _ui->addChannelMsg(channel_idx, with_sender, 0, nullptr, 0, true); // own_message: our own bot reply, never unread } #endif } @@ -499,7 +499,7 @@ bool MyMesh::tryBotChannelCommand(uint8_t channel_idx, const char* text, uint8_t if (_ui) { char with_sender[240]; snprintf(with_sender, sizeof(with_sender), "%s: %s", _prefs.node_name, out); - _ui->addChannelMsg(channel_idx, with_sender); + _ui->addChannelMsg(channel_idx, with_sender, 0, nullptr, 0, true); // own_message: our own bot reply, never unread } #endif if (_locfix_requested) startLocFix(LOCFIX_DEST_CHANNEL, nullptr, channel_idx); @@ -658,7 +658,7 @@ void MyMesh::sendLocFixResult(const char* msg) { if (_ui) { char with_sender[240]; snprintf(with_sender, sizeof(with_sender), "%s: %s", _prefs.node_name, msg); - _ui->addChannelMsg(_loc_fix.channel_idx, with_sender); + _ui->addChannelMsg(_loc_fix.channel_idx, with_sender, 0, nullptr, 0, true); // own_message: our own bot reply, never unread } #endif } diff --git a/examples/companion_radio/ui-new/MessageHistory.h b/examples/companion_radio/ui-new/MessageHistory.h index 8bb10e99..2a9b2005 100644 --- a/examples/companion_radio/ui-new/MessageHistory.h +++ b/examples/companion_radio/ui-new/MessageHistory.h @@ -101,8 +101,15 @@ public: // path/path_len_packed: the hop path this incoming post actually took (from // the received mesh::Packet), or nullptr/0 when not known (e.g. this is our // own outgoing post, before any relay echo has arrived). + // own_message: this is our own post (e.g. mirrored from an app-originated + // send) -- never counted unread, regardless of `viewing`. An on-device + // compose already forces `viewing` true itself (it's necessarily looking at + // the channel it just sent to), so this only matters for a send the device's + // own UI wasn't necessarily showing at the time -- otherwise our own sent + // message could bump the very badge it's supposed to leave alone. int addChannelMsg(uint8_t ch_idx, const char* text, bool viewing, uint32_t timestamp = 0, - const uint8_t* path = nullptr, uint8_t path_len_packed = 0) { + const uint8_t* path = nullptr, uint8_t path_len_packed = 0, + bool own_message = false) { // Guard against bogus channel indices (e.g. findChannelIdx() returned -1 // and was cast to uint8_t → 255). Storing such an entry would burn a ring // slot for a message that no visible channel can ever surface. @@ -135,7 +142,7 @@ public: if (path && path_len_packed) capturePath(_hist[pos].path_len, _hist[pos].path, path, path_len_packed); else _hist[pos].path_len = 0; - if (!viewing && _ch_unread[ch_idx] < 99) _ch_unread[ch_idx]++; + if (!viewing && !own_message && _ch_unread[ch_idx] < 99) _ch_unread[ch_idx]++; return pos; } diff --git a/examples/companion_radio/ui-new/MessagesScreen.h b/examples/companion_radio/ui-new/MessagesScreen.h index 85ca3335..571803d8 100644 --- a/examples/companion_radio/ui-new/MessagesScreen.h +++ b/examples/companion_radio/ui-new/MessagesScreen.h @@ -735,9 +735,10 @@ public: // (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) { + 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); + 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 diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index fcba7f12..b382564a 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1767,9 +1767,9 @@ int UITask::getRecentDMContacts(uint8_t out[][NodePrefs::FAVOURITE_PREFIX_LEN], } int UITask::addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp, - const uint8_t* path, uint8_t path_len) { + 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); + return ((MessagesScreen*)messages_screen)->addChannelMsg(channel_idx, text, timestamp, path, path_len, own_message); } void UITask::armChannelRelay(int pos, uint32_t seq) { diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 6f23a249..f18529da 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -386,7 +386,8 @@ public: 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) override; + 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,