mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-28 23:34:03 +00:00
cleanup to get more space
This commit is contained in:
parent
65dcd8254e
commit
50a65a1393
@ -11,10 +11,23 @@
|
|||||||
using graphics::MessageRenderer::setThreadMode;
|
using graphics::MessageRenderer::setThreadMode;
|
||||||
using graphics::MessageRenderer::ThreadMode;
|
using graphics::MessageRenderer::ThreadMode;
|
||||||
|
|
||||||
static size_t getMessageSize(const StoredMessage &m)
|
// Calculate serialized size for a StoredMessage
|
||||||
|
static inline size_t getMessageSize(const StoredMessage &m)
|
||||||
{
|
{
|
||||||
// serialized size = fixed 16 bytes + text length (capped at MAX_MESSAGE_SIZE)
|
// serialized size = fixed 16 bytes + text length (capped at MAX_MESSAGE_SIZE)
|
||||||
return 16 + std::min(static_cast<size_t>(MAX_MESSAGE_SIZE), m.text.size());
|
return 16 + std::min<size_t>(MAX_MESSAGE_SIZE, strnlen(m.text, MAX_MESSAGE_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void assignTimestamp(StoredMessage &sm)
|
||||||
|
{
|
||||||
|
uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true);
|
||||||
|
if (nowSecs) {
|
||||||
|
sm.timestamp = nowSecs;
|
||||||
|
sm.isBootRelative = false;
|
||||||
|
} else {
|
||||||
|
sm.timestamp = millis() / 1000;
|
||||||
|
sm.isBootRelative = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageStore::logMemoryUsage(const char *context) const
|
void MessageStore::logMemoryUsage(const char *context) const
|
||||||
@ -34,38 +47,34 @@ MessageStore::MessageStore(const std::string &label)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Live message handling (RAM only)
|
// Live message handling (RAM only)
|
||||||
void MessageStore::addLiveMessage(const StoredMessage &msg)
|
void MessageStore::addLiveMessage(StoredMessage &&msg)
|
||||||
{
|
{
|
||||||
if (liveMessages.size() >= MAX_MESSAGES_SAVED) {
|
if (liveMessages.size() >= MAX_MESSAGES_SAVED) {
|
||||||
liveMessages.pop_front(); // keep only most recent N
|
liveMessages.pop_front(); // keep only most recent N
|
||||||
}
|
}
|
||||||
liveMessages.push_back(msg);
|
// Use emplace_back with std::move to avoid extra copy
|
||||||
|
liveMessages.emplace_back(std::move(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persistence queue (used only on shutdown/reboot)
|
// Persistence queue (used only on shutdown/reboot)
|
||||||
void MessageStore::addMessage(const StoredMessage &msg)
|
void MessageStore::addMessage(StoredMessage &&msg)
|
||||||
{
|
{
|
||||||
if (messages.size() >= MAX_MESSAGES_SAVED) {
|
if (messages.size() >= MAX_MESSAGES_SAVED) {
|
||||||
messages.pop_front();
|
messages.pop_front();
|
||||||
}
|
}
|
||||||
messages.push_back(msg);
|
// Use emplace_back with std::move to avoid extra copy
|
||||||
|
messages.emplace_back(std::move(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
|
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
|
||||||
{
|
{
|
||||||
StoredMessage sm;
|
StoredMessage sm;
|
||||||
|
// Always use our local time (helper handles RTC vs boot time)
|
||||||
// Always use our local time, ignore packet.rx_time
|
assignTimestamp(sm);
|
||||||
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.channelIndex = packet.channel;
|
||||||
sm.text = std::string(reinterpret_cast<const char *>(packet.decoded.payload.bytes));
|
strncpy(sm.text, reinterpret_cast<const char *>(packet.decoded.payload.bytes), MAX_MESSAGE_SIZE - 1);
|
||||||
|
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||||
|
|
||||||
if (packet.from == 0) {
|
if (packet.from == 0) {
|
||||||
// Phone-originated (outgoing)
|
// Phone-originated (outgoing)
|
||||||
@ -98,7 +107,7 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa
|
|||||||
sm.ackStatus = AckStatus::ACKED;
|
sm.ackStatus = AckStatus::ACKED;
|
||||||
}
|
}
|
||||||
|
|
||||||
addLiveMessage(sm);
|
addLiveMessage(std::move(sm));
|
||||||
|
|
||||||
// Return reference to the most recently stored message
|
// Return reference to the most recently stored message
|
||||||
return liveMessages.back();
|
return liveMessages.back();
|
||||||
@ -109,19 +118,13 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
|
|||||||
{
|
{
|
||||||
StoredMessage sm;
|
StoredMessage sm;
|
||||||
|
|
||||||
// Always use our local time
|
// Always use our local time (helper handles RTC vs boot time)
|
||||||
uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true);
|
assignTimestamp(sm);
|
||||||
if (nowSecs > 0) {
|
|
||||||
sm.timestamp = nowSecs;
|
|
||||||
sm.isBootRelative = false;
|
|
||||||
} else {
|
|
||||||
sm.timestamp = millis() / 1000;
|
|
||||||
sm.isBootRelative = true; // mark for later upgrade
|
|
||||||
}
|
|
||||||
|
|
||||||
sm.sender = sender;
|
sm.sender = sender;
|
||||||
sm.channelIndex = channelIndex;
|
sm.channelIndex = channelIndex;
|
||||||
sm.text = text;
|
strncpy(sm.text, text.c_str(), MAX_MESSAGE_SIZE - 1);
|
||||||
|
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||||
|
|
||||||
// Default manual adds to broadcast
|
// Default manual adds to broadcast
|
||||||
sm.dest = NODENUM_BROADCAST;
|
sm.dest = NODENUM_BROADCAST;
|
||||||
@ -130,7 +133,7 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
|
|||||||
// Outgoing messages start as NONE until ACK/NACK arrives
|
// Outgoing messages start as NONE until ACK/NACK arrives
|
||||||
sm.ackStatus = AckStatus::NONE;
|
sm.ackStatus = AckStatus::NONE;
|
||||||
|
|
||||||
addLiveMessage(sm);
|
addLiveMessage(std::move(sm));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_MESSAGE_PERSISTENCE
|
#if ENABLE_MESSAGE_PERSISTENCE
|
||||||
@ -157,7 +160,7 @@ void MessageStore::saveToFlash()
|
|||||||
f.write((uint8_t *)&m.sender, sizeof(m.sender));
|
f.write((uint8_t *)&m.sender, sizeof(m.sender));
|
||||||
f.write((uint8_t *)&m.channelIndex, sizeof(m.channelIndex));
|
f.write((uint8_t *)&m.channelIndex, sizeof(m.channelIndex));
|
||||||
f.write((uint8_t *)&m.dest, sizeof(m.dest));
|
f.write((uint8_t *)&m.dest, sizeof(m.dest));
|
||||||
f.write((uint8_t *)m.text.c_str(), std::min(static_cast<size_t>(MAX_MESSAGE_SIZE), m.text.size()));
|
f.write((uint8_t *)m.text, strnlen(m.text, MAX_MESSAGE_SIZE));
|
||||||
f.write('\0'); // null terminator
|
f.write('\0'); // null terminator
|
||||||
|
|
||||||
uint8_t bootFlag = m.isBootRelative ? 1 : 0;
|
uint8_t bootFlag = m.isBootRelative ? 1 : 0;
|
||||||
@ -200,15 +203,8 @@ void MessageStore::loadFromFlash()
|
|||||||
f.readBytes((char *)&m.sender, sizeof(m.sender));
|
f.readBytes((char *)&m.sender, sizeof(m.sender));
|
||||||
f.readBytes((char *)&m.channelIndex, sizeof(m.channelIndex));
|
f.readBytes((char *)&m.channelIndex, sizeof(m.channelIndex));
|
||||||
f.readBytes((char *)&m.dest, sizeof(m.dest));
|
f.readBytes((char *)&m.dest, sizeof(m.dest));
|
||||||
|
f.readBytes(m.text, MAX_MESSAGE_SIZE - 1);
|
||||||
char c;
|
m.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||||
while (m.text.size() < MAX_MESSAGE_SIZE) {
|
|
||||||
if (f.readBytes(&c, 1) <= 0)
|
|
||||||
break;
|
|
||||||
if (c == '\0')
|
|
||||||
break;
|
|
||||||
m.text.push_back(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to read boot-relative flag (new format)
|
// Try to read boot-relative flag (new format)
|
||||||
uint8_t bootFlag = 0;
|
uint8_t bootFlag = 0;
|
||||||
@ -272,75 +268,65 @@ void MessageStore::clearAllMessages()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal helper: erase first or last message matching a predicate
|
||||||
|
template <typename Predicate>
|
||||||
|
static void eraseIf(std::deque<StoredMessage> &deque, Predicate pred, bool fromBack = false)
|
||||||
|
{
|
||||||
|
if (fromBack) {
|
||||||
|
// Iterate from the back and erase the first match from the end
|
||||||
|
for (auto it = deque.rbegin(); it != deque.rend(); ++it) {
|
||||||
|
if (pred(*it)) {
|
||||||
|
deque.erase(std::next(it).base());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Erase the first matching message from the front
|
||||||
|
auto it = std::find_if(deque.begin(), deque.end(), pred);
|
||||||
|
if (it != deque.end())
|
||||||
|
deque.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dismiss oldest message (RAM + persisted queue)
|
// Dismiss oldest message (RAM + persisted queue)
|
||||||
void MessageStore::dismissOldestMessage()
|
void MessageStore::dismissOldestMessage()
|
||||||
{
|
{
|
||||||
if (!liveMessages.empty()) {
|
eraseIf(liveMessages, [](StoredMessage &) { return true; });
|
||||||
liveMessages.pop_front();
|
eraseIf(messages, [](StoredMessage &) { return true; });
|
||||||
}
|
|
||||||
if (!messages.empty()) {
|
|
||||||
messages.pop_front();
|
|
||||||
}
|
|
||||||
saveToFlash();
|
saveToFlash();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismiss oldest message in a specific channel
|
// Dismiss oldest message in a specific channel
|
||||||
void MessageStore::dismissOldestMessageInChannel(uint8_t channel)
|
void MessageStore::dismissOldestMessageInChannel(uint8_t channel)
|
||||||
{
|
{
|
||||||
auto it = std::find_if(liveMessages.begin(), liveMessages.end(), [channel](const StoredMessage &m) {
|
auto pred = [channel](const StoredMessage &m) {
|
||||||
return m.type == MessageType::BROADCAST && m.channelIndex == channel;
|
return m.type == MessageType::BROADCAST && m.channelIndex == channel;
|
||||||
});
|
};
|
||||||
if (it != liveMessages.end()) {
|
eraseIf(liveMessages, pred);
|
||||||
liveMessages.erase(it);
|
eraseIf(messages, pred);
|
||||||
}
|
|
||||||
|
|
||||||
auto it2 = std::find_if(messages.begin(), messages.end(), [channel](const StoredMessage &m) {
|
|
||||||
return m.type == MessageType::BROADCAST && m.channelIndex == channel;
|
|
||||||
});
|
|
||||||
if (it2 != messages.end()) {
|
|
||||||
messages.erase(it2);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToFlash();
|
saveToFlash();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismiss oldest message in a direct conversation with a peer
|
// Dismiss oldest message in a direct conversation with a peer
|
||||||
void MessageStore::dismissOldestMessageWithPeer(uint32_t peer)
|
void MessageStore::dismissOldestMessageWithPeer(uint32_t peer)
|
||||||
{
|
{
|
||||||
auto it = std::find_if(liveMessages.begin(), liveMessages.end(), [peer](const StoredMessage &m) {
|
auto pred = [peer](const StoredMessage &m) {
|
||||||
if (m.type == MessageType::DM_TO_US) {
|
if (m.type == MessageType::DM_TO_US) {
|
||||||
uint32_t other = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender;
|
uint32_t other = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender;
|
||||||
return other == peer;
|
return other == peer;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
};
|
||||||
if (it != liveMessages.end()) {
|
eraseIf(liveMessages, pred);
|
||||||
liveMessages.erase(it);
|
eraseIf(messages, pred);
|
||||||
}
|
|
||||||
|
|
||||||
auto it2 = std::find_if(messages.begin(), messages.end(), [peer](const StoredMessage &m) {
|
|
||||||
if (m.type == MessageType::DM_TO_US) {
|
|
||||||
uint32_t other = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender;
|
|
||||||
return other == peer;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
if (it2 != messages.end()) {
|
|
||||||
messages.erase(it2);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToFlash();
|
saveToFlash();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismiss newest message (RAM + persisted queue)
|
// Dismiss newest message (RAM + persisted queue)
|
||||||
void MessageStore::dismissNewestMessage()
|
void MessageStore::dismissNewestMessage()
|
||||||
{
|
{
|
||||||
if (!liveMessages.empty()) {
|
eraseIf(liveMessages, [](StoredMessage &) { return true; }, true);
|
||||||
liveMessages.pop_back();
|
eraseIf(messages, [](StoredMessage &) { return true; }, true);
|
||||||
}
|
|
||||||
if (!messages.empty()) {
|
|
||||||
messages.pop_back();
|
|
||||||
}
|
|
||||||
saveToFlash();
|
saveToFlash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
// Max number of messages we’ll keep in history
|
// Max number of messages we’ll keep in history
|
||||||
constexpr size_t MAX_MESSAGES_SAVED = 20;
|
#ifndef MAX_MESSAGES_SAVED
|
||||||
|
#define MAX_MESSAGES_SAVED 20
|
||||||
|
#endif
|
||||||
constexpr size_t MAX_MESSAGE_SIZE = 220; // safe bound for text payload
|
constexpr size_t MAX_MESSAGE_SIZE = 220; // safe bound for text payload
|
||||||
|
|
||||||
// Explicit message classification
|
// Explicit message classification
|
||||||
@ -37,7 +39,7 @@ struct StoredMessage {
|
|||||||
uint32_t timestamp; // When message was created (secs since boot/RTC)
|
uint32_t timestamp; // When message was created (secs since boot/RTC)
|
||||||
uint32_t sender; // NodeNum of sender
|
uint32_t sender; // NodeNum of sender
|
||||||
uint8_t channelIndex; // Channel index used
|
uint8_t channelIndex; // Channel index used
|
||||||
std::string text; // UTF-8 text payload
|
char text[MAX_MESSAGE_SIZE]; // UTF-8 text payload
|
||||||
|
|
||||||
// Destination node.
|
// Destination node.
|
||||||
// 0xffffffff (NODENUM_BROADCAST) means broadcast,
|
// 0xffffffff (NODENUM_BROADCAST) means broadcast,
|
||||||
@ -68,11 +70,11 @@ class MessageStore
|
|||||||
explicit MessageStore(const std::string &label);
|
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);
|
void addLiveMessage(StoredMessage &&msg);
|
||||||
const std::deque<StoredMessage> &getLiveMessages() const { return liveMessages; }
|
const std::deque<StoredMessage> &getLiveMessages() const { return liveMessages; }
|
||||||
|
|
||||||
// Persistence methods (used only on boot/shutdown)
|
// Persistence methods (used only on boot/shutdown)
|
||||||
void addMessage(const StoredMessage &msg);
|
void addMessage(StoredMessage &&msg);
|
||||||
const StoredMessage &addFromPacket(const meshtastic_MeshPacket &mp); // Incoming/outgoing → RAM only
|
const StoredMessage &addFromPacket(const meshtastic_MeshPacket &mp); // Incoming/outgoing → RAM only
|
||||||
void addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text);
|
void addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text);
|
||||||
void saveToFlash();
|
void saveToFlash();
|
||||||
|
|||||||
@ -35,6 +35,14 @@ namespace MessageRenderer
|
|||||||
static std::vector<std::string> cachedLines;
|
static std::vector<std::string> cachedLines;
|
||||||
static std::vector<int> cachedHeights;
|
static std::vector<int> cachedHeights;
|
||||||
|
|
||||||
|
// UTF-8 skip helper
|
||||||
|
inline size_t utf8CharLen(uint8_t c) {
|
||||||
|
if ((c & 0xE0) == 0xC0) return 2;
|
||||||
|
if ((c & 0xF0) == 0xE0) return 3;
|
||||||
|
if ((c & 0xF8) == 0xF0) return 4;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
|
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
|
||||||
{
|
{
|
||||||
int cursorX = x;
|
int cursorX = x;
|
||||||
@ -43,28 +51,16 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
|
|||||||
// Step 1: Find tallest emote in the line
|
// Step 1: Find tallest emote in the line
|
||||||
int maxIconHeight = fontHeight;
|
int maxIconHeight = fontHeight;
|
||||||
for (size_t i = 0; i < line.length();) {
|
for (size_t i = 0; i < line.length();) {
|
||||||
bool matched = false;
|
|
||||||
for (int e = 0; e < emoteCount; ++e) {
|
for (int e = 0; e < emoteCount; ++e) {
|
||||||
size_t emojiLen = strlen(emotes[e].label);
|
size_t emojiLen = strlen(emotes[e].label);
|
||||||
if (line.compare(i, emojiLen, emotes[e].label) == 0) {
|
if (line.compare(i, emojiLen, emotes[e].label) == 0) {
|
||||||
if (emotes[e].height > maxIconHeight)
|
if (emotes[e].height > maxIconHeight)
|
||||||
maxIconHeight = emotes[e].height;
|
maxIconHeight = emotes[e].height;
|
||||||
i += emojiLen;
|
i += emojiLen;
|
||||||
matched = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!matched) {
|
i += utf8CharLen(static_cast<uint8_t>(line[i]));
|
||||||
uint8_t c = static_cast<uint8_t>(line[i]);
|
|
||||||
if ((c & 0xE0) == 0xC0)
|
|
||||||
i += 2;
|
|
||||||
else if ((c & 0xF0) == 0xE0)
|
|
||||||
i += 3;
|
|
||||||
else if ((c & 0xF8) == 0xF0)
|
|
||||||
i += 4;
|
|
||||||
else
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Baseline alignment
|
// Step 2: Baseline alignment
|
||||||
@ -233,60 +229,41 @@ const std::vector<uint32_t> &getSeenPeers()
|
|||||||
return seenPeers;
|
return seenPeers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int centerYForRow(int y, int size) {
|
||||||
|
int midY = y + (FONT_HEIGHT_SMALL / 2);
|
||||||
|
return midY - (size / 2);
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers for drawing status marks (thickened strokes)
|
// Helpers for drawing status marks (thickened strokes)
|
||||||
void drawCheckMark(OLEDDisplay *display, int x, int y, int size = 8)
|
void drawCheckMark(OLEDDisplay *display, int x, int y, int size = 8)
|
||||||
{
|
{
|
||||||
int h = size;
|
int topY = centerYForRow(y, size);
|
||||||
int w = size;
|
display->setColor(WHITE);
|
||||||
|
display->drawLine(x, topY + size / 2, x + size / 3, topY + size);
|
||||||
// Center mark vertically with the text row
|
display->drawLine(x, topY + size / 2 + 1, x + size / 3, topY + size + 1);
|
||||||
int midY = y + (FONT_HEIGHT_SMALL / 2);
|
display->drawLine(x + size / 3, topY + size, x + size, topY);
|
||||||
int topY = midY - (h / 2);
|
display->drawLine(x + size / 3, topY + size + 1, x + size, topY + 1);
|
||||||
|
|
||||||
display->setColor(WHITE); // ensure we use current fg
|
|
||||||
|
|
||||||
// Draw thicker checkmark by overdrawing lines with 1px offset
|
|
||||||
// arm 1
|
|
||||||
display->drawLine(x, topY + h / 2, x + w / 3, topY + h);
|
|
||||||
display->drawLine(x, topY + h / 2 + 1, x + w / 3, topY + h + 1);
|
|
||||||
// arm 2
|
|
||||||
display->drawLine(x + w / 3, topY + h, x + w, topY);
|
|
||||||
display->drawLine(x + w / 3, topY + h + 1, x + w, topY + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawXMark(OLEDDisplay *display, int x, int y, int size = 8)
|
void drawXMark(OLEDDisplay *display, int x, int y, int size = 8)
|
||||||
{
|
{
|
||||||
int h = size;
|
int topY = centerYForRow(y, size);
|
||||||
int w = size;
|
|
||||||
|
|
||||||
// Center mark vertically with the text row
|
|
||||||
int midY = y + (FONT_HEIGHT_SMALL / 2);
|
|
||||||
int topY = midY - (h / 2);
|
|
||||||
|
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
|
display->drawLine(x, topY, x + size, topY + size);
|
||||||
// Draw thicker X with 1px offset
|
display->drawLine(x, topY + 1, x + size, topY + size + 1);
|
||||||
display->drawLine(x, topY, x + w, topY + h);
|
display->drawLine(x + size, topY, x, topY + size);
|
||||||
display->drawLine(x, topY + 1, x + w, topY + h + 1);
|
display->drawLine(x + size, topY + 1, x, topY + size + 1);
|
||||||
display->drawLine(x + w, topY, x, topY + h);
|
|
||||||
display->drawLine(x + w, topY + 1, x, topY + h + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawRelayMark(OLEDDisplay *display, int x, int y, int size = 8)
|
void drawRelayMark(OLEDDisplay *display, int x, int y, int size = 8)
|
||||||
{
|
{
|
||||||
int r = size / 2;
|
int r = size / 2;
|
||||||
int midY = y + (FONT_HEIGHT_SMALL / 2);
|
int centerY = centerYForRow(y, size) + r;
|
||||||
int centerY = midY;
|
|
||||||
int centerX = x + r;
|
int centerX = x + r;
|
||||||
|
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
|
|
||||||
// Draw circle outline (relay = uncertain status)
|
|
||||||
display->drawCircle(centerX, centerY, r);
|
display->drawCircle(centerX, centerY, r);
|
||||||
|
display->drawLine(centerX, centerY - 2, centerX, centerY);
|
||||||
// Draw "?" inside (approx, 3px wide)
|
display->setPixel(centerX, centerY + 2);
|
||||||
display->drawLine(centerX, centerY - 2, centerX, centerY); // stem
|
|
||||||
display->setPixel(centerX, centerY + 2); // dot
|
|
||||||
display->drawLine(centerX - 1, centerY - 4, centerX + 1, centerY - 4);
|
display->drawLine(centerX - 1, centerY - 4, centerX + 1, centerY - 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,7 +455,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
ackForLine.push_back(m.ackStatus);
|
ackForLine.push_back(m.ackStatus);
|
||||||
|
|
||||||
// Split message text into wrapped lines
|
// Split message text into wrapped lines
|
||||||
std::vector<std::string> wrapped = generateLines(display, "", m.text.c_str(), textWidth);
|
std::vector<std::string> wrapped = generateLines(display, "", m.text, textWidth);
|
||||||
for (auto &ln : wrapped) {
|
for (auto &ln : wrapped) {
|
||||||
allLines.push_back(ln);
|
allLines.push_back(ln);
|
||||||
isMine.push_back(mine);
|
isMine.push_back(mine);
|
||||||
|
|||||||
@ -997,7 +997,8 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
|
|||||||
|
|
||||||
sm.sender = nodeDB->getNodeNum(); // us
|
sm.sender = nodeDB->getNodeNum(); // us
|
||||||
sm.channelIndex = channel;
|
sm.channelIndex = channel;
|
||||||
sm.text = std::string(message);
|
strncpy(sm.text, message, MAX_MESSAGE_SIZE - 1);
|
||||||
|
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||||
|
|
||||||
// Classify broadcast vs DM
|
// Classify broadcast vs DM
|
||||||
if (dest == NODENUM_BROADCAST) {
|
if (dest == NODENUM_BROADCAST) {
|
||||||
@ -1009,7 +1010,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
|
|||||||
}
|
}
|
||||||
sm.ackStatus = AckStatus::NONE;
|
sm.ackStatus = AckStatus::NONE;
|
||||||
|
|
||||||
messageStore.addLiveMessage(sm);
|
messageStore.addLiveMessage(std::move(sm));
|
||||||
|
|
||||||
// Auto-switch thread view on outgoing message
|
// Auto-switch thread view on outgoing message
|
||||||
if (sm.type == MessageType::BROADCAST) {
|
if (sm.type == MessageType::BROADCAST) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user