mirror of
https://github.com/meshtastic/firmware.git
synced 2025-08-06 13:44:46 +00:00
Add BaseUI menus to add and remove Favorited Nodes
This commit is contained in:
parent
428ca0972f
commit
3291824353
@ -175,12 +175,14 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
|
|||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
||||||
#endif
|
#endif
|
||||||
|
nodeDB->pause_sort(true);
|
||||||
// Store the message and set the expiration timestamp
|
// Store the message and set the expiration timestamp
|
||||||
strncpy(NotificationRenderer::alertBannerMessage, message, 255);
|
strncpy(NotificationRenderer::alertBannerMessage, message, 255);
|
||||||
NotificationRenderer::alertBannerMessage[255] = '\0'; // Ensure null termination
|
NotificationRenderer::alertBannerMessage[255] = '\0'; // Ensure null termination
|
||||||
NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
|
NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
|
||||||
NotificationRenderer::alertBannerCallback = bannerCallback;
|
NotificationRenderer::alertBannerCallback = bannerCallback;
|
||||||
NotificationRenderer::pauseBanner = false;
|
NotificationRenderer::pauseBanner = false;
|
||||||
|
NotificationRenderer::curSelected = 0;
|
||||||
NotificationRenderer::current_notification_type = notificationTypeEnum::node_picker;
|
NotificationRenderer::current_notification_type = notificationTypeEnum::node_picker;
|
||||||
|
|
||||||
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
||||||
|
@ -343,4 +343,30 @@ const int *getTextPositions(OLEDDisplay *display)
|
|||||||
return textPositions;
|
return textPositions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isAllowedPunctuation(char c)
|
||||||
|
{
|
||||||
|
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
|
||||||
|
return allowed.find(c) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string sanitizeString(const std::string &input)
|
||||||
|
{
|
||||||
|
std::string output;
|
||||||
|
bool inReplacement = false;
|
||||||
|
|
||||||
|
for (char c : input) {
|
||||||
|
if (std::isalnum(static_cast<unsigned char>(c)) || isAllowedPunctuation(c)) {
|
||||||
|
output += c;
|
||||||
|
inReplacement = false;
|
||||||
|
} else {
|
||||||
|
if (!inReplacement) {
|
||||||
|
output += 0xbf; // ISO-8859-1 for inverted question mark
|
||||||
|
inReplacement = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <OLEDDisplay.h>
|
#include <OLEDDisplay.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
@ -52,4 +53,8 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
|
|
||||||
const int *getTextPositions(OLEDDisplay *display);
|
const int *getTextPositions(OLEDDisplay *display);
|
||||||
|
|
||||||
|
bool isAllowedPunctuation(char c);
|
||||||
|
|
||||||
|
std::string sanitizeString(const std::string &input);
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "buzz.h"
|
#include "buzz.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/draw/UIRenderer.h"
|
#include "graphics/draw/UIRenderer.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "modules/AdminModule.h"
|
#include "modules/AdminModule.h"
|
||||||
@ -384,13 +385,13 @@ void menuHandler::favoriteBaseMenu()
|
|||||||
static const char **optionsArrayPtr;
|
static const char **optionsArrayPtr;
|
||||||
|
|
||||||
if (kb_found) {
|
if (kb_found) {
|
||||||
static const char *optionsArray[] = {"Back", "New Preset Msg", "New Freetext Msg"};
|
static const char *optionsArray[] = {"Back", "New Preset Msg", "New Freetext Msg", "Remove Favorite"};
|
||||||
|
optionsArrayPtr = optionsArray;
|
||||||
|
options = 4;
|
||||||
|
} else {
|
||||||
|
static const char *optionsArray[] = {"Back", "New Preset Msg", "Remove Favorite"};
|
||||||
optionsArrayPtr = optionsArray;
|
optionsArrayPtr = optionsArray;
|
||||||
options = 3;
|
options = 3;
|
||||||
} else {
|
|
||||||
static const char *optionsArray[] = {"Back", "New Preset Msg"};
|
|
||||||
optionsArrayPtr = optionsArray;
|
|
||||||
options = 2;
|
|
||||||
}
|
}
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
bannerOptions.message = "Favorites Action";
|
bannerOptions.message = "Favorites Action";
|
||||||
@ -399,8 +400,12 @@ void menuHandler::favoriteBaseMenu()
|
|||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
if (selected == 1) {
|
if (selected == 1) {
|
||||||
cannedMessageModule->LaunchWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
|
cannedMessageModule->LaunchWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
|
||||||
} else if (selected == 2) {
|
} else if (selected == 2 && kb_found) {
|
||||||
cannedMessageModule->LaunchFreetextWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
|
cannedMessageModule->LaunchFreetextWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
|
||||||
|
} else if ((!kb_found && selected == 2) || (selected == 3 && kb_found)) {
|
||||||
|
menuHandler::menuQueue = menuHandler::remove_favorite;
|
||||||
|
screen->setInterval(0);
|
||||||
|
runASAP = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
@ -438,13 +443,15 @@ void menuHandler::positionBaseMenu()
|
|||||||
|
|
||||||
void menuHandler::nodeListMenu()
|
void menuHandler::nodeListMenu()
|
||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back", "Reset NodeDB"};
|
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset NodeDB"};
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
bannerOptions.message = "Node Action";
|
bannerOptions.message = "Node Action";
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 2;
|
bannerOptions.optionsCount = 3;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
if (selected == 1) {
|
if (selected == 1) {
|
||||||
|
menuQueue = add_favorite;
|
||||||
|
} else if (selected == 2) {
|
||||||
menuQueue = reset_node_db_menu;
|
menuQueue = reset_node_db_menu;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -618,13 +625,13 @@ void menuHandler::TFTColorPickerMenu()
|
|||||||
|
|
||||||
void menuHandler::rebootMenu()
|
void menuHandler::rebootMenu()
|
||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Yes", "No"};
|
static const char *optionsArray[] = {"Back", "Confirm"};
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
bannerOptions.message = "Reboot Device?";
|
bannerOptions.message = "Reboot Device?";
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 2;
|
bannerOptions.optionsCount = 2;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
if (selected == 0) {
|
if (selected == 1) {
|
||||||
IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0));
|
IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0));
|
||||||
nodeDB->saveToDisk();
|
nodeDB->saveToDisk();
|
||||||
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
||||||
@ -633,6 +640,37 @@ void menuHandler::rebootMenu()
|
|||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void menuHandler::addFavoriteMenu()
|
||||||
|
{
|
||||||
|
screen->showNodePicker("Node To Favorite", 30000, [](int nodenum) -> void {
|
||||||
|
LOG_WARN("Nodenum: %u", nodenum);
|
||||||
|
nodeDB->set_favorite(true, nodenum);
|
||||||
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void menuHandler::removeFavoriteMenu()
|
||||||
|
{
|
||||||
|
|
||||||
|
static const char *optionsArray[] = {"Back", "Yes"};
|
||||||
|
BannerOverlayOptions bannerOptions;
|
||||||
|
std::string message = "Unfavorite This Node?\n";
|
||||||
|
auto node = nodeDB->getMeshNode(graphics::UIRenderer::currentFavoriteNodeNum);
|
||||||
|
if (node && node->has_user) {
|
||||||
|
message += sanitizeString(node->user.long_name).substr(0, 15);
|
||||||
|
}
|
||||||
|
bannerOptions.message = message.c_str();
|
||||||
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
|
bannerOptions.optionsCount = 2;
|
||||||
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
|
if (selected == 1) {
|
||||||
|
nodeDB->set_favorite(false, graphics::UIRenderer::currentFavoriteNodeNum);
|
||||||
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
screen->showOverlayBanner(bannerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
void menuHandler::handleMenuSwitch()
|
void menuHandler::handleMenuSwitch()
|
||||||
{
|
{
|
||||||
switch (menuQueue) {
|
switch (menuQueue) {
|
||||||
@ -677,6 +715,12 @@ void menuHandler::handleMenuSwitch()
|
|||||||
case reboot_menu:
|
case reboot_menu:
|
||||||
rebootMenu();
|
rebootMenu();
|
||||||
break;
|
break;
|
||||||
|
case add_favorite:
|
||||||
|
addFavoriteMenu();
|
||||||
|
break;
|
||||||
|
case remove_favorite:
|
||||||
|
removeFavoriteMenu();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
menuQueue = menu_none;
|
menuQueue = menu_none;
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,9 @@ class menuHandler
|
|||||||
buzzermodemenupicker,
|
buzzermodemenupicker,
|
||||||
mui_picker,
|
mui_picker,
|
||||||
tftcolormenupicker,
|
tftcolormenupicker,
|
||||||
reboot_menu
|
reboot_menu,
|
||||||
|
add_favorite,
|
||||||
|
remove_favorite
|
||||||
};
|
};
|
||||||
static screenMenus menuQueue;
|
static screenMenus menuQueue;
|
||||||
|
|
||||||
@ -42,6 +44,8 @@ class menuHandler
|
|||||||
static void nodeListMenu();
|
static void nodeListMenu();
|
||||||
static void resetNodeDBMenu();
|
static void resetNodeDBMenu();
|
||||||
static void rebootMenu();
|
static void rebootMenu();
|
||||||
|
static void addFavoriteMenu();
|
||||||
|
static void removeFavoriteMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
@ -60,11 +60,125 @@ void NotificationRenderer::resetBanner()
|
|||||||
{
|
{
|
||||||
alertBannerMessage[0] = '\0';
|
alertBannerMessage[0] = '\0';
|
||||||
current_notification_type = notificationTypeEnum::none;
|
current_notification_type = notificationTypeEnum::none;
|
||||||
|
nodeDB->pause_sort(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
|
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||||
{
|
{
|
||||||
|
switch (current_notification_type) {
|
||||||
|
case notificationTypeEnum::text_banner:
|
||||||
|
case notificationTypeEnum::selection_picker:
|
||||||
drawAlertBannerOverlay(display, state);
|
drawAlertBannerOverlay(display, state);
|
||||||
|
break;
|
||||||
|
case notificationTypeEnum::node_picker:
|
||||||
|
drawNodePicker(display, state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||||
|
{
|
||||||
|
static uint32_t selectedNodenum = 0;
|
||||||
|
|
||||||
|
if (!isOverlayBannerShowing() || pauseBanner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// === Layout Configuration ===
|
||||||
|
constexpr uint16_t vPadding = 2;
|
||||||
|
alertBannerOptions = nodeDB->getNumMeshNodes() - 1;
|
||||||
|
|
||||||
|
// let the box drawing function calculate the widths?
|
||||||
|
|
||||||
|
const char *lineStarts[MAX_LINES + 1] = {0};
|
||||||
|
uint16_t lineCount = 0;
|
||||||
|
|
||||||
|
// Parse lines
|
||||||
|
char *alertEnd = alertBannerMessage + strnlen(alertBannerMessage, sizeof(alertBannerMessage));
|
||||||
|
lineStarts[lineCount] = alertBannerMessage;
|
||||||
|
|
||||||
|
while ((lineCount < MAX_LINES) && (lineStarts[lineCount] < alertEnd)) {
|
||||||
|
lineStarts[lineCount + 1] = std::find((char *)lineStarts[lineCount], alertEnd, '\n');
|
||||||
|
if (lineStarts[lineCount + 1][0] == '\n')
|
||||||
|
lineStarts[lineCount + 1] += 1;
|
||||||
|
lineCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle input
|
||||||
|
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) {
|
||||||
|
curSelected--;
|
||||||
|
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) {
|
||||||
|
curSelected++;
|
||||||
|
} else if (inEvent == INPUT_BROKER_SELECT) {
|
||||||
|
resetBanner();
|
||||||
|
alertBannerCallback(selectedNodenum);
|
||||||
|
|
||||||
|
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
|
||||||
|
resetBanner();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curSelected == -1)
|
||||||
|
curSelected = alertBannerOptions - 1;
|
||||||
|
if (curSelected == alertBannerOptions)
|
||||||
|
curSelected = 0;
|
||||||
|
|
||||||
|
inEvent = INPUT_BROKER_NONE;
|
||||||
|
if (alertBannerMessage[0] == '\0')
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint16_t totalLines = lineCount + alertBannerOptions;
|
||||||
|
uint16_t screenHeight = display->height();
|
||||||
|
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
|
||||||
|
uint8_t visibleTotalLines = std::min<uint8_t>(totalLines, (screenHeight - vPadding * 2) / effectiveLineHeight);
|
||||||
|
uint8_t linesShown = lineCount;
|
||||||
|
const char *linePointers[visibleTotalLines + 1] = {0}; // this is sort of a dynamic allocation
|
||||||
|
|
||||||
|
// copy the linestarts to display to the linePointers holder
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
linePointers[i] = lineStarts[i];
|
||||||
|
}
|
||||||
|
char scratchLineBuffer[visibleTotalLines - lineCount][40];
|
||||||
|
LOG_WARN("Requestion %u buffers", visibleTotalLines - lineCount);
|
||||||
|
|
||||||
|
uint8_t firstOptionToShow = 0;
|
||||||
|
if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) {
|
||||||
|
if (curSelected > alertBannerOptions - visibleTotalLines + lineCount)
|
||||||
|
firstOptionToShow = alertBannerOptions - visibleTotalLines + lineCount;
|
||||||
|
else
|
||||||
|
firstOptionToShow = curSelected - 1;
|
||||||
|
} else {
|
||||||
|
firstOptionToShow = 0;
|
||||||
|
}
|
||||||
|
int scratchLineNum = 0;
|
||||||
|
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
||||||
|
char temp_name[16] = {0};
|
||||||
|
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
|
||||||
|
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
|
||||||
|
strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF));
|
||||||
|
}
|
||||||
|
// make temp buffer for name
|
||||||
|
// fi
|
||||||
|
if (i == curSelected) {
|
||||||
|
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
|
||||||
|
if (isHighResolution) {
|
||||||
|
strncpy(scratchLineBuffer[scratchLineNum], "> ", 3);
|
||||||
|
strncpy(scratchLineBuffer[scratchLineNum] + 2, temp_name, 36);
|
||||||
|
strncpy(scratchLineBuffer[scratchLineNum] + strlen(temp_name) + 2, " <", 3);
|
||||||
|
} else {
|
||||||
|
strncpy(scratchLineBuffer[scratchLineNum], ">", 2);
|
||||||
|
strncpy(scratchLineBuffer[scratchLineNum] + 1, temp_name, 37);
|
||||||
|
strncpy(scratchLineBuffer[scratchLineNum] + strlen(temp_name) + 1, "<", 2);
|
||||||
|
}
|
||||||
|
scratchLineBuffer[scratchLineNum][39] = '\0';
|
||||||
|
} else {
|
||||||
|
strncpy(scratchLineBuffer[scratchLineNum], temp_name, 36);
|
||||||
|
}
|
||||||
|
linePointers[linesShown] = scratchLineBuffer[scratchLineNum++];
|
||||||
|
LOG_WARN("Using buffer %u", scratchLineNum);
|
||||||
|
}
|
||||||
|
drawNotificationBox(display, state, linePointers, totalLines, firstOptionToShow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||||
@ -75,10 +189,6 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
|||||||
// === Layout Configuration ===
|
// === Layout Configuration ===
|
||||||
constexpr uint16_t vPadding = 2;
|
constexpr uint16_t vPadding = 2;
|
||||||
|
|
||||||
// Setup font and alignment
|
|
||||||
display->setFont(FONT_SMALL);
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
|
|
||||||
uint16_t optionWidths[alertBannerOptions] = {0};
|
uint16_t optionWidths[alertBannerOptions] = {0};
|
||||||
uint16_t maxWidth = 0;
|
uint16_t maxWidth = 0;
|
||||||
uint16_t arrowsWidth = display->getStringWidth("> <", 4, true);
|
uint16_t arrowsWidth = display->getStringWidth("> <", 4, true);
|
||||||
@ -191,6 +301,7 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
|||||||
void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[],
|
void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[],
|
||||||
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth)
|
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth)
|
||||||
{
|
{
|
||||||
|
|
||||||
bool is_picker = false;
|
bool is_picker = false;
|
||||||
uint16_t lineCount = 0;
|
uint16_t lineCount = 0;
|
||||||
// === Layout Configuration ===
|
// === Layout Configuration ===
|
||||||
@ -202,7 +313,10 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
|
|
||||||
if (maxWidth != 0)
|
if (maxWidth != 0)
|
||||||
is_picker = true;
|
is_picker = true;
|
||||||
// seelction box
|
|
||||||
|
// Setup font and alignment
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
while (lines[lineCount] != nullptr) {
|
while (lines[lineCount] != nullptr) {
|
||||||
auto newlinePointer = strchr(lines[lineCount], '\n');
|
auto newlinePointer = strchr(lines[lineCount], '\n');
|
||||||
|
@ -24,6 +24,7 @@ class NotificationRenderer
|
|||||||
static void resetBanner();
|
static void resetBanner();
|
||||||
static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state);
|
static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
|
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
|
static void drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1],
|
static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1],
|
||||||
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
|
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
|
||||||
|
|
||||||
|
@ -18,32 +18,6 @@
|
|||||||
#include <RTC.h>
|
#include <RTC.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
bool isAllowedPunctuation(char c)
|
|
||||||
{
|
|
||||||
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
|
|
||||||
return allowed.find(c) != std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string sanitizeString(const std::string &input)
|
|
||||||
{
|
|
||||||
std::string output;
|
|
||||||
bool inReplacement = false;
|
|
||||||
|
|
||||||
for (char c : input) {
|
|
||||||
if (std::isalnum(static_cast<unsigned char>(c)) || isAllowedPunctuation(c)) {
|
|
||||||
output += c;
|
|
||||||
inReplacement = false;
|
|
||||||
} else {
|
|
||||||
if (!inReplacement) {
|
|
||||||
output += 0xbf; // ISO-8859-1 for inverted question mark
|
|
||||||
inReplacement = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !MESHTASTIC_EXCLUDE_GPS
|
#if !MESHTASTIC_EXCLUDE_GPS
|
||||||
|
|
||||||
// External variables
|
// External variables
|
||||||
|
@ -1694,9 +1694,24 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||||
|
if (lite && lite->is_favorite != is_favorite) {
|
||||||
|
lite->is_favorite = is_favorite;
|
||||||
|
sortMeshDB();
|
||||||
|
saveNodeDatabaseToDisk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeDB::pause_sort(bool paused)
|
||||||
|
{
|
||||||
|
sortingIsPaused = paused;
|
||||||
|
}
|
||||||
|
|
||||||
void NodeDB::sortMeshDB()
|
void NodeDB::sortMeshDB()
|
||||||
{
|
{
|
||||||
if (lastSort == 0 || !Throttle::isWithinTimespanMs(lastSort, 1000 * 5)) {
|
if (!sortingIsPaused && (lastSort == 0 || !Throttle::isWithinTimespanMs(lastSort, 1000 * 5))) {
|
||||||
lastSort = millis();
|
lastSort = millis();
|
||||||
bool changed = true;
|
bool changed = true;
|
||||||
while (changed) { // dumb reverse bubble sort, but probably not bad for what we're doing
|
while (changed) { // dumb reverse bubble sort, but probably not bad for what we're doing
|
||||||
|
@ -191,6 +191,16 @@ class NodeDB
|
|||||||
*/
|
*/
|
||||||
bool updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex = 0);
|
bool updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex = 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets a node either favorite or unfavorite
|
||||||
|
*/
|
||||||
|
void set_favorite(bool is_favorite, uint32_t nodeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other functions like the node picker can request a pause in the node sorting
|
||||||
|
*/
|
||||||
|
void pause_sort(bool paused);
|
||||||
|
|
||||||
/// @return our node number
|
/// @return our node number
|
||||||
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
|
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
|
||||||
|
|
||||||
@ -283,6 +293,11 @@ class NodeDB
|
|||||||
/// Find a node in our DB, create an empty NodeInfoLite if missing
|
/// Find a node in our DB, create an empty NodeInfoLite if missing
|
||||||
meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n);
|
meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal boolean to track sorting paused
|
||||||
|
*/
|
||||||
|
bool sortingIsPaused = false;
|
||||||
|
|
||||||
/// pick a provisional nodenum we hope no one is using
|
/// pick a provisional nodenum we hope no one is using
|
||||||
void pickNewNodeNum();
|
void pickNewNodeNum();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user