diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index 0903c94d..ae81ec0a 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -84,8 +84,19 @@ public: 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; - virtual void addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0) {} - virtual void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0) {} + // Returns the new entry's ring position (see MessageHistory::addChannelMsg), + // or -1 on a UI variant that doesn't track history (default no-op below) -- + // callers that need it (to then arm a relay-echo tracker) should check for + // that instead of assuming a valid position. + virtual int addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0) { 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(). + virtual void armChannelRelay(int pos, uint32_t seq) {} + // ack_tag/ack_deadline_ms/resends: pending-ACK tracking for an outgoing DM + // (0 = none, e.g. incoming or "no ack expected") -- see MessageHistory::addDMMsg. + virtual 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) {} // A node shared its current position via a [LOC] message. pub_key is the // sender's key prefix for a verified DM share, or null for a channel share // (keyed by name, best-effort). Default no-op so UI variants opt in. diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 5d6033a7..8303f522 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1972,6 +1972,22 @@ void MyMesh::handleCmdFrame(size_t len) { memcpy(&out_frame[2], &expected_ack, 4); memcpy(&out_frame[6], &est_timeout, 4); _serial->writeFrame(out_frame, 10); + +#ifdef DISPLAY_CLASS + // Mirror this app-originated DM into the on-device history too, same as + // a message composed on-device (MessagesScreen::afterSend) -- otherwise + // the two queues drift and a DM sent from the phone app never shows up + // if that same contact is later opened on the device's own screen. + // ack_tag/ack_deadline mirror the on-device pending->()/x delivery marker + // too (same "+4s margin over the estimate" the on-device compose path + // uses); resends stays 0 -- the app already owns its own resend/retry + // decision, so this only drives the on-screen status, never a second, + // independent auto-resend from the device itself. + if (_ui && txt_type == TXT_TYPE_PLAIN) { + uint32_t ack_deadline_ms = expected_ack ? (millis() + est_timeout + 4000) : 0; + _ui->addDMMsg(recipient->id.pub_key, true, text, msg_timestamp, expected_ack, ack_deadline_ms, 0); + } +#endif } } else { writeErrFrame(recipient == NULL @@ -1994,6 +2010,28 @@ void MyMesh::handleCmdFrame(size_t len) { bool success = getChannel(channel_idx, channel); if (success && sendGroupMessage(msg_timestamp, channel.channel, _prefs.node_name, text, len - i)) { writeOKFrame(); +#ifdef DISPLAY_CLASS + // Mirror this app-originated channel post into the on-device history, + // same "Me: " framing MessagesScreen::afterSend uses for an on-device + // compose -- otherwise the two queues drift and a post sent from the + // phone app never shows up if that channel is later opened on-device. + // text isn't guaranteed null-terminated (len - i is its real length, + // same bound sendGroupMessage above was just given), so bound the copy. + if (_ui) { + char entry[MAX_TEXT_LEN + 8]; // "Me: " + text + 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); + // Same "relayed into mesh" marker an on-device channel send arms (see + // MessagesScreen::afterSend): sendGroupMessage above already went + // through sendFloodScoped(GroupChannel&, ...), which calls + // trackRelaySend() unconditionally, so lastChannelRelaySeq() is + // already the seq for the send that was just made -- this just + // attaches it to the matching history entry. + if (pos >= 0) _ui->armChannelRelay(pos, lastChannelRelaySeq()); + } +#endif } else { writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx } diff --git a/examples/companion_radio/ui-new/MessageHistory.h b/examples/companion_radio/ui-new/MessageHistory.h index c848b732..c99fb1c9 100644 --- a/examples/companion_radio/ui-new/MessageHistory.h +++ b/examples/companion_radio/ui-new/MessageHistory.h @@ -212,8 +212,13 @@ public: _dm_hist[pos].resends_left = (outgoing && ack_tag) ? resends : 0; } + // ack_tag/ack_deadline_ms/resends let an outgoing DM (e.g. one the phone app + // just sent via CMD_SEND_TXT_MSG) carry the same pending-ACK tracking a + // message composed on-device gets from storeDMMsg() directly — otherwise it + // shows with no delivery status at all. Unused (0) for incoming. void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, - uint32_t sender_timestamp = 0) { + uint32_t sender_timestamp = 0, uint32_t ack_tag = 0, + uint32_t ack_deadline_ms = 0, uint8_t resends = 0) { // Drop retried copies of an incoming DM: a resend reuses the sender's // timestamp and text but carries a fresh packet hash, so the mesh dup-filter // lets it through. Match on prefix + sender_timestamp + text to suppress it. @@ -225,7 +230,7 @@ public: return; // duplicate retry — already in history } } - storeDMMsg(pub_key, outgoing, text, 0, 0, outgoing ? 0 : sender_timestamp, 0); + storeDMMsg(pub_key, outgoing, text, ack_tag, ack_deadline_ms, sender_timestamp, resends); } int dmHistCountForContact(const uint8_t* prefix) const { diff --git a/examples/companion_radio/ui-new/MessagesScreen.h b/examples/companion_radio/ui-new/MessagesScreen.h index c25b7cb6..fb569d1d 100644 --- a/examples/companion_radio/ui-new/MessagesScreen.h +++ b/examples/companion_radio/ui-new/MessagesScreen.h @@ -661,10 +661,12 @@ public: return pos; } void markChannelRelayed(uint32_t seq) { _history.markChannelRelayed(seq); } + 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 sender_timestamp = 0, uint32_t ack_tag = 0, + uint32_t ack_deadline_ms = 0, uint8_t resends = 0) { bool viewing = (_phase == DM_HIST && memcmp(_sel_contact.id.pub_key, pub_key, 4) == 0); - _history.addDMMsg(pub_key, outgoing, text, sender_timestamp); + _history.addDMMsg(pub_key, outgoing, text, sender_timestamp, ack_tag, ack_deadline_ms, resends); if (viewing && _dm_hist_sel > 0) { _dm_hist_sel++; _dm_hist_scroll++; } // see addChannelMsg } void markDmDelivered(uint32_t ack_crc) { _history.markDmDelivered(ack_crc); } diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index c1587ed9..661e994d 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1766,9 +1766,13 @@ int UITask::getRecentDMContacts(uint8_t out[][NodePrefs::FAVOURITE_PREFIX_LEN], return ((MessagesScreen*)messages_screen)->getRecentDMContacts(out, max); } -void UITask::addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp) { +int UITask::addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp) { _last_notif_ch_idx = (int)channel_idx; - ((MessagesScreen*)messages_screen)->addChannelMsg(channel_idx, text, timestamp); + return ((MessagesScreen*)messages_screen)->addChannelMsg(channel_idx, text, timestamp); +} + +void UITask::armChannelRelay(int pos, uint32_t seq) { + ((MessagesScreen*)messages_screen)->armChannelRelay(pos, seq); } int UITask::getChannelUnreadCount() const { @@ -1801,8 +1805,9 @@ 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) { - ((MessagesScreen*)messages_screen)->addDMMsg(pub_key, outgoing, text, sender_timestamp); +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) { + ((MessagesScreen*)messages_screen)->addDMMsg(pub_key, outgoing, text, sender_timestamp, ack_tag, ack_deadline_ms, resends); } int UITask::getDMUnreadTotal() const { diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 2a492087..0259a5ff 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -385,8 +385,10 @@ public: void stopMelody(); bool isMelodyPlaying(); void showAlert(const char* text, int duration_millis); - void addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0) override; - void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0) override; + int addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0) 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) override; void onMsgAck(uint32_t ack_crc) override; void onChannelRelayed(uint32_t seq) override; void onRoomLoginResult(const uint8_t* pub_key, bool success, uint8_t permissions) override;