mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-27 23:12:39 +00:00
Signal bars for message ack
This commit is contained in:
parent
84cef67b82
commit
784e71f2fa
@ -38,7 +38,7 @@ extern bool hasUnreadMessage;
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
int bannerSignalBars = -1;
|
||||
InputEvent NotificationRenderer::inEvent;
|
||||
int8_t NotificationRenderer::curSelected = 0;
|
||||
char NotificationRenderer::alertBannerMessage[256] = {0};
|
||||
@ -461,10 +461,10 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
||||
void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[],
|
||||
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth)
|
||||
{
|
||||
|
||||
bool is_picker = false;
|
||||
uint16_t lineCount = 0;
|
||||
// === Layout Configuration ===
|
||||
|
||||
// Layout Configuration
|
||||
constexpr uint16_t hPadding = 5;
|
||||
constexpr uint16_t vPadding = 2;
|
||||
bool needs_bell = false;
|
||||
@ -478,13 +478,31 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
// Measure all lines and calculate widest width
|
||||
uint16_t widestLineWithBars = 0;
|
||||
|
||||
while (lines[lineCount] != nullptr) {
|
||||
auto newlinePointer = strchr(lines[lineCount], '\n');
|
||||
if (newlinePointer)
|
||||
lineLengths[lineCount] = (newlinePointer - lines[lineCount]); // Check for newlines first
|
||||
else // if the newline wasn't found, then pull string length from strlen
|
||||
lineLengths[lineCount] = strlen(lines[lineCount]);
|
||||
else
|
||||
lineLengths[lineCount] = strlen(lines[lineCount]); // Fallback to strlen if no newline
|
||||
|
||||
lineWidths[lineCount] = display->getStringWidth(lines[lineCount], lineLengths[lineCount], true);
|
||||
|
||||
// Calculate potential width including signal bars (without altering per-line widths)
|
||||
uint16_t potentialWidth = lineWidths[lineCount];
|
||||
if (bannerSignalBars >= 0 && strstr(lines[lineCount], "Signal:") != nullptr) {
|
||||
const int totalBars = 5;
|
||||
const int barWidth = 3;
|
||||
const int barSpacing = 2;
|
||||
int barsWidth = totalBars * barWidth + (totalBars - 1) * barSpacing + 6;
|
||||
potentialWidth += barsWidth;
|
||||
}
|
||||
|
||||
if (potentialWidth > widestLineWithBars)
|
||||
widestLineWithBars = potentialWidth;
|
||||
|
||||
if (!is_picker) {
|
||||
needs_bell |= (strstr(alertBannerMessage, "Alert Received") != nullptr);
|
||||
if (lineWidths[lineCount] > maxWidth)
|
||||
@ -492,10 +510,15 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
||||
}
|
||||
lineCount++;
|
||||
}
|
||||
// count lines
|
||||
|
||||
// Use widestLineWithBars to size the box if signal bars exist
|
||||
if (widestLineWithBars > maxWidth)
|
||||
maxWidth = widestLineWithBars;
|
||||
|
||||
uint16_t boxWidth = hPadding * 2 + maxWidth;
|
||||
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
// Special sizing rules for M5STACK_UNITC6L
|
||||
if (needs_bell) {
|
||||
if (isHighResolution && boxWidth <= 150)
|
||||
boxWidth += 26;
|
||||
@ -527,7 +550,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
||||
boxTop = 0;
|
||||
}
|
||||
|
||||
// === Draw Box ===
|
||||
// Draw Box
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(boxLeft, boxTop, boxWidth, boxHeight);
|
||||
display->setColor(WHITE);
|
||||
@ -541,89 +564,71 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
||||
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
|
||||
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
|
||||
display->setColor(WHITE);
|
||||
int16_t lineY = boxTop + vPadding;
|
||||
int swingRange = 8;
|
||||
static int swingOffset = 0;
|
||||
static bool swingRight = true;
|
||||
static unsigned long lastSwingTime = 0;
|
||||
unsigned long now = millis();
|
||||
int swingSpeedMs = 10 / (swingRange * 2);
|
||||
if (now - lastSwingTime >= (unsigned long)swingSpeedMs) {
|
||||
lastSwingTime = now;
|
||||
if (swingRight) {
|
||||
swingOffset++;
|
||||
if (swingOffset >= swingRange)
|
||||
swingRight = false;
|
||||
} else {
|
||||
swingOffset--;
|
||||
if (swingOffset <= 0)
|
||||
swingRight = true;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
bool isTitle = (i == 0);
|
||||
int globalOptionIndex = (i - 1) + firstOptionToShow;
|
||||
bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected);
|
||||
|
||||
uint16_t visibleWidth = 64 - hPadding * 2;
|
||||
if (totalLines > visibleTotalLines)
|
||||
visibleWidth -= 6;
|
||||
// Draw Content
|
||||
int16_t lineY = boxTop + vPadding;
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
char lineBuffer[lineLengths[i] + 1];
|
||||
strncpy(lineBuffer, lines[i], lineLengths[i]);
|
||||
lineBuffer[lineLengths[i]] = '\0';
|
||||
|
||||
if (isTitle) {
|
||||
if (visibleTotalLines == 1) {
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
|
||||
display->setColor(WHITE);
|
||||
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
|
||||
} else {
|
||||
display->setColor(WHITE);
|
||||
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
|
||||
display->setColor(BLACK);
|
||||
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
|
||||
display->setColor(WHITE);
|
||||
if (needs_bell) {
|
||||
int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2;
|
||||
display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert);
|
||||
display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert);
|
||||
bool isSignalLine = (bannerSignalBars >= 0 && strstr(lineBuffer, "Signal:") != nullptr);
|
||||
|
||||
if (isSignalLine) {
|
||||
// Center text + signal bars as a single group
|
||||
const int totalBars = 5;
|
||||
const int barWidth = 3;
|
||||
const int barSpacing = 2;
|
||||
const int barHeightStep = 2;
|
||||
|
||||
int textWidth = display->getStringWidth(lineBuffer, strlen(lineBuffer), true);
|
||||
int barsWidth = totalBars * barWidth + (totalBars - 1) * barSpacing + 6;
|
||||
int totalWidth = textWidth + barsWidth;
|
||||
|
||||
int groupStartX = boxLeft + (boxWidth - totalWidth) / 2;
|
||||
|
||||
display->drawString(groupStartX, lineY, lineBuffer);
|
||||
|
||||
int baseX = groupStartX + textWidth + 6;
|
||||
int baseY = lineY + effectiveLineHeight - 1;
|
||||
|
||||
for (int b = 0; b < totalBars; b++) {
|
||||
int barHeight = (b + 1) * barHeightStep;
|
||||
int x = baseX + b * (barWidth + barSpacing);
|
||||
int y = baseY - barHeight;
|
||||
|
||||
if (b < bannerSignalBars) {
|
||||
display->fillRect(x, y, barWidth, barHeight);
|
||||
} else {
|
||||
display->drawRect(x, y, barWidth, barHeight);
|
||||
}
|
||||
}
|
||||
lineY = boxTop + effectiveLineHeight + 1;
|
||||
} else if (isSelectedOption) {
|
||||
display->setColor(WHITE);
|
||||
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
|
||||
display->setColor(BLACK);
|
||||
if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) {
|
||||
int textX = boxLeft + hPadding + swingOffset;
|
||||
display->drawString(textX, lineY - 1, lineBuffer);
|
||||
} else {
|
||||
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer);
|
||||
}
|
||||
display->setColor(WHITE);
|
||||
lineY += effectiveLineHeight;
|
||||
} else {
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
|
||||
display->setColor(WHITE);
|
||||
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer);
|
||||
lineY += effectiveLineHeight;
|
||||
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
|
||||
display->drawString(textX, lineY, lineBuffer);
|
||||
}
|
||||
|
||||
lineY += effectiveLineHeight;
|
||||
}
|
||||
|
||||
// Scroll Bar (Thicker, inside box, not over title)
|
||||
if (totalLines > visibleTotalLines) {
|
||||
const uint8_t scrollBarWidth = 5;
|
||||
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
|
||||
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
|
||||
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
|
||||
|
||||
float ratio = (float)visibleTotalLines / totalLines;
|
||||
uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
|
||||
float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
|
||||
uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
|
||||
|
||||
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
|
||||
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
|
||||
}
|
||||
|
||||
#else
|
||||
// Default (non-M5STACK) layout
|
||||
if (needs_bell) {
|
||||
if (isHighResolution && boxWidth <= 150)
|
||||
boxWidth += 26;
|
||||
@ -647,7 +652,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
||||
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
|
||||
boxHeight += (isHighResolution) ? 2 : 1;
|
||||
|
||||
// === Draw Box ===
|
||||
// Draw Box
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2);
|
||||
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
|
||||
@ -663,46 +668,57 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
||||
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
|
||||
display->setColor(WHITE);
|
||||
|
||||
// === Draw Content ===
|
||||
// Draw Content
|
||||
int16_t lineY = boxTop + vPadding;
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
|
||||
if (needs_bell && i == 0) {
|
||||
int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2;
|
||||
display->drawXbm(textX - 10, bellY, 8, 8, bell_alert);
|
||||
display->drawXbm(textX + lineWidths[i] + 2, bellY, 8, 8, bell_alert);
|
||||
}
|
||||
char lineBuffer[lineLengths[i] + 1];
|
||||
strncpy(lineBuffer, lines[i], lineLengths[i]);
|
||||
lineBuffer[lineLengths[i]] = '\0';
|
||||
// Determine if this is a pop-up or a pick list
|
||||
if (alertBannerOptions > 0 && i == 0) {
|
||||
// Pick List
|
||||
display->setColor(WHITE);
|
||||
int background_yOffset = 1;
|
||||
// Determine if we have low hanging characters
|
||||
if (strchr(lineBuffer, 'p') || strchr(lineBuffer, 'g') || strchr(lineBuffer, 'y') || strchr(lineBuffer, 'j')) {
|
||||
background_yOffset = -1;
|
||||
|
||||
bool isSignalLine = (bannerSignalBars >= 0 && strstr(lineBuffer, "Signal:") != nullptr);
|
||||
|
||||
if (isSignalLine) {
|
||||
// Center text + signal bars as a single group
|
||||
const int totalBars = 5;
|
||||
const int barWidth = 3;
|
||||
const int barSpacing = 2;
|
||||
const int barHeightStep = 2;
|
||||
|
||||
int textWidth = display->getStringWidth(lineBuffer, strlen(lineBuffer), true);
|
||||
int barsWidth = totalBars * barWidth + (totalBars - 1) * barSpacing + 6;
|
||||
int totalWidth = textWidth + barsWidth;
|
||||
|
||||
int groupStartX = boxLeft + (boxWidth - totalWidth) / 2;
|
||||
|
||||
display->drawString(groupStartX, lineY, lineBuffer);
|
||||
|
||||
int baseX = groupStartX + textWidth + 6;
|
||||
int baseY = lineY + effectiveLineHeight - 1;
|
||||
|
||||
for (int b = 0; b < totalBars; b++) {
|
||||
int barHeight = (b + 1) * barHeightStep;
|
||||
int x = baseX + b * (barWidth + barSpacing);
|
||||
int y = baseY - barHeight;
|
||||
|
||||
if (b < bannerSignalBars) {
|
||||
display->fillRect(x, y, barWidth, barHeight);
|
||||
} else {
|
||||
display->drawRect(x, y, barWidth, barHeight);
|
||||
}
|
||||
}
|
||||
display->fillRect(boxLeft, boxTop + 1, boxWidth, effectiveLineHeight - background_yOffset);
|
||||
display->setColor(BLACK);
|
||||
int yOffset = 3;
|
||||
display->drawString(textX, lineY - yOffset, lineBuffer);
|
||||
display->setColor(WHITE);
|
||||
lineY += (effectiveLineHeight - 2 - background_yOffset);
|
||||
} else {
|
||||
// Pop-up
|
||||
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
|
||||
display->drawString(textX, lineY, lineBuffer);
|
||||
lineY += (effectiveLineHeight);
|
||||
}
|
||||
|
||||
lineY += effectiveLineHeight;
|
||||
}
|
||||
|
||||
// === Scroll Bar (Thicker, inside box, not over title) ===
|
||||
// Scroll Bar (Thicker, inside box, not over title)
|
||||
if (totalLines > visibleTotalLines) {
|
||||
const uint8_t scrollBarWidth = 5;
|
||||
|
||||
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
|
||||
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight; // start after title line
|
||||
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
|
||||
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
|
||||
|
||||
float ratio = (float)visibleTotalLines / totalLines;
|
||||
|
||||
@ -44,6 +44,10 @@ extern MessageStore messageStore;
|
||||
// Remove Canned message screen if no action is taken for some milliseconds
|
||||
#define INACTIVATE_AFTER_MS 20000
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
extern int bannerSignalBars;
|
||||
}
|
||||
extern ScanI2C::DeviceAddress cardkb_found;
|
||||
extern bool graphics::isMuted;
|
||||
extern bool osk_found;
|
||||
@ -2167,7 +2171,7 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
|
||||
} else if (isAck) {
|
||||
// Relay ACK → mark as RELAYED, still no final ACK
|
||||
this->ack = false;
|
||||
waitingForAck = false; // don’t wait for more
|
||||
waitingForAck = false;
|
||||
} else {
|
||||
// Explicit failure
|
||||
this->ack = false;
|
||||
@ -2202,18 +2206,33 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
|
||||
const char *channelName = channels.getName(this->channel);
|
||||
const char *nodeName = getNodeName(this->incoming);
|
||||
|
||||
// Signal quality calculation (Good / Fair / Bad)
|
||||
const char *qualityLabel;
|
||||
int bars = 0;
|
||||
|
||||
if (lastRxSnr > -7 && lastRxRssi > -110) {
|
||||
qualityLabel = "Good";
|
||||
bars = 4;
|
||||
} else if (lastRxSnr > -15 && lastRxRssi > -120) {
|
||||
qualityLabel = "Fair";
|
||||
bars = 2;
|
||||
} else {
|
||||
qualityLabel = "Bad";
|
||||
bars = 1;
|
||||
}
|
||||
|
||||
if (this->ack) {
|
||||
if (this->lastSentNode == NODENUM_BROADCAST) {
|
||||
snprintf(buf, sizeof(buf), "Message sent to\n#%s\n\nSNR: %.1f dB RSSI: %d dBm",
|
||||
(channelName && channelName[0]) ? channelName : "unknown", this->lastRxSnr, this->lastRxRssi);
|
||||
snprintf(buf, sizeof(buf), "Message sent to\n#%s\n\nSignal: %s",
|
||||
(channelName && channelName[0]) ? channelName : "unknown", qualityLabel);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "DM sent to\n@%s\n\nSNR: %.1f dB RSSI: %d dBm",
|
||||
(nodeName && nodeName[0]) ? nodeName : "unknown", this->lastRxSnr, this->lastRxRssi);
|
||||
snprintf(buf, sizeof(buf), "DM sent to\n@%s\n\nSignal: %s",
|
||||
(nodeName && nodeName[0]) ? nodeName : "unknown", qualityLabel);
|
||||
}
|
||||
} else if (isAck && !isFromDest) {
|
||||
// Relay ACK banner
|
||||
snprintf(buf, sizeof(buf), "DM Relayed\n(Status Unknown)\n%s\n\nSNR: %.1f dB RSSI: %d dBm",
|
||||
(nodeName && nodeName[0]) ? nodeName : "unknown", this->lastRxSnr, this->lastRxRssi);
|
||||
snprintf(buf, sizeof(buf), "DM Relayed\n(Status Unknown)\n%s\n\nSignal: %s",
|
||||
(nodeName && nodeName[0]) ? nodeName : "unknown", qualityLabel);
|
||||
} else {
|
||||
if (this->lastSentNode == NODENUM_BROADCAST) {
|
||||
snprintf(buf, sizeof(buf), "Message failed to\n#%s",
|
||||
@ -2225,7 +2244,8 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
|
||||
|
||||
opts.message = buf;
|
||||
opts.durationMs = 3000;
|
||||
screen->showOverlayBanner(opts);
|
||||
graphics::bannerSignalBars = bars; // tell banner renderer how many bars to draw
|
||||
screen->showOverlayBanner(opts); // this triggers drawNotificationBox()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user