уведомления: режимы по каналам, тихие часы с часовым поясом, отключение автовключения дисплея
Run Unit Tests / test (push) Canceled after 0s
Build and deploy Docs site to GitHub Pages / github-pages (push) Canceled after 0s
PR Build Check / build (Heltec_v3_companion_radio_ble) (push) Canceled after 0s
PR Build Check / build (Heltec_v3_repeater) (push) Canceled after 0s
PR Build Check / build (Heltec_v3_room_server) (push) Canceled after 0s
PR Build Check / build (LilyGo_Tlora_C6_repeater_) (push) Canceled after 0s
PR Build Check / build (PicoW_repeater) (push) Canceled after 0s
PR Build Check / build (RAK_4631_companion_radio_ble) (push) Canceled after 0s
PR Build Check / build (RAK_4631_companion_radio_ethernet) (push) Canceled after 0s
PR Build Check / build (RAK_4631_repeater) (push) Canceled after 0s
PR Build Check / build (RAK_4631_repeater_ethernet) (push) Canceled after 0s
PR Build Check / build (RAK_4631_room_server) (push) Canceled after 0s
PR Build Check / build (RAK_4631_room_server_ethernet) (push) Canceled after 0s
PR Build Check / build (Tbeam_SX1276_repeater) (push) Canceled after 0s
PR Build Check / build (wio-e5-mini_repeater) (push) Canceled after 0s
PR Build Check / build (wio_wm1110_repeater) (push) Canceled after 0s

This commit is contained in:
2026-10-08 20:28:59 +03:00
parent 76e754f7ad
commit 28e802dcf0
5 changed files with 382 additions and 20 deletions
@@ -41,6 +41,11 @@ public:
void disableBluetooth() { _interfaceManager->disableBluetooth(); }
virtual void msgRead(int msgcount) = 0;
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0;
// То же, но без вывода на экран (тихий час или канал в режиме SILENT):
// сообщение попадает в непрочитанные, звука и всплытия нет.
virtual void newMsgSilent(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
newMsg(path_len, from_name, text, msgcount);
}
virtual void notify(UIEventType t = UIEventType::none) = 0;
virtual void loop() = 0;
};
+32 -14
View File
@@ -361,7 +361,10 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
}
} else {
#ifdef DISPLAY_CLASS
if (_ui) _ui->notify(UIEventType::newContactMessage);
// тихие часы глушат и уведомление о новом контакте
if (_ui && !_prefs.isQuietNow(getRTCClock()->getCurrentTime())) {
_ui->notify(UIEventType::newContactMessage);
}
#endif
}
@@ -468,9 +471,13 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
// we only want to show text messages on display, not cli data
bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN;
if (should_display && _ui) {
_ui->newMsg(path_len, from.name, text, offline_queue_len);
if (!_serial->isConnected()) {
_ui->notify(UIEventType::contactMessage);
bool quiet = _prefs.isQuietNow(getRTCClock()->getCurrentTime());
bool alert = _prefs.msg_popup && !quiet;
if (alert) {
if (!_serial->isConnected()) _ui->notify(UIEventType::contactMessage);
_ui->newMsg(path_len, from.name, text, offline_queue_len);
} else {
_ui->newMsgSilent(path_len, from.name, text, offline_queue_len);
}
}
#endif
@@ -573,19 +580,30 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
uint8_t frame[1];
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
_serial->writeFrame(frame, 1);
} else {
#ifdef DISPLAY_CLASS
if (_ui) _ui->notify(UIEventType::channelMessage);
#endif
}
#ifdef DISPLAY_CLASS
// Get the channel name from the channel index
const char *channel_name = "Unknown";
ChannelDetails channel_details;
if (getChannel(channel_idx, channel_details)) {
channel_name = channel_details.name;
// Настройки уведомлений: канал может быть в режиме POPUP / SILENT / OFF,
// плюс тихие часы глушат звук и всплытие для всех входящих.
uint8_t ch_mode = _prefs.getChannelMode(channel_idx);
if (ch_mode != CH_MODE_OFF) {
bool quiet = _prefs.isQuietNow(getRTCClock()->getCurrentTime());
bool alert = (ch_mode == CH_MODE_POPUP) && _prefs.msg_popup && !quiet;
// Get the channel name from the channel index
const char *channel_name = "Unknown";
ChannelDetails channel_details;
if (getChannel(channel_idx, channel_details)) {
channel_name = channel_details.name;
}
if (_ui) {
if (alert) {
if (!_serial->isConnected()) _ui->notify(UIEventType::channelMessage);
_ui->newMsg(path_len, channel_name, text, offline_queue_len);
} else {
_ui->newMsgSilent(path_len, channel_name, text, offline_queue_len);
}
}
}
if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len);
#endif
}
+58
View File
@@ -9,6 +9,18 @@
#define ADVERT_LOC_NONE 0
#define ADVERT_LOC_SHARE 1
// Режимы уведомлений по каналам (по 2 бита на канал)
#define CH_MODE_POPUP 0 // звук + экран переключается на сообщение
#define CH_MODE_SILENT 1 // в непрочитанные, но без звука и без всплытия
#define CH_MODE_OFF 2 // на устройстве не уведомляем вообще
// Сколько каналов покрываем настройками. Совпадает с MAX_GROUP_CHANNELS у companion-сборок,
// но этот заголовок его не включает, поэтому задаём свой предел.
#ifndef NOTIFY_MAX_CHANNELS
#define NOTIFY_MAX_CHANNELS 40
#endif
#define CH_MODE_BYTES ((NOTIFY_MAX_CHANNELS * 2 + 7) / 8)
class NodePrefs : public ConfigSerializer { // persisted to file
public:
float airtime_factor = 0;
@@ -42,6 +54,15 @@ public:
uint8_t default_scope_key[16];
char ui_theme[12]; // имя темы дисплея; пусто = тема по умолчанию для платы
// Уведомления о входящих (меню на первом экране)
uint8_t msg_popup = 1; // переключать экран на входящее сообщение
uint8_t msg_wake_display = 1; // зажигать дисплей при входящем сообщении
uint8_t quiet_en = 0; // часы тишины включены
uint8_t quiet_from = 23; // час начала (локальный)
uint8_t quiet_to = 7; // час окончания (локальный)
int8_t tz_offset = 0; // смещение от UTC в часах, -12..+14
uint8_t ch_mode[CH_MODE_BYTES]; // режимы по каналам (CH_MODE_*), по 2 бита
private:
class RadioPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now)
NodePrefs* _parent;
@@ -114,6 +135,14 @@ private:
def("auto_add", _parent->autoadd_config); // bitmask for auto-add contacts config
def("man_add", _parent->manual_add_contacts);
def("theme", _parent->ui_theme, sizeof(_parent->ui_theme)); // имя темы дисплея
// уведомления
def("msg_popup", _parent->msg_popup);
def("msg_wake", _parent->msg_wake_display);
def("quiet_en", _parent->quiet_en);
def("quiet_from", _parent->quiet_from);
def("quiet_to", _parent->quiet_to);
def("tz", _parent->tz_offset);
def("ch_mode", (void *)_parent->ch_mode, sizeof(_parent->ch_mode)); // hex-блоб
def("tel_base", _parent->telemetry_mode_base);
def("tel_loc", _parent->telemetry_mode_loc);
def("tel_env", _parent->telemetry_mode_env);
@@ -140,9 +169,38 @@ public:
node_name[0] = 0;
default_scope_name[0] = 0;
ui_theme[0] = 0;
memset(ch_mode, 0, sizeof(ch_mode)); // все каналы — CH_MODE_POPUP
memset(default_scope_key, 0, sizeof(default_scope_key));
}
// new accessor methods
bool isRepeatEn() const { return repeat.disable_fwd == 0; }
void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; }
// Настройки уведомлений по каналам
uint8_t getChannelMode(uint8_t idx) const {
if (idx >= NOTIFY_MAX_CHANNELS) return CH_MODE_POPUP;
uint8_t sh = (uint8_t)((idx & 3) * 2);
return (uint8_t)((ch_mode[idx >> 2] >> sh) & 0x03);
}
void setChannelMode(uint8_t idx, uint8_t mode) {
if (idx >= NOTIFY_MAX_CHANNELS) return;
uint8_t sh = (uint8_t)((idx & 3) * 2);
ch_mode[idx >> 2] = (uint8_t)((ch_mode[idx >> 2] & ~(0x03 << sh)) | ((mode & 0x03) << sh));
}
// Локальный час суток по unix-времени RTC и смещению часового пояса
uint8_t localHour(uint32_t utc) const {
int32_t h = (int32_t)((utc / 3600) % 24) + tz_offset;
h %= 24;
if (h < 0) h += 24;
return (uint8_t)h;
}
// Тихие часы: окно может пересекать полночь (например, 23..7)
bool isQuietNow(uint32_t utc) const {
if (!quiet_en) return false;
if (quiet_from == quiet_to) return false;
uint8_t h = localHour(utc);
if (quiet_from < quiet_to) return h >= quiet_from && h < quiet_to;
return h >= quiet_from || h < quiet_to;
}
};
+283 -6
View File
@@ -43,6 +43,25 @@
#endif
#define UI_RADIO_FREQ_STEP_KHZ 25 // шаг перестройки частоты
#define UI_RADIO_MENU_TIMEOUT_MILLIS 300000UL // 5 минут простоя — выйти из меню без сохранения
#define NOTIFY_MAIN_ROWS 7 // строк в меню уведомлений
// Строка меню: выбранная — инверсией, значение — справа вторичным цветом
static void drawMenuRow(DisplayDriver& display, int y, bool sel, const char* label, const char* value) {
if (sel) {
display.setColor(UIColor::primary_txt);
display.fillRect(0, y - 1, display.width(), 9);
display.setColor(UIColor::window_bkg);
display.drawTextLeftAlign(1, y, label);
if (value[0]) display.drawTextRightAlign(display.width() - 2, y, value);
} else {
display.setColor(UIColor::primary_txt);
display.drawTextLeftAlign(1, y, label);
if (value[0]) {
display.setColor(UIColor::secondary_txt);
display.drawTextRightAlign(display.width() - 2, y, value);
}
}
}
// BW перебирается по списку допустимых значений, а не шагом
static const float UI_RADIO_BW_STEPS[] = {
@@ -203,6 +222,47 @@ class HomeScreen : public UIScreen {
void renderThemeEditor(DisplayDriver& display);
uint8_t pageStep(int8_t dir);
// меню уведомлений (первый экран, длинное нажатие)
enum NotifyScreen { NOTIFY_OFF, NOTIFY_MAIN, NOTIFY_CHANNELS, NOTIFY_QUIET };
uint8_t _notify_screen;
uint8_t _notify_sel;
bool _notify_val_edit; // правка часа в подменю тихих часов
unsigned long _notify_last_activity;
void notifyEnter() {
_task->clearAlert();
_notify_screen = NOTIFY_MAIN;
_notify_sel = 0;
_notify_val_edit = false;
_notify_last_activity = millis();
}
void notifyLeave(bool save) {
_notify_screen = NOTIFY_OFF;
_notify_val_edit = false;
if (save) {
the_mesh.savePrefs();
_task->showAlert("Saved", 1000);
}
_task->showPendingMessages();
}
uint8_t alertsMode() const { // 0 = звук+вибро, 1 = только звук, 2 = только вибро, 3 = выкл
bool snd = _node_prefs->buzzer_quiet == 0, vib = _node_prefs->vibe_quiet == 0;
return (uint8_t)(snd ? (vib ? 0 : 1) : (vib ? 2 : 3));
}
void alertsCycle() {
uint8_t m = (uint8_t)((alertsMode() + 1) & 3);
// 0 both, 1 sound, 2 vibe, 3 off
_node_prefs->buzzer_quiet = (m == 2 || m == 3) ? 1 : 0;
_node_prefs->vibe_quiet = (m == 1 || m == 3) ? 1 : 0;
}
uint8_t channelCount();
uint8_t channelIdxAt(uint8_t pos);
uint8_t notifyRowCount();
void notifyStep(int8_t dir);
void notifyActivate();
void notifyValueStep(int8_t dir);
void renderNotifyMenu(DisplayDriver& display);
void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
// Convert millivolts to percentage
@@ -276,10 +336,11 @@ public:
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
_shutdown_init(false), _radio_edit(false), _radio_val_edit(false), _radio_sel(0),
_radio_last_activity(0), _theme_edit(false), _theme_sel(0), _theme_last_activity(0),
_notify_screen(NOTIFY_OFF), _notify_sel(0), _notify_val_edit(false), _notify_last_activity(0),
sensors_lpp(200) { }
// в меню настройки (радио или темы) входящие сообщения не всплывают поверх экрана
bool suppressAlerts() override { return _radio_edit || _theme_edit; }
// в меню настроек (радио, темы, уведомления) входящие сообщения не всплывают поверх экрана
bool suppressAlerts() override { return _radio_edit || _theme_edit || _notify_screen != NOTIFY_OFF; }
void poll() override {
if (_radio_edit) {
@@ -298,6 +359,14 @@ public:
_task->keepDisplayOn();
}
}
if (_notify_screen != NOTIFY_OFF) {
if (millis() - _notify_last_activity >= UI_RADIO_MENU_TIMEOUT_MILLIS) {
notifyLeave(false);
_task->blankDisplay();
} else {
_task->keepDisplayOn();
}
}
if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released
_task->shutdown();
}
@@ -314,6 +383,10 @@ public:
renderThemeEditor(display);
return 5000;
}
if (_notify_screen != NOTIFY_OFF) {
renderNotifyMenu(display);
return 5000;
}
display.setColor(UIColor::title_bkg);
display.fillRect(0, 0, display.width(), 12);
char tmp[80];
@@ -613,6 +686,22 @@ public:
}
return true;
}
if (_notify_screen != NOTIFY_OFF) { // меню уведомлений
_notify_last_activity = millis();
if (c == KEY_NEXT || c == KEY_RIGHT) {
if (_notify_val_edit) notifyValueStep(+1); else notifyStep(+1);
return true;
}
if (c == KEY_PREV || c == KEY_LEFT) {
if (_notify_val_edit) notifyValueStep(-1); else notifyStep(-1);
return true;
}
if (c == KEY_ENTER) {
notifyActivate();
return true;
}
return true;
}
if (c == KEY_LEFT || c == KEY_PREV) {
_page = pageStep(-1);
return true;
@@ -630,6 +719,10 @@ public:
_task->showAlert("Nodes must match", 1500);
return true;
}
if (c == KEY_ENTER && _page == HomePage::FIRST) {
notifyEnter(); // настройки входящих сообщений и уведомлений
return true;
}
if (c == KEY_ENTER && _page == HomePage::THEME) {
themeEditEnter();
return true;
@@ -845,6 +938,173 @@ void HomeScreen::renderThemeEditor(DisplayDriver& display) {
}
}
uint8_t HomeScreen::channelCount() {
uint8_t n = 0;
ChannelDetails d;
for (uint8_t i = 0; i < NOTIFY_MAX_CHANNELS; i++) {
if (the_mesh.getChannel(i, d) && d.name[0] != 0) n++;
}
return n;
}
uint8_t HomeScreen::channelIdxAt(uint8_t pos) {
uint8_t n = 0;
ChannelDetails d;
for (uint8_t i = 0; i < NOTIFY_MAX_CHANNELS; i++) {
if (the_mesh.getChannel(i, d) && d.name[0] != 0) {
if (n == pos) return i;
n++;
}
}
return 0xFF;
}
uint8_t HomeScreen::notifyRowCount() {
switch (_notify_screen) {
case NOTIFY_QUIET: return 5; // Quiet hours / From / To / Timezone / Back
case NOTIFY_CHANNELS: return (uint8_t)(channelCount() + 1); // каналы + Back
default: return NOTIFY_MAIN_ROWS;
}
}
void HomeScreen::notifyStep(int8_t dir) {
uint8_t rows = notifyRowCount();
if (rows == 0) return;
_notify_sel = (uint8_t)((_notify_sel + rows + dir) % rows);
}
void HomeScreen::notifyValueStep(int8_t dir) { // правка часа тишины и часового пояса
if (_notify_screen != NOTIFY_QUIET) return;
if (_notify_sel == 1 || _notify_sel == 2) {
int v = (int)(_notify_sel == 1 ? _node_prefs->quiet_from : _node_prefs->quiet_to) + dir;
v %= 24;
if (v < 0) v += 24;
if (_notify_sel == 1) _node_prefs->quiet_from = (uint8_t)v;
else _node_prefs->quiet_to = (uint8_t)v;
} else if (_notify_sel == 3) { // timezone, -12..+14
int v = (int)_node_prefs->tz_offset + dir;
if (v < -12) v = -12;
if (v > 14) v = 14;
_node_prefs->tz_offset = (int8_t)v;
}
}
void HomeScreen::notifyActivate() { // длинное нажатие
if (_notify_val_edit) {
_notify_val_edit = false; // закончили правку часа
return;
}
if (_notify_screen == NOTIFY_MAIN) {
switch (_notify_sel) {
case 0: // Unread — открыть непрочитанные
notifyLeave(false);
_task->showMessages();
return;
case 1: _node_prefs->msg_popup = _node_prefs->msg_popup ? 0 : 1; return;
case 2: _node_prefs->msg_wake_display = _node_prefs->msg_wake_display ? 0 : 1; return;
case 3: alertsCycle(); return;
case 4: _notify_screen = NOTIFY_CHANNELS; _notify_sel = 0; return;
case 5: _notify_screen = NOTIFY_QUIET; _notify_sel = 0; return;
default: notifyLeave(true); return; // Save & exit
}
}
if (_notify_screen == NOTIFY_QUIET) {
switch (_notify_sel) {
case 0: _node_prefs->quiet_en = _node_prefs->quiet_en ? 0 : 1; return;
case 1:
case 2:
case 3: _notify_val_edit = true; return; // править час / часовой пояс
default: _notify_screen = NOTIFY_MAIN; _notify_sel = 5; return; // Back
}
}
if (_notify_screen == NOTIFY_CHANNELS) {
if (_notify_sel >= channelCount()) { // Back
_notify_screen = NOTIFY_MAIN;
_notify_sel = 4;
return;
}
uint8_t idx = channelIdxAt(_notify_sel);
if (idx != 0xFF) {
uint8_t m = (uint8_t)((_node_prefs->getChannelMode(idx) + 1) % 3);
_node_prefs->setChannelMode(idx, m); // POPUP -> SILENT -> OFF
}
}
}
void HomeScreen::renderNotifyMenu(DisplayDriver& display) {
char buf[24];
char val[16];
display.setTextSize(1);
if (_notify_screen == NOTIFY_QUIET) {
static const char* names[5] = { "Quiet hours", "From", "To", "Timezone", "Back" };
int y = 2;
for (uint8_t r = 0; r < 5; r++, y += 10) {
val[0] = 0;
if (r == 0) strcpy(val, _node_prefs->quiet_en ? "on" : "off");
else if (r == 1) snprintf(val, sizeof(val), "%02d:00", _node_prefs->quiet_from);
else if (r == 2) snprintf(val, sizeof(val), "%02d:00", _node_prefs->quiet_to);
else if (r == 3) snprintf(val, sizeof(val), "%+03d", (int)_node_prefs->tz_offset);
drawMenuRow(display, y, r == _notify_sel, names[r], val);
}
if (_notify_val_edit) {
display.setColor(UIColor::warning_txt);
display.drawTextCentered(display.width() / 2, 64 - 10, "click+ dbl- hold=back");
}
return;
}
if (_notify_screen == NOTIFY_CHANNELS) {
uint8_t n = channelCount();
uint8_t rows = (uint8_t)(n + 1);
const uint8_t VISIBLE = 6;
uint8_t first = _notify_sel >= VISIBLE ? (uint8_t)(_notify_sel - VISIBLE + 1) : 0;
static const char* mode_names[3] = { "popup", "silent", "off" };
int y = 2;
for (uint8_t r = first; r < rows && y <= 52; r++, y += 10) {
if (r < n) {
uint8_t idx = channelIdxAt(r);
ChannelDetails cd;
if (the_mesh.getChannel(idx, cd)) {
strncpy(buf, cd.name, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
} else {
strcpy(buf, "?");
}
strcpy(val, mode_names[_node_prefs->getChannelMode(idx) % 3]);
} else {
strcpy(buf, "Back");
val[0] = 0;
}
drawMenuRow(display, y, r == _notify_sel, buf, val);
}
return;
}
// NOTIFY_MAIN
static const char* labels[NOTIFY_MAIN_ROWS] = {
"Unread", "Popups", "Wake screen", "Alerts", "Channels", "Quiet hours", "Save & exit"
};
static const char* alert_names[4] = { "sound+vibe", "sound", "vibe", "off" };
int y = 1;
for (uint8_t r = 0; r < NOTIFY_MAIN_ROWS; r++, y += 9) {
val[0] = 0;
switch (r) {
case 0: snprintf(val, sizeof(val), "%d", _task->getMsgCount()); break;
case 1: strcpy(val, _node_prefs->msg_popup ? "on" : "off"); break;
case 2: strcpy(val, _node_prefs->msg_wake_display ? "on" : "off"); break;
case 3: strcpy(val, alert_names[alertsMode()]); break;
case 4: snprintf(val, sizeof(val), "%d >", channelCount()); break;
case 5:
if (_node_prefs->quiet_en) snprintf(val, sizeof(val), "%02d-%02d", _node_prefs->quiet_from, _node_prefs->quiet_to);
else strcpy(val, "off");
break;
default: break;
}
drawMenuRow(display, y, r == _notify_sel, labels[r], val);
}
}
class MsgPreviewScreen : public UIScreen {
UITask* _task;
mesh::RTCClock* _rtc;
@@ -1044,13 +1304,16 @@ void UITask::msgRead(int msgcount) {
}
}
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
void UITask::queueMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
_msgcount = msgcount;
((MsgPreviewScreen *) msg_preview)->addPreview(path_len, from_name, text);
}
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
queueMsg(path_len, from_name, text, msgcount);
if (curr != NULL && curr->suppressAlerts()) {
// Экран попросил не перекрывать его (меню настройки радио): сообщение уже
// Экран попросил не перекрывать его (меню настройки): сообщение уже
// в очереди непрочитанных, покажем его после выхода из меню.
_msg_pending = true;
_next_refresh = 100;
@@ -1059,7 +1322,8 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
setCurrScreen(msg_preview);
if (_display != NULL) {
if (!_display->isOn() && !hasConnection()) {
// зажигать экран по входящему можно запретить (в меню уведомлений)
if (!_display->isOn() && !hasConnection() && _node_prefs->msg_wake_display) {
_display->turnOn();
}
if (_display->isOn()) {
@@ -1069,6 +1333,19 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
}
}
void UITask::newMsgSilent(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
// тихий режим (часы тишины или канал SILENT): только в непрочитанные,
// экран не трогаем и звук не подаём
queueMsg(path_len, from_name, text, msgcount);
_msg_pending = true;
_next_refresh = 100; // чтобы обновился счётчик на первом экране
}
void UITask::showMessages() {
_msg_pending = false;
setCurrScreen(msg_preview);
}
void UITask::userLedHandler() {
#ifdef PIN_STATUS_LED
int cur_time = millis();
+4
View File
@@ -62,6 +62,8 @@ class UITask : public AbstractUITask {
char handleDoubleClick(char c);
char handleTripleClick(char c);
void queueMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount);
void setCurrScreen(UIScreen* c);
public:
@@ -79,6 +81,7 @@ public:
void blankDisplay(); // погасить экран немедленно
void clearAlert(); // снять текущий попап
void showPendingMessages(); // открыть предпросмотр, если сообщение пришло «в фоне»
void showMessages(); // открыть предпросмотр непрочитанных по запросу
int getMsgCount() const { return _msgcount; }
bool hasDisplay() const { return _display != NULL; }
DisplayDriver* getDisplay() { return _display; }
@@ -101,6 +104,7 @@ public:
// from AbstractUITask
void msgRead(int msgcount) override;
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
void newMsgSilent(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
void notify(UIEventType t = UIEventType::none) override;
void loop() override;