уведомления: режимы по каналам, тихие часы с часовым поясом, отключение автовключения дисплея
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
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:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user