diff --git a/src/MessageStore.cpp b/src/MessageStore.cpp index 916edc96d..4bf778eb9 100644 --- a/src/MessageStore.cpp +++ b/src/MessageStore.cpp @@ -1,9 +1,9 @@ #include "MessageStore.h" #include "FSCommon.h" -#include "NodeDB.h" // for nodeDB->getNodeNum() +#include "NodeDB.h" #include "SPILock.h" #include "SafeFile.h" -#include "configuration.h" // for millis() +#include "configuration.h" #include "gps/RTC.h" #include "graphics/draw/MessageRenderer.h" @@ -15,7 +15,7 @@ MessageStore::MessageStore(const std::string &label) filename = "/Messages_" + label + ".msgs"; } -// === Live message handling (RAM only) === +// Live message handling (RAM only) void MessageStore::addLiveMessage(const StoredMessage &msg) { if (liveMessages.size() >= MAX_MESSAGES_SAVED) { @@ -24,7 +24,7 @@ void MessageStore::addLiveMessage(const StoredMessage &msg) liveMessages.push_back(msg); } -// === Persistence queue (used only on shutdown/reboot) === +// Persistence queue (used only on shutdown/reboot) void MessageStore::addMessage(const StoredMessage &msg) { if (messages.size() >= MAX_MESSAGES_SAVED) { @@ -32,8 +32,7 @@ void MessageStore::addMessage(const StoredMessage &msg) } messages.push_back(msg); } - -void MessageStore::addFromPacket(const meshtastic_MeshPacket &packet) +const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet) { StoredMessage sm; @@ -47,26 +46,41 @@ void MessageStore::addFromPacket(const meshtastic_MeshPacket &packet) sm.isBootRelative = true; // mark for later upgrade } - sm.sender = packet.from; sm.channelIndex = packet.channel; sm.text = std::string(reinterpret_cast(packet.decoded.payload.bytes)); - // Classification logic - if (packet.to == NODENUM_BROADCAST || packet.decoded.dest == NODENUM_BROADCAST) { - sm.dest = NODENUM_BROADCAST; - sm.type = MessageType::BROADCAST; - } else if (packet.to == nodeDB->getNodeNum()) { - sm.dest = nodeDB->getNodeNum(); // DM to us - sm.type = MessageType::DM_TO_US; + if (packet.from == 0) { + // Phone-originated (outgoing) + sm.sender = nodeDB->getNodeNum(); // our node ID + if (packet.decoded.dest == 0 || packet.decoded.dest == NODENUM_BROADCAST) { + sm.dest = NODENUM_BROADCAST; + sm.type = MessageType::BROADCAST; + } else { + sm.dest = packet.decoded.dest; + sm.type = MessageType::DM_TO_US; + } } else { - sm.dest = NODENUM_BROADCAST; // fallback - sm.type = MessageType::BROADCAST; + // Normal incoming + sm.sender = packet.from; + if (packet.to == NODENUM_BROADCAST || packet.decoded.dest == NODENUM_BROADCAST) { + sm.dest = NODENUM_BROADCAST; + sm.type = MessageType::BROADCAST; + } else if (packet.to == nodeDB->getNodeNum()) { + sm.dest = nodeDB->getNodeNum(); // DM to us + sm.type = MessageType::DM_TO_US; + } else { + sm.dest = NODENUM_BROADCAST; // fallback + sm.type = MessageType::BROADCAST; + } } addLiveMessage(sm); + + // Return reference to the most recently stored message + return liveMessages.back(); } -// === Outgoing/manual message === +// Outgoing/manual message void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text) { StoredMessage sm; @@ -92,7 +106,7 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st addLiveMessage(sm); } -// === Save RAM queue to flash (called on shutdown) === +// Save RAM queue to flash (called on shutdown) void MessageStore::saveToFlash() { #ifdef FSCom @@ -128,7 +142,7 @@ void MessageStore::saveToFlash() #endif } -// === Load persisted messages into RAM (called at boot) === +// Load persisted messages into RAM (called at boot) void MessageStore::loadFromFlash() { messages.clear(); @@ -189,7 +203,7 @@ void MessageStore::loadFromFlash() #endif } -// === Clear all messages (RAM + persisted queue) === +// Clear all messages (RAM + persisted queue) void MessageStore::clearAllMessages() { liveMessages.clear(); @@ -203,7 +217,7 @@ void MessageStore::clearAllMessages() #endif } -// === Dismiss oldest message (RAM + persisted queue) === +// Dismiss oldest message (RAM + persisted queue) void MessageStore::dismissOldestMessage() { if (!liveMessages.empty()) { @@ -215,7 +229,7 @@ void MessageStore::dismissOldestMessage() saveToFlash(); } -// === Dismiss newest message (RAM + persisted queue) === +// Dismiss newest message (RAM + persisted queue) void MessageStore::dismissNewestMessage() { if (!liveMessages.empty()) { @@ -227,7 +241,7 @@ void MessageStore::dismissNewestMessage() saveToFlash(); } -// === Helper filters for future use === +// Helper filters for future use std::deque MessageStore::getChannelMessages(uint8_t channel) const { std::deque result; @@ -250,7 +264,7 @@ std::deque MessageStore::getDirectMessages() const return result; } -// === Upgrade boot-relative timestamps once RTC is valid === +// Upgrade boot-relative timestamps once RTC is valid // Only same-boot boot-relative messages are healed. // Persisted boot-relative messages from old boots stay ??? forever. void MessageStore::upgradeBootRelativeTimestamps() @@ -280,5 +294,5 @@ void MessageStore::upgradeBootRelativeTimestamps() } } -// === Global definition === +// Global definition MessageStore messageStore("default"); diff --git a/src/MessageStore.h b/src/MessageStore.h index 3c77a1cb9..5f991d850 100644 --- a/src/MessageStore.h +++ b/src/MessageStore.h @@ -1,5 +1,5 @@ #pragma once -#include "mesh/generated/meshtastic/mesh.pb.h" // for meshtastic_MeshPacket +#include "mesh/generated/meshtastic/mesh.pb.h" #include #include #include @@ -42,32 +42,31 @@ class MessageStore public: explicit MessageStore(const std::string &label); - // === Live RAM methods (always current, used by UI and runtime) === + // Live RAM methods (always current, used by UI and runtime) void addLiveMessage(const StoredMessage &msg); const std::deque &getLiveMessages() const { return liveMessages; } - // === Persistence methods (used only on boot/shutdown) === - void addMessage(const StoredMessage &msg); // Add to persistence queue - void addFromPacket(const meshtastic_MeshPacket &mp); // Incoming → RAM only - void addFromString(uint32_t sender, uint8_t channelIndex, - const std::string &text); // Outgoing/manual → RAM only - void saveToFlash(); // Persist RAM → flash - void loadFromFlash(); // Restore flash → RAM + // Persistence methods (used only on boot/shutdown) + void addMessage(const StoredMessage &msg); + const StoredMessage &addFromPacket(const meshtastic_MeshPacket &mp); // Incoming/outgoing → RAM only + void addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text); + void saveToFlash(); + void loadFromFlash(); - // === Clear all messages (RAM + persisted queue) === + // Clear all messages (RAM + persisted queue) void clearAllMessages(); - // === Dismiss helpers === - void dismissOldestMessage(); // Drop front() from history - void dismissNewestMessage(); // Drop back() from history (useful for current screen) + // Dismiss helpers + void dismissOldestMessage(); + void dismissNewestMessage(); - // === Unified accessor (for UI code, defaults to RAM buffer) === + // Unified accessor (for UI code, defaults to RAM buffer) const std::deque &getMessages() const { return liveMessages; } // Optional: direct access to persisted copy (mainly for debugging/inspection) const std::deque &getPersistedMessages() const { return messages; } - // === Helper filters for future use === + // Helper filters for future use std::deque getChannelMessages(uint8_t channel) const; std::deque getDirectMessages() const; std::deque getConversationWith(uint32_t peer) const; @@ -76,14 +75,10 @@ class MessageStore void upgradeBootRelativeTimestamps(); private: - // RAM buffer (always current, main source for UI) std::deque liveMessages; - - // Persisted storage (only updated on shutdown, loaded at boot) - std::deque messages; - + std::deque messages; // persisted copy std::string filename; }; -// === Global instance (defined in MessageStore.cpp) === -extern MessageStore messageStore; +// Global instance (defined in MessageStore.cpp) +extern MessageStore messageStore; \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index ab45f33dc..162ca99b3 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -283,12 +283,6 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int pi.drawFrame(display, state, x, y); } -// Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled -static bool shouldDrawMessage(const meshtastic_MeshPacket *packet) -{ - return packet->from != 0 && !moduleConfig.store_forward.enabled; -} - /** * Given a recent lat/lon return a guess of the heading the user is walking on. * @@ -514,10 +508,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) void Screen::setup() { - // === Enable display rendering === + // Enable display rendering useDisplay = true; - // === Load saved brightness from UI config === + // Load saved brightness from UI config // For OLED displays (SSD1306), default brightness is 255 if not set if (uiconfig.screen_brightness == 0) { #if defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) @@ -529,7 +523,7 @@ void Screen::setup() brightness = uiconfig.screen_brightness; } - // === Detect OLED subtype (if supported by board variant) === + // Detect OLED subtype (if supported by board variant) #ifdef AutoOLEDWire_h if (isAUTOOled) static_cast(dispdev)->setDetected(model); @@ -544,7 +538,7 @@ void Screen::setup() static_cast(dispdev)->setRGB(TFT_MESH); #endif - // === Initialize display and UI system === + // Initialize display and UI system ui->init(); displayWidth = dispdev->width(); displayHeight = dispdev->height(); @@ -556,7 +550,7 @@ void Screen::setup() ui->disableAllIndicators(); // Disable page indicator dots ui->getUiState()->userData = this; // Allow static callbacks to access Screen instance - // === Apply loaded brightness === + // Apply loaded brightness #if defined(ST7789_CS) static_cast(dispdev)->setDisplayBrightness(brightness); #elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306) @@ -564,20 +558,20 @@ void Screen::setup() #endif LOG_INFO("Applied screen brightness: %d", brightness); - // === Set custom overlay callbacks === + // Set custom overlay callbacks static OverlayCallback overlays[] = { graphics::UIRenderer::drawNavigationBar // Custom indicator icons for each frame }; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); - // === Enable UTF-8 to display mapping === + // Enable UTF-8 to display mapping dispdev->setFontTableLookupFunction(customFontTableLookup); #ifdef USERPREFS_OEM_TEXT logo_timeout *= 2; // Give more time for branded boot logos #endif - // === Configure alert frames (e.g., "Resuming..." or region name) === + // Configure alert frames (e.g., "Resuming..." or region name) EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip slow refresh alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { #ifdef ARCH_ESP32 @@ -593,10 +587,10 @@ void Screen::setup() ui->setFrames(alertFrames, 1); ui->disableAutoTransition(); // Require manual navigation between frames - // === Log buffer for on-screen logs (3 lines max) === + // Log buffer for on-screen logs (3 lines max) dispdev->setLogBuffer(3, 32); - // === Optional screen mirroring or flipping (e.g. for T-Beam orientation) === + // Optional screen mirroring or flipping (e.g. for T-Beam orientation) #ifdef SCREEN_MIRROR dispdev->mirrorScreen(); #else @@ -612,7 +606,7 @@ void Screen::setup() } #endif - // === Generate device ID from MAC address === + // Generate device ID from MAC address uint8_t dmac[6]; getMacAddr(dmac); snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]); @@ -621,7 +615,7 @@ void Screen::setup() handleSetOn(false); // Ensure proper init for Arduino targets #endif - // === Turn on display and trigger first draw === + // Turn on display and trigger first draw handleSetOn(true); determineResolution(dispdev->height(), dispdev->width()); ui->update(); @@ -644,7 +638,7 @@ void Screen::setup() touchScreenImpl1->init(); #endif - // === Subscribe to device status updates === + // Subscribe to device status updates powerStatusObserver.observe(&powerStatus->onNewStatus); gpsStatusObserver.observe(&gpsStatus->onNewStatus); nodeStatusObserver.observe(&nodeStatus->onNewStatus); @@ -652,16 +646,14 @@ void Screen::setup() #if !MESHTASTIC_EXCLUDE_ADMIN adminMessageObserver.observe(adminModule); #endif - if (textMessageModule) - textMessageObserver.observe(textMessageModule); if (inputBroker) inputObserver.observe(inputBroker); - // === Load persisted messages into RAM === + // Load persisted messages into RAM messageStore.loadFromFlash(); LOG_INFO("MessageStore loaded from flash"); - // === Notify modules that support UI events === + // Notify modules that support UI events MeshModule::observeUIEvents(&uiFrameEventObserver); } @@ -1130,7 +1122,7 @@ void Screen::setFrames(FrameFocus focus) } fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE - this->frameCount = numframes; // ✅ Save frame count for use in custom overlay + this->frameCount = numframes; // Save frame count for use in custom overlay LOG_DEBUG("Finished build frames. numframes: %d", numframes); ui->setFrames(normalFrames, numframes); @@ -1150,10 +1142,6 @@ void Screen::setFrames(FrameFocus focus) case FOCUS_FAULT: ui->switchToFrame(fsi.positions.fault); break; - case FOCUS_TEXTMESSAGE: - hasUnreadMessage = false; // ✅ Clear when message is *viewed* - ui->switchToFrame(fsi.positions.textMessage); - break; case FOCUS_MODULE: // Whichever frame was marked by MeshModule::requestFocus(), if any // If no module requested focus, will show the first frame instead @@ -1438,120 +1426,6 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) return 0; } -// Handles when message is received; will jump to text message frame. -int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) -{ - if (!showingNormalScreen) - return 0; - - // === Build stored message === - StoredMessage sm; - - // Always use our local time - uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true); - if (nowSecs > 0) { - sm.timestamp = nowSecs; - sm.isBootRelative = false; - } else { - sm.timestamp = millis() / 1000; - sm.isBootRelative = true; // mark for later upgrade - } - - sm.channelIndex = packet->channel; - sm.text = std::string(reinterpret_cast(packet->decoded.payload.bytes)); - - if (packet->from == 0) { - // Outgoing message (sent by us, typically via phone) - sm.sender = nodeDB->getNodeNum(); // us - if (packet->decoded.dest == 0 || packet->decoded.dest == NODENUM_BROADCAST) { - // Fix: treat 0 as broadcast, not DM:00000000 - sm.dest = NODENUM_BROADCAST; - sm.type = MessageType::BROADCAST; - } else { - sm.dest = packet->decoded.dest; - sm.type = MessageType::DM_TO_US; - } - - } else { - // === Incoming message === - sm.sender = packet->from; - if (packet->to == NODENUM_BROADCAST || packet->decoded.dest == NODENUM_BROADCAST) { - sm.dest = NODENUM_BROADCAST; - sm.type = MessageType::BROADCAST; - } else { - sm.dest = nodeDB->getNodeNum(); // our node (we are DM target) - sm.type = MessageType::DM_TO_US; - } - - hasUnreadMessage = true; // only incoming triggers mail icon - - // Wake/force display if configured - if (shouldWakeOnReceivedMessage()) { - setOn(true); - forceDisplay(); - } - - // Prepare banner - const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); - const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; - - const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes); - char banner[256]; - - bool isAlert = false; - for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) { - if (msgRaw[i] == '\x07') { // bell - isAlert = true; - break; - } - } - - if (isAlert) { - if (longName && longName[0]) { - snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); - } else { - strcpy(banner, "Alert Received"); - } - } else { - if (longName && longName[0]) { -#if defined(M5STACK_UNITC6L) - strcpy(banner, "New Message"); -#else - snprintf(banner, sizeof(banner), "New Message from\n%s", longName); -#endif - } else { - strcpy(banner, "New Message"); - } - } - -#if defined(M5STACK_UNITC6L) - screen->setOn(true); - screen->showSimpleBanner(banner, 1500); - playLongBeep(); -#else - screen->showSimpleBanner(banner, 3000); -#endif - } - - // Save to store (both outgoing + incoming) - messageStore.addLiveMessage(sm); - - // Keep frame set active - setFrames(FOCUS_PRESERVE); - - // Auto-switch thread view - if (sm.type == MessageType::BROADCAST) { - graphics::MessageRenderer::setThreadMode(graphics::MessageRenderer::ThreadMode::CHANNEL, sm.channelIndex); - } else if (sm.type == MessageType::DM_TO_US) { - uint32_t peer = (packet->from == 0) ? sm.dest : sm.sender; - graphics::MessageRenderer::setThreadMode(graphics::MessageRenderer::ThreadMode::DIRECT, -1, peer); - } - - // Reset scroll so newest message starts from the top - graphics::MessageRenderer::resetScrollState(); - - return 0; -} // Triggered by MeshModules int Screen::handleUIFrameEvent(const UIFrameEvent *event) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 74b8d7c5d..9902bf03f 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -40,7 +40,6 @@ class Screen FOCUS_DEFAULT, // No specific frame FOCUS_PRESERVE, // Return to the previous frame FOCUS_FAULT, - FOCUS_TEXTMESSAGE, FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus FOCUS_CLOCK, FOCUS_SYSTEM, @@ -209,8 +208,6 @@ class Screen : public concurrency::OSThread CallbackObserver(this, &Screen::handleStatusUpdate); CallbackObserver nodeStatusObserver = CallbackObserver(this, &Screen::handleStatusUpdate); - CallbackObserver textMessageObserver = - CallbackObserver(this, &Screen::handleTextMessage); CallbackObserver uiFrameEventObserver = CallbackObserver(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules CallbackObserver inputObserver = @@ -229,7 +226,6 @@ class Screen : public concurrency::OSThread FOCUS_DEFAULT, // No specific frame FOCUS_PRESERVE, // Return to the previous frame FOCUS_FAULT, - FOCUS_TEXTMESSAGE, FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus FOCUS_CLOCK, FOCUS_SYSTEM, @@ -575,7 +571,6 @@ class Screen : public concurrency::OSThread // Handle observer events int handleStatusUpdate(const meshtastic::Status *arg); - int handleTextMessage(const meshtastic_MeshPacket *arg); int handleUIFrameEvent(const UIFrameEvent *arg); int handleInputEvent(const InputEvent *arg); int handleAdminMessage(AdminModule_ObserverData *arg); diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index e5db6988c..f8d232b72 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -1,26 +1,3 @@ -/* -BaseUI - -Developed and Maintained By: -- Ronald Garcia (HarukiToreda) – Lead development and implementation. -- JasonP (Xaositek) – Screen layout and icon design, UI improvements and testing. -- TonyG (Tropho) – Project management, structural planning, and testing - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -*/ - #include "configuration.h" #if HAS_SCREEN #include "MessageRenderer.h" @@ -28,25 +5,23 @@ along with this program. If not, see . // Core includes #include "MessageStore.h" #include "NodeDB.h" +#include "UIRenderer.h" #include "configuration.h" #include "gps/RTC.h" +#include "graphics/Screen.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" +#include "graphics/TimeFormatters.h" #include "graphics/emotes.h" #include "main.h" #include "meshUtils.h" - -// Additional includes for UI rendering -#include "UIRenderer.h" -#include "graphics/TimeFormatters.h" - -// Additional includes for dependencies #include #include // External declarations extern bool hasUnreadMessage; extern meshtastic_DeviceState devicestate; +extern graphics::Screen *screen; using graphics::Emote; using graphics::emotes; @@ -65,7 +40,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string int cursorX = x; const int fontHeight = FONT_HEIGHT_SMALL; - // === Step 1: Find tallest emote in the line === + // Step 1: Find tallest emote in the line int maxIconHeight = fontHeight; for (size_t i = 0; i < line.length();) { bool matched = false; @@ -248,7 +223,7 @@ uint32_t getThreadPeer() return currentPeer; } -// === Accessors for menuHandler === +// Accessors for menuHandler const std::vector &getSeenChannels() { return seenChannels; @@ -656,6 +631,73 @@ void renderMessageContent(OLEDDisplay *display, const std::vector & } } +void handleNewMessage(const StoredMessage &sm, const meshtastic_MeshPacket &packet) +{ + if (packet.from != 0) { + hasUnreadMessage = true; + + if (shouldWakeOnReceivedMessage()) { + screen->setOn(true); + // screen->forceDisplay(); <-- remove, let Screen handle this + } + + // Banner logic + const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from); + const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; + const char *msgRaw = reinterpret_cast(packet.decoded.payload.bytes); + + char banner[256]; + bool isAlert = false; + for (size_t i = 0; i < packet.decoded.payload.size && i < 100; i++) { + if (msgRaw[i] == '\x07') { + isAlert = true; + break; + } + } + + if (isAlert) { + if (longName && longName[0]) + snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); + else + strcpy(banner, "Alert Received"); + } else { + if (longName && longName[0]) { +#if defined(M5STACK_UNITC6L) + strcpy(banner, "New Message"); +#else + snprintf(banner, sizeof(banner), "New Message from\n%s", longName); +#endif + } else + strcpy(banner, "New Message"); + } + + // Shorter banner if already in a conversation (Channel or Direct) + bool inThread = (getThreadMode() != ThreadMode::ALL); + +#if defined(M5STACK_UNITC6L) + screen->setOn(true); + screen->showSimpleBanner(banner, inThread ? 1000 : 1500); + playLongBeep(); +#else + screen->showSimpleBanner(banner, inThread ? 1000 : 3000); +#endif + } + + // No setFrames() here anymore + setThreadFor(sm, packet); + resetScrollState(); +} + +void setThreadFor(const StoredMessage &sm, const meshtastic_MeshPacket &packet) +{ + if (sm.type == MessageType::BROADCAST) { + setThreadMode(ThreadMode::CHANNEL, sm.channelIndex); + } else if (sm.type == MessageType::DM_TO_US) { + uint32_t peer = (packet.from == 0) ? sm.dest : sm.sender; + setThreadMode(ThreadMode::DIRECT, -1, peer); + } +} + } // namespace MessageRenderer } // namespace graphics #endif \ No newline at end of file diff --git a/src/graphics/draw/MessageRenderer.h b/src/graphics/draw/MessageRenderer.h index e799c971c..98018e90f 100644 --- a/src/graphics/draw/MessageRenderer.h +++ b/src/graphics/draw/MessageRenderer.h @@ -1,7 +1,9 @@ #pragma once +#include "MessageStore.h" // for StoredMessage #include "OLEDDisplay.h" #include "OLEDDisplayUi.h" #include "graphics/emotes.h" +#include "mesh/generated/meshtastic/mesh.pb.h" // for meshtastic_MeshPacket #include #include @@ -10,7 +12,7 @@ namespace graphics namespace MessageRenderer { -// === Thread filter modes === +// Thread filter modes enum class ThreadMode { ALL, CHANNEL, DIRECT }; // Setter for switching thread mode @@ -50,5 +52,11 @@ void renderMessageContent(OLEDDisplay *display, const std::vector & // Reset scroll state when new messages arrive void resetScrollState(); +// Helper to auto-select the correct thread mode from a message +void setThreadFor(const StoredMessage &sm, const meshtastic_MeshPacket &packet); + +// Handles a new incoming/outgoing message: banner, wake, thread select, scroll reset +void handleNewMessage(const StoredMessage &sm, const meshtastic_MeshPacket &packet); + } // namespace MessageRenderer -} // namespace graphics \ No newline at end of file +} // namespace graphics diff --git a/src/modules/TextMessageModule.cpp b/src/modules/TextMessageModule.cpp index 72df330c5..e41daddc9 100644 --- a/src/modules/TextMessageModule.cpp +++ b/src/modules/TextMessageModule.cpp @@ -1,10 +1,13 @@ #include "TextMessageModule.h" #include "MeshService.h" +#include "MessageStore.h" #include "NodeDB.h" #include "PowerFSM.h" #include "buzz.h" #include "configuration.h" #include "graphics/Screen.h" +#include "graphics/draw/MessageRenderer.h" + TextMessageModule *textMessageModule; ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp) @@ -13,15 +16,23 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp auto &p = mp.decoded; LOG_INFO("Received text msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes); #endif + // We only store/display messages destined for us. - // Keep a copy of the most recent text message. devicestate.rx_text_message = mp; devicestate.has_rx_text_message = true; + // Store in the central message history + const StoredMessage &sm = messageStore.addFromPacket(mp); + + // Pass message to renderer (banner + thread switching + scroll reset) + graphics::MessageRenderer::handleNewMessage(sm, mp); + // Only trigger screen wake if configuration allows it if (shouldWakeOnReceivedMessage()) { powerFSM.trigger(EVENT_RECEIVED_MSG); } + + // Notify any observers (e.g. external modules that care about packets) notifyObservers(&mp); return ProcessMessage::CONTINUE; // Let others look at this message also if they want @@ -30,4 +41,4 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp bool TextMessageModule::wantPacket(const meshtastic_MeshPacket *p) { return MeshService::isTextPayload(p); -} \ No newline at end of file +} diff --git a/src/modules/TextMessageModule.h b/src/modules/TextMessageModule.h index 3d833334b..e719f1abc 100644 --- a/src/modules/TextMessageModule.h +++ b/src/modules/TextMessageModule.h @@ -29,4 +29,4 @@ class TextMessageModule : public SinglePortModule, public Observable