mirror of
https://github.com/pelgraine/Meck.git
synced 2026-08-09 18:22:45 +02:00
Added basic Public channel only view message history and compose - bugs still present
This commit is contained in:
@@ -4,6 +4,22 @@
|
||||
#include "variant.h" // Board-specific defines (HAS_GPS, etc.)
|
||||
#include "target.h" // For sensors, board, etc.
|
||||
|
||||
// T-Deck Pro Keyboard support
|
||||
#if defined(LilyGo_TDeck_Pro)
|
||||
#include "TCA8418Keyboard.h"
|
||||
TCA8418Keyboard keyboard(I2C_ADDR_KEYBOARD, &Wire);
|
||||
|
||||
// Compose mode state
|
||||
static bool composeMode = false;
|
||||
static char composeBuffer[138]; // 137 chars max + null terminator
|
||||
static int composePos = 0;
|
||||
|
||||
void initKeyboard();
|
||||
void handleKeyboardInput();
|
||||
void drawComposeScreen();
|
||||
void sendComposedMessage();
|
||||
#endif
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
static uint32_t _atoi(const char* sp) {
|
||||
uint32_t n = 0;
|
||||
@@ -303,6 +319,11 @@ void setup() {
|
||||
MESH_DEBUG_PRINTLN("setup() - ui_task.begin() done");
|
||||
#endif
|
||||
|
||||
// Initialize T-Deck Pro keyboard
|
||||
#if defined(LilyGo_TDeck_Pro)
|
||||
initKeyboard();
|
||||
#endif
|
||||
|
||||
// Enable GPS by default on T-Deck Pro
|
||||
#if HAS_GPS
|
||||
// Set GPS enabled in both sensor manager and node prefs
|
||||
@@ -319,7 +340,232 @@ void loop() {
|
||||
the_mesh.loop();
|
||||
sensors.loop();
|
||||
#ifdef DISPLAY_CLASS
|
||||
// Skip UITask rendering when in compose mode to prevent flickering
|
||||
#if defined(LilyGo_TDeck_Pro)
|
||||
if (!composeMode) {
|
||||
ui_task.loop();
|
||||
}
|
||||
#else
|
||||
ui_task.loop();
|
||||
#endif
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
// Handle T-Deck Pro keyboard input
|
||||
#if defined(LilyGo_TDeck_Pro)
|
||||
handleKeyboardInput();
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// T-DECK PRO KEYBOARD FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
#if defined(LilyGo_TDeck_Pro)
|
||||
|
||||
void initKeyboard() {
|
||||
// Keyboard uses the same I2C bus as other peripherals (already initialized)
|
||||
if (keyboard.begin()) {
|
||||
MESH_DEBUG_PRINTLN("setup() - Keyboard initialized");
|
||||
composeBuffer[0] = '\0';
|
||||
composePos = 0;
|
||||
composeMode = false;
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("setup() - Keyboard initialization failed!");
|
||||
}
|
||||
}
|
||||
|
||||
void handleKeyboardInput() {
|
||||
if (!keyboard.isReady()) return;
|
||||
|
||||
char key = keyboard.readKey();
|
||||
if (key == 0) return;
|
||||
|
||||
Serial.printf("handleKeyboardInput: key='%c' (0x%02X) composeMode=%d\n",
|
||||
key >= 32 ? key : '?', key, composeMode);
|
||||
|
||||
if (composeMode) {
|
||||
// In compose mode - handle text input
|
||||
if (key == '\r') {
|
||||
// Enter - send the message
|
||||
Serial.println("Compose: Enter pressed, sending...");
|
||||
if (composePos > 0) {
|
||||
sendComposedMessage();
|
||||
}
|
||||
composeMode = false;
|
||||
composeBuffer[0] = '\0';
|
||||
composePos = 0;
|
||||
ui_task.gotoHomeScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == '\b') {
|
||||
// Backspace - check if shift was recently pressed for cancel combo
|
||||
if (keyboard.wasShiftRecentlyPressed(500)) {
|
||||
// Shift+Backspace = Cancel
|
||||
Serial.println("Compose: Shift+Backspace, cancelling...");
|
||||
composeMode = false;
|
||||
composeBuffer[0] = '\0';
|
||||
composePos = 0;
|
||||
ui_task.gotoHomeScreen();
|
||||
return;
|
||||
}
|
||||
// Regular backspace - delete last character
|
||||
if (composePos > 0) {
|
||||
composePos--;
|
||||
composeBuffer[composePos] = '\0';
|
||||
Serial.printf("Compose: Backspace, pos now %d\n", composePos);
|
||||
drawComposeScreen();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular character input
|
||||
if (key >= 32 && key < 127 && composePos < 137) {
|
||||
composeBuffer[composePos++] = key;
|
||||
composeBuffer[composePos] = '\0';
|
||||
Serial.printf("Compose: Added '%c', pos now %d\n", key, composePos);
|
||||
drawComposeScreen();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal mode - not composing
|
||||
switch (key) {
|
||||
case 'c':
|
||||
case 'C':
|
||||
// Enter compose mode
|
||||
composeMode = true;
|
||||
composeBuffer[0] = '\0';
|
||||
composePos = 0;
|
||||
Serial.println("Entering compose mode");
|
||||
drawComposeScreen();
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
case 'M':
|
||||
// Go to channel message screen
|
||||
Serial.println("Opening channel messages");
|
||||
ui_task.gotoChannelScreen();
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
case 'W':
|
||||
case 'a':
|
||||
case 'A':
|
||||
// Navigate left/previous
|
||||
Serial.println("Nav: Previous");
|
||||
ui_task.injectKey(0xF2); // KEY_PREV
|
||||
break;
|
||||
|
||||
case 's':
|
||||
case 'S':
|
||||
case 'd':
|
||||
case 'D':
|
||||
// Navigate right/next
|
||||
Serial.println("Nav: Next");
|
||||
ui_task.injectKey(0xF1); // KEY_NEXT
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
// Select/Enter
|
||||
Serial.println("Nav: Enter/Select");
|
||||
ui_task.injectKey(13); // KEY_ENTER
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
case 'Q':
|
||||
case '\b':
|
||||
// Go back to home screen
|
||||
Serial.println("Nav: Back to home");
|
||||
ui_task.gotoHomeScreen();
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
// Space - also acts as next/select
|
||||
Serial.println("Nav: Space (Next)");
|
||||
ui_task.injectKey(0xF1); // KEY_NEXT
|
||||
break;
|
||||
|
||||
default:
|
||||
Serial.printf("Unhandled key in normal mode: '%c' (0x%02X)\n", key, key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void drawComposeScreen() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
display.startFrame();
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setCursor(0, 0);
|
||||
display.print("Compose - Public Channel");
|
||||
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(0, 11, display.width(), 1);
|
||||
|
||||
display.setCursor(0, 14);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
|
||||
// Word wrap the compose buffer
|
||||
int x = 0;
|
||||
int y = 14;
|
||||
int charsPerLine = display.width() / 6;
|
||||
char charStr[2] = {0, 0}; // Buffer for single character as string
|
||||
|
||||
for (int i = 0; i < composePos; i++) {
|
||||
charStr[0] = composeBuffer[i];
|
||||
display.print(charStr);
|
||||
x++;
|
||||
if (x >= charsPerLine) {
|
||||
x = 0;
|
||||
y += 11;
|
||||
display.setCursor(0, y);
|
||||
}
|
||||
}
|
||||
|
||||
// Show cursor
|
||||
display.print("_");
|
||||
|
||||
// Status bar
|
||||
int statusY = display.height() - 12;
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(0, statusY - 2, display.width(), 1);
|
||||
display.setCursor(0, statusY);
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
|
||||
char status[50];
|
||||
sprintf(status, "%d/137 Enter:Send Sh+Del:Cancel", composePos);
|
||||
display.print(status);
|
||||
|
||||
display.endFrame();
|
||||
#endif
|
||||
}
|
||||
|
||||
void sendComposedMessage() {
|
||||
if (composePos == 0) return;
|
||||
|
||||
MESH_DEBUG_PRINTLN("Sending message: %s", composeBuffer);
|
||||
|
||||
// Get the Public channel (index 0)
|
||||
ChannelDetails channel;
|
||||
if (the_mesh.getChannel(0, channel)) {
|
||||
uint32_t timestamp = rtc_clock.getCurrentTime();
|
||||
|
||||
// Send to channel
|
||||
if (the_mesh.sendGroupMessage(timestamp, channel.channel,
|
||||
the_mesh.getNodePrefs()->node_name,
|
||||
composeBuffer, composePos)) {
|
||||
MESH_DEBUG_PRINTLN("Message sent to Public channel");
|
||||
ui_task.showAlert("Sent!", 1500);
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("Failed to send message");
|
||||
ui_task.showAlert("Send failed!", 1500);
|
||||
}
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("Could not get Public channel");
|
||||
ui_task.showAlert("No channel!", 1500);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // LilyGo_TDeck_Pro
|
||||
@@ -0,0 +1,238 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ui/UIScreen.h>
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
#include <MeshCore.h>
|
||||
|
||||
// Maximum messages to store in history
|
||||
#define CHANNEL_MSG_HISTORY_SIZE 20
|
||||
#define CHANNEL_MSG_TEXT_LEN 160
|
||||
|
||||
class UITask; // Forward declaration
|
||||
|
||||
class ChannelScreen : public UIScreen {
|
||||
public:
|
||||
struct ChannelMessage {
|
||||
uint32_t timestamp;
|
||||
uint8_t path_len;
|
||||
char text[CHANNEL_MSG_TEXT_LEN];
|
||||
bool valid;
|
||||
};
|
||||
|
||||
private:
|
||||
UITask* _task;
|
||||
mesh::RTCClock* _rtc;
|
||||
|
||||
ChannelMessage _messages[CHANNEL_MSG_HISTORY_SIZE];
|
||||
int _msgCount; // Total messages stored
|
||||
int _newestIdx; // Index of newest message (circular buffer)
|
||||
int _scrollPos; // Current scroll position (0 = newest)
|
||||
int _msgsPerPage; // Messages that fit on screen
|
||||
|
||||
public:
|
||||
ChannelScreen(UITask* task, mesh::RTCClock* rtc)
|
||||
: _task(task), _rtc(rtc), _msgCount(0), _newestIdx(-1), _scrollPos(0), _msgsPerPage(3) {
|
||||
// Initialize all messages as invalid
|
||||
for (int i = 0; i < CHANNEL_MSG_HISTORY_SIZE; i++) {
|
||||
_messages[i].valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new message to the history
|
||||
void addMessage(uint8_t path_len, const char* sender, const char* text) {
|
||||
// Move to next slot in circular buffer
|
||||
_newestIdx = (_newestIdx + 1) % CHANNEL_MSG_HISTORY_SIZE;
|
||||
|
||||
ChannelMessage* msg = &_messages[_newestIdx];
|
||||
msg->timestamp = _rtc->getCurrentTime();
|
||||
msg->path_len = path_len;
|
||||
msg->valid = true;
|
||||
|
||||
// The text already contains "Sender: message" format, just store it
|
||||
strncpy(msg->text, text, CHANNEL_MSG_TEXT_LEN - 1);
|
||||
msg->text[CHANNEL_MSG_TEXT_LEN - 1] = '\0';
|
||||
|
||||
if (_msgCount < CHANNEL_MSG_HISTORY_SIZE) {
|
||||
_msgCount++;
|
||||
}
|
||||
|
||||
// Reset scroll to show newest message
|
||||
_scrollPos = 0;
|
||||
}
|
||||
|
||||
int getMessageCount() const { return _msgCount; }
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
char tmp[32];
|
||||
|
||||
// Header
|
||||
display.setCursor(0, 0);
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.print("Public Channel");
|
||||
|
||||
// Message count on right
|
||||
sprintf(tmp, "[%d]", _msgCount);
|
||||
display.setCursor(display.width() - display.getTextWidth(tmp) - 2, 0);
|
||||
display.print(tmp);
|
||||
|
||||
// Divider line
|
||||
display.drawRect(0, 11, display.width(), 1);
|
||||
|
||||
if (_msgCount == 0) {
|
||||
display.setCursor(0, 25);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.print("No messages yet");
|
||||
display.setCursor(0, 40);
|
||||
display.print("Press C to compose");
|
||||
} else {
|
||||
int lineHeight = 10;
|
||||
int headerHeight = 14;
|
||||
int footerHeight = 14;
|
||||
int availableHeight = display.height() - headerHeight - footerHeight;
|
||||
|
||||
// Calculate chars per line based on display width
|
||||
int charsPerLine = display.width() / 6;
|
||||
|
||||
int y = headerHeight;
|
||||
|
||||
// Display messages from scroll position
|
||||
int msgsDrawn = 0;
|
||||
for (int i = 0; i + _scrollPos < _msgCount && y < display.height() - footerHeight - lineHeight; i++) {
|
||||
// Calculate index in circular buffer
|
||||
int idx = _newestIdx - _scrollPos - i;
|
||||
while (idx < 0) idx += CHANNEL_MSG_HISTORY_SIZE;
|
||||
idx = idx % CHANNEL_MSG_HISTORY_SIZE;
|
||||
|
||||
if (!_messages[idx].valid) continue;
|
||||
|
||||
ChannelMessage* msg = &_messages[idx];
|
||||
|
||||
// Time indicator with hop count
|
||||
display.setCursor(0, y);
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
|
||||
uint32_t age = _rtc->getCurrentTime() - msg->timestamp;
|
||||
if (age < 60) {
|
||||
sprintf(tmp, "(%d) %ds", msg->path_len == 0xFF ? 0 : msg->path_len, age);
|
||||
} else if (age < 3600) {
|
||||
sprintf(tmp, "(%d) %dm", msg->path_len == 0xFF ? 0 : msg->path_len, age / 60);
|
||||
} else if (age < 86400) {
|
||||
sprintf(tmp, "(%d) %dh", msg->path_len == 0xFF ? 0 : msg->path_len, age / 3600);
|
||||
} else {
|
||||
sprintf(tmp, "(%d) %dd", msg->path_len == 0xFF ? 0 : msg->path_len, age / 86400);
|
||||
}
|
||||
display.print(tmp);
|
||||
y += lineHeight;
|
||||
|
||||
// Message text with word wrap - the text already contains "Sender: message"
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
|
||||
int textLen = strlen(msg->text);
|
||||
int pos = 0;
|
||||
int linesForThisMsg = 0;
|
||||
int maxLinesPerMsg = 3; // Allow up to 3 lines per message
|
||||
|
||||
while (pos < textLen && linesForThisMsg < maxLinesPerMsg && y < display.height() - footerHeight - 2) {
|
||||
display.setCursor(0, y);
|
||||
|
||||
// Find how much text fits on this line
|
||||
int lineEnd = pos + charsPerLine;
|
||||
if (lineEnd >= textLen) {
|
||||
lineEnd = textLen;
|
||||
} else {
|
||||
// Try to break at a space
|
||||
int lastSpace = -1;
|
||||
for (int j = pos; j < lineEnd && j < textLen; j++) {
|
||||
if (msg->text[j] == ' ') lastSpace = j;
|
||||
}
|
||||
if (lastSpace > pos) lineEnd = lastSpace;
|
||||
}
|
||||
|
||||
// Print this line segment
|
||||
char lineBuf[42];
|
||||
int lineLen = lineEnd - pos;
|
||||
if (lineLen > 40) lineLen = 40;
|
||||
strncpy(lineBuf, msg->text + pos, lineLen);
|
||||
lineBuf[lineLen] = '\0';
|
||||
display.print(lineBuf);
|
||||
|
||||
pos = lineEnd;
|
||||
// Skip space at start of next line
|
||||
while (pos < textLen && msg->text[pos] == ' ') pos++;
|
||||
|
||||
y += lineHeight;
|
||||
linesForThisMsg++;
|
||||
}
|
||||
|
||||
// If we truncated, show ellipsis indicator
|
||||
if (pos < textLen && linesForThisMsg >= maxLinesPerMsg) {
|
||||
// Message was truncated - could add "..." but space is tight
|
||||
}
|
||||
|
||||
y += 2; // Small gap between messages
|
||||
msgsDrawn++;
|
||||
_msgsPerPage = msgsDrawn;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer with scroll indicator and controls
|
||||
int footerY = display.height() - 12;
|
||||
display.drawRect(0, footerY - 2, display.width(), 1);
|
||||
display.setCursor(0, footerY);
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
|
||||
// Left side: Q:Exit
|
||||
display.print("Q:Exit");
|
||||
|
||||
// Middle: scroll position indicator
|
||||
if (_msgCount > _msgsPerPage) {
|
||||
int endMsg = _scrollPos + _msgsPerPage;
|
||||
if (endMsg > _msgCount) endMsg = _msgCount;
|
||||
sprintf(tmp, "%d-%d/%d", _scrollPos + 1, endMsg, _msgCount);
|
||||
// Center it roughly
|
||||
int midX = display.width() / 2 - 15;
|
||||
display.setCursor(midX, footerY);
|
||||
display.print(tmp);
|
||||
}
|
||||
|
||||
// Right side: controls
|
||||
sprintf(tmp, "W/S:Scrl C:New");
|
||||
display.setCursor(display.width() - display.getTextWidth(tmp) - 2, footerY);
|
||||
display.print(tmp);
|
||||
|
||||
#if AUTO_OFF_MILLIS == 0 // e-ink
|
||||
return 5000; // Refresh every 5s
|
||||
#else
|
||||
return 1000; // Refresh every 1s for time updates
|
||||
#endif
|
||||
}
|
||||
|
||||
bool handleInput(char c) override {
|
||||
// KEY_PREV (0xF2) or 'w' - scroll up (older messages)
|
||||
if (c == 0xF2 || c == 'w' || c == 'W') {
|
||||
if (_scrollPos + _msgsPerPage < _msgCount) {
|
||||
_scrollPos++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// KEY_NEXT (0xF1) or 's' - scroll down (newer messages)
|
||||
if (c == 0xF1 || c == 's' || c == 'S') {
|
||||
if (_scrollPos > 0) {
|
||||
_scrollPos--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// KEY_ENTER or 'c' - compose (handled by main.cpp keyboard handler)
|
||||
// 'q' - go back (handled by main.cpp keyboard handler)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset scroll position to newest
|
||||
void resetScroll() {
|
||||
_scrollPos = 0;
|
||||
}
|
||||
};
|
||||
@@ -30,6 +30,7 @@
|
||||
#endif
|
||||
|
||||
#include "icons.h"
|
||||
#include "ChannelScreen.h"
|
||||
|
||||
class SplashScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
@@ -580,6 +581,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
splash = new SplashScreen(this);
|
||||
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs);
|
||||
msg_preview = new MsgPreviewScreen(this, &rtc_clock);
|
||||
channel_screen = new ChannelScreen(this, &rtc_clock);
|
||||
setCurrScreen(splash);
|
||||
}
|
||||
|
||||
@@ -628,7 +630,12 @@ void UITask::msgRead(int msgcount) {
|
||||
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
|
||||
_msgcount = msgcount;
|
||||
|
||||
// Add to preview screen (for notifications)
|
||||
((MsgPreviewScreen *) msg_preview)->addPreview(path_len, from_name, text);
|
||||
|
||||
// Also add to channel history screen
|
||||
((ChannelScreen *) channel_screen)->addMessage(path_len, from_name, text);
|
||||
|
||||
setCurrScreen(msg_preview);
|
||||
|
||||
if (_display != NULL) {
|
||||
@@ -922,3 +929,25 @@ void UITask::toggleBuzzer() {
|
||||
_next_refresh = 0; // trigger refresh
|
||||
#endif
|
||||
}
|
||||
|
||||
void UITask::injectKey(char c) {
|
||||
if (c != 0 && curr) {
|
||||
// Turn on display if it's off
|
||||
if (_display != NULL && !_display->isOn()) {
|
||||
_display->turnOn();
|
||||
}
|
||||
curr->handleInput(c);
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
|
||||
_next_refresh = 100; // trigger refresh
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::gotoChannelScreen() {
|
||||
((ChannelScreen *) channel_screen)->resetScroll();
|
||||
setCurrScreen(channel_screen);
|
||||
if (_display != NULL && !_display->isOn()) {
|
||||
_display->turnOn();
|
||||
}
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
_next_refresh = 100;
|
||||
}
|
||||
@@ -51,6 +51,7 @@ class UITask : public AbstractUITask {
|
||||
UIScreen* splash;
|
||||
UIScreen* home;
|
||||
UIScreen* msg_preview;
|
||||
UIScreen* channel_screen; // Channel message history screen
|
||||
UIScreen* curr;
|
||||
|
||||
void userLedHandler();
|
||||
@@ -73,15 +74,23 @@ public:
|
||||
void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs);
|
||||
|
||||
void gotoHomeScreen() { setCurrScreen(home); }
|
||||
void gotoChannelScreen(); // Navigate to channel message screen
|
||||
void showAlert(const char* text, int duration_millis);
|
||||
int getMsgCount() const { return _msgcount; }
|
||||
bool hasDisplay() const { return _display != NULL; }
|
||||
bool isButtonPressed() const;
|
||||
bool isOnChannelScreen() const { return curr == channel_screen; }
|
||||
|
||||
void toggleBuzzer();
|
||||
bool getGPSState();
|
||||
void toggleGPS();
|
||||
|
||||
// Inject a key press from external source (e.g., keyboard)
|
||||
void injectKey(char c);
|
||||
|
||||
// Get current screen for checking state
|
||||
UIScreen* getCurrentScreen() const { return curr; }
|
||||
UIScreen* getMsgPreviewScreen() const { return msg_preview; }
|
||||
|
||||
// from AbstractUITask
|
||||
void msgRead(int msgcount) override;
|
||||
@@ -90,4 +99,4 @@ public:
|
||||
void loop() override;
|
||||
|
||||
void shutdown(bool restart = false);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user