Multi message storage (#8182)

* First try at multimessage storage and display

* Nrf built issue fix

* Message view mode

* Add channel name instead of channel slot

* trunk fix

* Fix for DM threading

* fix for message time

* rename of view mode to Conversations

* Reply in thread feature

* rename Select View Mode to Select Conversation

* dismiss all live fix

* Messages from phone show on screen

* Decoupled message packets from screen.cpp and cleaned up

* Cannedmessage cleanup and emotes fixed

* Ack on messages sent

* Ack message cleanup

* Dismiss feature fixed

* removed legacy temporary messages

* Emote picker fix

* Memory size debug

* Build error fix

* Sanity checks are okay sometimes

* Lengthen channel name and finalize cleanup removal of Broadcast

* Change DM to @ in order to unify on a single method

* Continue unifying display, also show message status on the "isMine" lines

* Add context for incoming messages

* Better to say "in" vs "on"

* crash fix for confirmation nodes

* Fix outbound labels based to avoid creating delays

* Eink autoscroll dissabled

* gating for message storage when not using a screen

* revert

* Build fail fix

* Don't error out with unset MAC address in unit tests

* Provide some extra spacing for low hanging characters in messages

* Reorder menu options and reword Respond

* Reword menus to better reflect actions

* Go to thread from favorite screen

* Reorder Favorite Action Menu with simple word modifications

* Consolidate wording on "Chats"

* Mute channel fix

* trunk fix

* Clean up how muting works along with when we wake the screen

* Fix builds for HELTEC_MESH_SOLAR

* Signal bars for message ack

* fix for notification renderer

* Remove duplicate code, fix more Chats, and fix C6L MessageRenderer

* Fix to many warnings related to BaseUI

* preset aware signal strength display

* More C6L fixes and clean up header lines

* Use text aligns for message layout where necessary

* Attempt to fix memory usage of invalidLifetime

* Update channel mute for adjusted protobuf

* Missed a comma in merge conflicts

* cleanup to get more space

* Trunk fixes

* Optimize Hi Rez Chirpy to save space

* more fixes

* More cleanup

* Remove used getConversationWith

* Remove unused dismissNewestMessage

* Fix another build error on occassion

* Dimiss key combo function deprecated

* More cleanup

* Fn symbol code removed

* Waypoint cleanup

* Trunk fix

* Fixup Waypoint screen with BaseUI code

* Implement Haruki's ClockRenderer and broadcast decomposeTime across various files.

* Revert "Implement Haruki's ClockRenderer and broadcast decomposeTime across various files."

This reverts commit 2f65721774.

* Implement Haruki's ClockRenderer and broadcast decomposeTime across various files. Attempt 2!

* remove memory usage debug

* Revert only RangeTestModule.cpp change

* Switch from dynamic std::string storage to fixed-size char[]

* Removing old left over code

* More optimization

* Free Heap when not on Message screen

* build error fixes

* Restore ellipsis to end of long names

* Remove legacy function renderMessageContent

* improved destination filtering

* force PKI

* cleanup

* Shorten longNames to not exceed message popups

* log messages sent from apps

* Trunk fix

* Improve layout of messages screen

* Fix potential crash for undefined variable

* Revert changes to RedirectablePrint.cpp

* Apply shortening to longNames in Select Destination

* Fix short name displays

* Fix sprintfOverlappingData issue

* Fix nullPointerRedundantCheck warning on ESP32

* Add "Delete All Chats" to all chat views

* Improve getSafeNodeName / sanitizeString code.

* Improve getSafeNodeName further

* Restore auto favorite; but only if not CLIENT_BASE

* Don't favorite if WE are CLIENT_BASE role

* Don't run message persistent in MUI

* Fix broken endifs

* Unkwnown nodes no longer show as ??? on message  thread

* More delete options and cleanup of code

* fix for delete this chat

* Message menu cleanup

* trunk fix

* Clean up some menu options and remove some Unit C6L ifdefines

* Rework Delete flow

* Desperate times call for desperate measures

* Create a background on the connected icon to reduce overlap impact

* Optimize code for background image

* Fix for Muzi_Base

* Trunk Fixes

* Remove the up/down shortcut to launch canned messages (#8370)

* Remove the up/down shortcut to launch canned messages

* Enabled MQTT and WEBSERVER by default (#8679)

Signed-off-by: kur1k0 <zhuzirun@m5stack.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>

---------

Signed-off-by: kur1k0 <zhuzirun@m5stack.com>
Co-authored-by: Riker <zhuzirun@m5stack.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* Correct string length calculation for signal bars

* Manual message scrolling

* Fix

* Restore CannedMessages on Home Frame

* UpDown situational destination for textMessage

* Correct up/down destinations on textMessage frame

* Update Screen.h for handleTextMessage

* Update Screen.cpp to repair a merge issue

* Add nudge scroll on UpDownEncoder devices.

* Set nodeName to maximum size

* Revert "Set nodeName to maximum size"

This reverts commit e254f39925.

* Reflow Node Lists and TLora Pager Views (#8942)

* Add files via upload

* Move files into the right place

* Short or Long Names for everyone!

* Add scrolling to Node list

* Pagination fix for Latest to oldest per page

* Page counters

* Dynamic scaling of column counts based upon screen size, clean up box drawing

* Reflow Node Lists and TLora Pager Views (#8942)

* Add files via upload

* Move files into the right place

* Short or Long Names for everyone!

* Add scrolling to Node list

* Pagination fix for Latest to oldest per page

* Page counters

* Dynamic scaling of column counts based upon screen size, clean up box drawing

* Update exempt labels for stale bot workflow

Adds triaged and backlog to the list of exempt labels.

* Update naming of Frame Visibility toggles

* Fix to scrolling

* Fix for content cutting off when from us

* Fix for "delete this chat" now it does delete the current one

* Rework isHighResolution to be an enum called ScreenResolution

* Migrate Unit C6L macro guards into currentResolution UltraLow checks

* Mistakes happen - restoring NodeList Renderer line

---------

Signed-off-by: kur1k0 <zhuzirun@m5stack.com>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Riker <zhuzirun@m5stack.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: whywilson <m.tools@qq.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
This commit is contained in:
HarukiToreda
2025-12-24 17:13:31 -05:00
committed by GitHub
parent e5c3eda2a2
commit 9da4396c6f
37 changed files with 3337 additions and 1656 deletions

View File

@@ -46,6 +46,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#include "FSCommon.h"
#include "MeshService.h"
#include "MessageStore.h"
#include "RadioLibInterface.h"
#include "error.h"
#include "gps/GeoCoord.h"
@@ -64,10 +65,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "modules/WaypointModule.h"
#include "sleep.h"
#include "target_specific.h"
using graphics::Emote;
using graphics::emotes;
using graphics::numEmotes;
extern MessageStore messageStore;
#if USE_TFTDISPLAY
extern uint16_t TFT_MESH;
@@ -119,10 +117,6 @@ uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100};
// we'll need to hold onto pointers for the modules that can draw a frame.
std::vector<MeshModule *> moduleFrames;
// Global variables for screen function overlay symbols
std::vector<std::string> functionSymbol;
std::string functionSymbolString;
#if HAS_GPS
// GeoCoord object for the screen
GeoCoord geoCoord;
@@ -263,19 +257,11 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
} else {
// otherwise, just display the module frame that's aligned with the current frame
module_frame = state->currentFrame;
// LOG_DEBUG("Screen is not in transition. Frame: %d", module_frame);
}
// LOG_DEBUG("Draw Module Frame %d", module_frame);
MeshModule &pi = *moduleFrames.at(module_frame);
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.
*
@@ -322,16 +308,16 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
{
graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
LOG_INFO("Protobuf Value uiconfig.screen_rgb_color: %d", uiconfig.screen_rgb_color);
int32_t rawRGB = uiconfig.screen_rgb_color;
if (rawRGB > 0 && rawRGB <= 255255255) {
uint8_t TFT_MESH_r = (rawRGB >> 16) & 0xFF;
uint8_t TFT_MESH_g = (rawRGB >> 8) & 0xFF;
uint8_t TFT_MESH_b = rawRGB & 0xFF;
LOG_INFO("Values of r,g,b: %d, %d, %d", TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
if (TFT_MESH_r <= 255 && TFT_MESH_g <= 255 && TFT_MESH_b <= 255) {
TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
// Only validate the combined value once
if (rawRGB > 0 && rawRGB <= 255255255) {
// Extract each component as a normal int first
int r = (rawRGB >> 16) & 0xFF;
int g = (rawRGB >> 8) & 0xFF;
int b = rawRGB & 0xFF;
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
TFT_MESH = COLOR565(static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b));
}
}
@@ -550,10 +536,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)
@@ -565,7 +551,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<AutoOLEDWire *>(dispdev)->setDetected(model);
@@ -587,7 +573,7 @@ void Screen::setup()
static_cast<ST7796Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
// === Initialize display and UI system ===
// Initialize display and UI system
ui->init();
displayWidth = dispdev->width();
displayHeight = dispdev->height();
@@ -599,7 +585,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<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
@@ -607,20 +593,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
@@ -636,10 +622,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
@@ -657,7 +643,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]);
@@ -666,9 +652,9 @@ 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());
graphics::currentResolution = graphics::determineScreenResolution(dispdev->height(), dispdev->width());
ui->update();
#ifndef USE_EINK
ui->update(); // Some SSD1306 clones drop the first draw, so run twice
@@ -689,7 +675,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);
@@ -697,12 +683,14 @@ void Screen::setup()
#if !MESHTASTIC_EXCLUDE_ADMIN
adminMessageObserver.observe(adminModule);
#endif
if (textMessageModule)
textMessageObserver.observe(textMessageModule);
if (inputBroker)
inputObserver.observe(inputBroker);
// === Notify modules that support UI events ===
// Load persisted messages into RAM
messageStore.loadFromFlash();
LOG_INFO("MessageStore loaded from flash");
// Notify modules that support UI events
MeshModule::observeUIEvents(&uiFrameEventObserver);
}
@@ -773,6 +761,23 @@ int32_t Screen::runOnce()
if (displayHeight == 0) {
displayHeight = dispdev->getHeight();
}
// Detect frame transitions and clear message cache when leaving text message screen
{
static int8_t lastFrameIndex = -1;
int8_t currentFrameIndex = ui->getUiState()->currentFrame;
int8_t textMsgIndex = framesetInfo.positions.textMessage;
if (lastFrameIndex != -1 && currentFrameIndex != lastFrameIndex) {
if (lastFrameIndex == textMsgIndex && currentFrameIndex != textMsgIndex) {
graphics::MessageRenderer::clearMessageCache();
}
}
lastFrameIndex = currentFrameIndex;
}
menuHandler::handleMenuSwitch(dispdev);
// Show boot screen for first logo_timeout seconds, then switch to normal operation.
@@ -828,17 +833,17 @@ int32_t Screen::runOnce()
break;
case Cmd::ON_PRESS:
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
handleOnPress();
showFrame(FrameDirection::NEXT);
}
break;
case Cmd::SHOW_PREV_FRAME:
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
handleShowPrevFrame();
showFrame(FrameDirection::PREVIOUS);
}
break;
case Cmd::SHOW_NEXT_FRAME:
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
handleShowNextFrame();
showFrame(FrameDirection::NEXT);
}
break;
case Cmd::START_ALERT_FRAME: {
@@ -859,6 +864,7 @@ int32_t Screen::runOnce()
break;
case Cmd::STOP_ALERT_FRAME:
NotificationRenderer::pauseBanner = false;
break;
case Cmd::STOP_BOOT_SCREEN:
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
@@ -1029,9 +1035,6 @@ void Screen::setFrames(FrameFocus focus)
}
#endif
// Declare this early so its available in FOCUS_PRESERVE block
bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
if (!hiddenFrames.home) {
fsi.positions.home = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
@@ -1043,11 +1046,16 @@ void Screen::setFrames(FrameFocus focus)
indicatorIcons.push_back(icon_mail);
#ifndef USE_EINK
if (!hiddenFrames.nodelist) {
fsi.positions.nodelist = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
if (!hiddenFrames.nodelist_nodes) {
fsi.positions.nodelist_nodes = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicListScreen_Nodes;
indicatorIcons.push_back(icon_nodes);
}
if (!hiddenFrames.nodelist_location) {
fsi.positions.nodelist_location = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicListScreen_Location;
indicatorIcons.push_back(icon_list);
}
#endif
// Show detailed node views only on E-Ink builds
@@ -1069,11 +1077,13 @@ void Screen::setFrames(FrameFocus focus)
}
#endif
#if HAS_GPS
#ifdef USE_EINK
if (!hiddenFrames.nodelist_bearings) {
fsi.positions.nodelist_bearings = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
indicatorIcons.push_back(icon_list);
}
#endif
if (!hiddenFrames.gps) {
fsi.positions.gps = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
@@ -1173,7 +1183,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);
@@ -1193,10 +1203,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
@@ -1239,8 +1245,11 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
void Screen::toggleFrameVisibility(const std::string &frameName)
{
#ifndef USE_EINK
if (frameName == "nodelist") {
hiddenFrames.nodelist = !hiddenFrames.nodelist;
if (frameName == "nodelist_nodes") {
hiddenFrames.nodelist_nodes = !hiddenFrames.nodelist_nodes;
}
if (frameName == "nodelist_location") {
hiddenFrames.nodelist_location = !hiddenFrames.nodelist_location;
}
#endif
#ifdef USE_EINK
@@ -1255,9 +1264,11 @@ void Screen::toggleFrameVisibility(const std::string &frameName)
}
#endif
#if HAS_GPS
#ifdef USE_EINK
if (frameName == "nodelist_bearings") {
hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
}
#endif
if (frameName == "gps") {
hiddenFrames.gps = !hiddenFrames.gps;
}
@@ -1279,8 +1290,10 @@ void Screen::toggleFrameVisibility(const std::string &frameName)
bool Screen::isFrameHidden(const std::string &frameName) const
{
#ifndef USE_EINK
if (frameName == "nodelist")
return hiddenFrames.nodelist;
if (frameName == "nodelist_nodes")
return hiddenFrames.nodelist_nodes;
if (frameName == "nodelist_location")
return hiddenFrames.nodelist_location;
#endif
#ifdef USE_EINK
if (frameName == "nodelist_lastheard")
@@ -1291,8 +1304,10 @@ bool Screen::isFrameHidden(const std::string &frameName) const
return hiddenFrames.nodelist_distance;
#endif
#if HAS_GPS
#ifdef USE_EINK
if (frameName == "nodelist_bearings")
return hiddenFrames.nodelist_bearings;
#endif
if (frameName == "gps")
return hiddenFrames.gps;
#endif
@@ -1308,37 +1323,6 @@ bool Screen::isFrameHidden(const std::string &frameName) const
return false;
}
// Dismisses the currently displayed screen frame, if possible
// Relevant for text message, waypoint, others in future?
// Triggered with a CardKB keycombo
void Screen::hideCurrentFrame()
{
uint8_t currentFrame = ui->getUiState()->currentFrame;
bool dismissed = false;
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
LOG_INFO("Hide Text Message");
devicestate.has_rx_text_message = false;
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
} else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
LOG_DEBUG("Hide Waypoint");
devicestate.has_rx_waypoint = false;
hiddenFrames.waypoint = true;
dismissed = true;
} else if (currentFrame == framesetInfo.positions.wifi) {
LOG_DEBUG("Hide WiFi Screen");
hiddenFrames.wifi = true;
dismissed = true;
} else if (currentFrame == framesetInfo.positions.lora) {
LOG_INFO("Hide LoRa");
hiddenFrames.lora = true;
dismissed = true;
}
if (dismissed) {
setFrames(FOCUS_DEFAULT); // You could also use FOCUS_PRESERVE
}
}
void Screen::handleStartFirmwareUpdateScreen()
{
LOG_DEBUG("Show firmware screen");
@@ -1391,28 +1375,6 @@ void Screen::decreaseBrightness()
/* TO DO: add little popup in center of screen saying what brightness level it is set to*/
}
void Screen::setFunctionSymbol(std::string sym)
{
if (std::find(functionSymbol.begin(), functionSymbol.end(), sym) == functionSymbol.end()) {
functionSymbol.push_back(sym);
functionSymbolString = "";
for (auto symbol : functionSymbol) {
functionSymbolString = symbol + " " + functionSymbolString;
}
setFastFramerate();
}
}
void Screen::removeFunctionSymbol(std::string sym)
{
functionSymbol.erase(std::remove(functionSymbol.begin(), functionSymbol.end(), sym), functionSymbol.end());
functionSymbolString = "";
for (auto symbol : functionSymbol) {
functionSymbolString = symbol + " " + functionSymbolString;
}
setFastFramerate();
}
void Screen::handleOnPress()
{
// If screen was off, just wake it, otherwise advance to next frame
@@ -1424,23 +1386,17 @@ void Screen::handleOnPress()
}
}
void Screen::handleShowPrevFrame()
void Screen::showFrame(FrameDirection direction)
{
// If screen was off, just wake it, otherwise go back to previous frame
// If we are in a transition, the press must have bounced, drop it.
// Only advance frames when UI is stable
if (ui->getUiState()->frameState == FIXED) {
ui->previousFrame();
lastScreenTransition = millis();
setFastFramerate();
}
}
void Screen::handleShowNextFrame()
{
// If screen was off, just wake it, otherwise advance to next frame
// If we are in a transition, the press must have bounced, drop it.
if (ui->getUiState()->frameState == FIXED) {
ui->nextFrame();
if (direction == FrameDirection::NEXT) {
ui->nextFrame();
} else {
ui->previousFrame();
}
lastScreenTransition = millis();
setFastFramerate();
}
@@ -1466,7 +1422,6 @@ void Screen::setFastFramerate()
int Screen::handleStatusUpdate(const meshtastic::Status *arg)
{
// LOG_DEBUG("Screen got status update %d", arg->getStatusType());
switch (arg->getStatusType()) {
case STATUS_TYPE_NODE:
if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
@@ -1584,11 +1539,11 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
screen->showSimpleBanner(banner, 3000);
} else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
if (currentResolution == ScreenResolution::UltraLow) {
strcpy(banner, "New Message");
} else {
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
}
} else {
strcpy(banner, "New Message");
}
@@ -1624,16 +1579,26 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event)
if (showingNormalScreen) {
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET) {
setFrames(FOCUS_MODULE);
}
// Regenerate the frameset, while Attempt to maintain focus on the current frame
else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND)
// Regenerate the frameset, while attempting to maintain focus on the current frame
else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND) {
setFrames(FOCUS_PRESERVE);
}
// Don't regenerate the frameset, just re-draw whatever is on screen ASAP
else if (event->action == UIFrameEvent::Action::REDRAW_ONLY)
else if (event->action == UIFrameEvent::Action::REDRAW_ONLY) {
setFastFramerate();
}
// Jump directly to the Text Message screen
else if (event->action == UIFrameEvent::Action::SWITCH_TO_TEXTMESSAGE) {
setFrames(FOCUS_PRESERVE); // preserve current frame ordering
ui->switchToFrame(framesetInfo.positions.textMessage);
setFastFramerate(); // force redraw ASAP
}
}
return 0;
@@ -1671,7 +1636,48 @@ int Screen::handleInputEvent(const InputEvent *event)
menuHandler::handleMenuSwitch(dispdev);
return 0;
}
// UP/DOWN in message screen scrolls through message threads
if (ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
if (event->inputEvent == INPUT_BROKER_UP) {
if (messageStore.getMessages().empty()) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else {
graphics::MessageRenderer::scrollUp();
setFastFramerate(); // match existing behavior
return 0;
}
}
if (event->inputEvent == INPUT_BROKER_DOWN) {
if (messageStore.getMessages().empty()) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else {
graphics::MessageRenderer::scrollDown();
setFastFramerate();
return 0;
}
}
}
// UP/DOWN in node list screens scrolls through node pages
if (ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_nodes ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_location ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) {
if (event->inputEvent == INPUT_BROKER_UP) {
graphics::NodeListRenderer::scrollUp();
setFastFramerate();
return 0;
}
if (event->inputEvent == INPUT_BROKER_DOWN) {
graphics::NodeListRenderer::scrollDown();
setFastFramerate();
return 0;
}
}
// Use left or right input from a keyboard to move between frames,
// so long as a mesh module isn't using these events for some other purpose
if (showingNormalScreen) {
@@ -1685,16 +1691,39 @@ int Screen::handleInputEvent(const InputEvent *event)
// If no modules are using the input, move between frames
if (!inputIntercepted) {
#if defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2
bool handledEncoderScroll = false;
const bool isTextMessageFrame = (framesetInfo.positions.textMessage != 255 &&
this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage &&
!messageStore.getMessages().empty());
if (isTextMessageFrame) {
if (event->inputEvent == INPUT_BROKER_UP_LONG) {
graphics::MessageRenderer::nudgeScroll(-1);
handledEncoderScroll = true;
} else if (event->inputEvent == INPUT_BROKER_DOWN_LONG) {
graphics::MessageRenderer::nudgeScroll(1);
handledEncoderScroll = true;
}
}
if (handledEncoderScroll) {
setFastFramerate();
return 0;
}
#endif
if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS) {
showPrevFrame();
showFrame(FrameDirection::PREVIOUS);
} else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
showNextFrame();
showFrame(FrameDirection::NEXT);
} else if (event->inputEvent == INPUT_BROKER_UP_LONG) {
// Long press up button for fast frame switching
showPrevFrame();
} else if (event->inputEvent == INPUT_BROKER_DOWN_LONG) {
// Long press down button for fast frame switching
showNextFrame();
} else if ((event->inputEvent == INPUT_BROKER_UP || event->inputEvent == INPUT_BROKER_DOWN) &&
this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else if (event->inputEvent == INPUT_BROKER_SELECT) {
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
menuHandler::homeBaseMenu();
@@ -1709,20 +1738,21 @@ int Screen::handleInputEvent(const InputEvent *event)
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
menuHandler::loraMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
if (devicestate.rx_text_message.from) {
if (!messageStore.getMessages().empty()) {
menuHandler::messageResponseMenu();
} else {
#if defined(M5STACK_UNITC6L)
menuHandler::textMessageMenu();
#else
menuHandler::textMessageBaseMenu();
#endif
if (currentResolution == ScreenResolution::UltraLow) {
menuHandler::textMessageMenu();
} else {
menuHandler::textMessageBaseMenu();
}
}
} else if (framesetInfo.positions.firstFavorite != 255 &&
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
menuHandler::favoriteBaseMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist ||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_nodes ||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_location ||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
@@ -1733,7 +1763,7 @@ int Screen::handleInputEvent(const InputEvent *event)
menuHandler::wifiBaseMenu();
}
} else if (event->inputEvent == INPUT_BROKER_BACK) {
showPrevFrame();
showFrame(FrameDirection::PREVIOUS);
} else if (event->inputEvent == INPUT_BROKER_CANCEL) {
setOn(false);
}