mirror of
https://github.com/pelgraine/Meck.git
synced 2026-08-11 11:12:43 +02:00
selective channel notification functions - off/mute, only at tags, or alll, under channels in settingscreen
This commit is contained in:
@@ -289,6 +289,9 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
if (file.read((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)) != sizeof(_prefs.default_scope_key)) {
|
||||
memset(_prefs.default_scope_key, 0, sizeof(_prefs.default_scope_key));
|
||||
}
|
||||
if (file.read((uint8_t *)_prefs.channel_notif, sizeof(_prefs.channel_notif)) != sizeof(_prefs.channel_notif)) {
|
||||
memset(_prefs.channel_notif, 0, sizeof(_prefs.channel_notif)); // default: NOTIF_ALL
|
||||
}
|
||||
|
||||
// Clamp to valid ranges
|
||||
if (_prefs.dark_mode > 1) _prefs.dark_mode = 0;
|
||||
@@ -298,6 +301,10 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
if (_prefs.ui_font_style > 2) _prefs.ui_font_style = 0;
|
||||
if (_prefs.tx_fail_reset_threshold > 10) _prefs.tx_fail_reset_threshold = 3;
|
||||
if (_prefs.rx_fail_reboot_threshold > 10) _prefs.rx_fail_reboot_threshold = 3;
|
||||
// Clamp channel notification preferences to valid range
|
||||
for (int i = 0; i < (int)sizeof(_prefs.channel_notif); i++) {
|
||||
if (_prefs.channel_notif[i] > 2) _prefs.channel_notif[i] = 0;
|
||||
}
|
||||
// auto_lock_minutes: only accept known options (0, 2, 5, 10, 15, 30)
|
||||
{
|
||||
uint8_t alm = _prefs.auto_lock_minutes;
|
||||
@@ -357,6 +364,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.ui_font_style, sizeof(_prefs.ui_font_style)); // 105
|
||||
file.write((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 106
|
||||
file.write((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 137
|
||||
file.write((uint8_t *)_prefs.channel_notif, sizeof(_prefs.channel_notif)); // 153
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
#define ADVERT_LOC_NONE 0
|
||||
#define ADVERT_LOC_SHARE 1
|
||||
|
||||
// Per-channel notification preferences (stored in channel_notif[])
|
||||
#define NOTIF_ALL 0 // Notify on all messages (default)
|
||||
#define NOTIF_MENTIONS 1 // Notify only when @nodename appears in message
|
||||
#define NOTIF_NONE 2 // No notifications (muted)
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
@@ -53,6 +58,12 @@ struct NodePrefs { // persisted to file
|
||||
char default_scope_name[31]; // e.g. "au-nsw", empty = unscoped
|
||||
uint8_t default_scope_key[16]; // TransportKey derived from "#" + name
|
||||
|
||||
// --- Per-channel notification preferences ---
|
||||
// Index 0..MAX_GROUP_CHANNELS-1 for group channels, index MAX_GROUP_CHANNELS for DMs.
|
||||
// Values: NOTIF_ALL (0), NOTIF_MENTIONS (1), NOTIF_NONE (2).
|
||||
// Defaults to NOTIF_ALL for all channels.
|
||||
uint8_t channel_notif[21]; // 20 group channels + 1 DM slot
|
||||
|
||||
// --- Font helpers (inline, no overhead) ---
|
||||
// Returns the DisplayDriver text-size index for "small/body" text.
|
||||
// T-Deck Pro: 0 = built-in 6×8 (or 7pt with custom fonts), 1 = 9pt.
|
||||
|
||||
@@ -148,8 +148,10 @@ public:
|
||||
|
||||
// Add a new message to the history
|
||||
// peer_name: for DMs, the contact this message belongs to (sender for received, recipient for sent)
|
||||
// suppressUnread: if true, do not increment the unread counter for this message
|
||||
void addMessage(uint8_t channel_idx, uint8_t path_len, const char* sender, const char* text,
|
||||
const uint8_t* path_bytes = nullptr, int8_t snr = 0, const char* peer_name = nullptr) {
|
||||
const uint8_t* path_bytes = nullptr, int8_t snr = 0, const char* peer_name = nullptr,
|
||||
bool suppressUnread = false) {
|
||||
// Move to next slot in circular buffer
|
||||
_newestIdx = (_newestIdx + 1) % CHANNEL_MSG_HISTORY_SIZE;
|
||||
|
||||
@@ -191,8 +193,9 @@ public:
|
||||
_replySelectPos = -1;
|
||||
|
||||
// Track unread count for this channel (only for received messages, not sent)
|
||||
// path_len == 0 means locally sent
|
||||
if (path_len != 0) {
|
||||
// path_len == 0 means locally sent.
|
||||
// suppressUnread: per-channel notification preference says not to count this message.
|
||||
if (path_len != 0 && !suppressUnread) {
|
||||
int unreadSlot = (channel_idx == 0xFF) ? MAX_GROUP_CHANNELS : channel_idx;
|
||||
if (unreadSlot >= 0 && unreadSlot <= MAX_GROUP_CHANNELS) {
|
||||
_unread[unreadSlot]++;
|
||||
|
||||
@@ -1930,15 +1930,27 @@ public:
|
||||
snprintf(tmp, sizeof(tmp), " %s [*]", ch.name);
|
||||
}
|
||||
if (selected) {
|
||||
// Show edit/delete hints on right
|
||||
// Build hint with notification state + actions
|
||||
uint8_t nPref = _prefs->channel_notif[chIdx];
|
||||
const char* nTag = (nPref == NOTIF_NONE) ? "Off" :
|
||||
(nPref == NOTIF_MENTIONS) ? "@" : "All";
|
||||
char hintBuf[40];
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
const char* hint = chIdx > 0 ? "Ent:Region Hold:Del" : "Ent:Region";
|
||||
if (chIdx > 0) {
|
||||
snprintf(hintBuf, sizeof(hintBuf), "Notif:%s Ent:Region Hold:Del", nTag);
|
||||
} else {
|
||||
snprintf(hintBuf, sizeof(hintBuf), "Notif:%s Ent:Region", nTag);
|
||||
}
|
||||
#else
|
||||
const char* hint = chIdx > 0 ? "Ent:Region X:Del" : "Ent:Region";
|
||||
if (chIdx > 0) {
|
||||
snprintf(hintBuf, sizeof(hintBuf), "N:%s Ent:Region X:Del", nTag);
|
||||
} else {
|
||||
snprintf(hintBuf, sizeof(hintBuf), "N:%s Ent:Region", nTag);
|
||||
}
|
||||
#endif
|
||||
int hintW = display.getTextWidth(hint);
|
||||
int hintW = display.getTextWidth(hintBuf);
|
||||
display.setCursor(display.width() - hintW - 2, y);
|
||||
display.print(hint);
|
||||
display.print(hintBuf);
|
||||
display.setCursor(0, y);
|
||||
}
|
||||
}
|
||||
@@ -3286,6 +3298,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// N: cycle notification preference (All -> Mentions -> None -> All)
|
||||
if (c == 'n' || c == 'N') {
|
||||
if (_rows[_cursor].type == ROW_CHANNEL) {
|
||||
uint8_t chIdx = _rows[_cursor].param;
|
||||
uint8_t cur = _prefs->channel_notif[chIdx];
|
||||
_prefs->channel_notif[chIdx] = (cur + 1) % 3;
|
||||
the_mesh.savePrefs();
|
||||
const char* labels[] = {"All", "Mentions", "Off"};
|
||||
Serial.printf("Settings: Channel %d notif -> %s\n",
|
||||
chIdx, labels[_prefs->channel_notif[chIdx]]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Q: back -- if in sub-screen, return to top level; else exit settings
|
||||
if (c == 'q' || c == 'Q') {
|
||||
if (_subScreen != SUB_NONE) {
|
||||
|
||||
@@ -66,6 +66,12 @@
|
||||
#include "ModemManager.h"
|
||||
#endif
|
||||
|
||||
// Per-channel notification suppression flag.
|
||||
// Set by newMsg() based on channel_notif preference, checked by notify()
|
||||
// to suppress buzzer/vibration. Safe because both are called sequentially
|
||||
// from the same mesh callback on the same thread.
|
||||
static bool s_lastMsgSuppressed = false;
|
||||
|
||||
class SplashScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
unsigned long dismiss_after;
|
||||
@@ -1454,6 +1460,15 @@ void UITask::dismissBootHint() {
|
||||
}
|
||||
|
||||
void UITask::notify(UIEventType t) {
|
||||
// Per-channel notification gating: if the last message was from a
|
||||
// muted channel (or mentions-only without an @mention), suppress
|
||||
// buzzer and vibration. Ack events are never suppressed.
|
||||
if (s_lastMsgSuppressed && t != UIEventType::ack) {
|
||||
s_lastMsgSuppressed = false; // Consume the flag
|
||||
return;
|
||||
}
|
||||
s_lastMsgSuppressed = false;
|
||||
|
||||
#if defined(PIN_BUZZER)
|
||||
switch(t){
|
||||
case UIEventType::contactMessage:
|
||||
@@ -1520,6 +1535,44 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Per-channel notification preference check ---
|
||||
// Determines whether to suppress toast, buzzer, keyboard flash, vibration,
|
||||
// display wake, and unread counter for this message. Messages are ALWAYS
|
||||
// stored in history regardless -- only alerts and unread badges are gated.
|
||||
bool suppressNotif = false;
|
||||
{
|
||||
int notifSlot = (channel_idx == 0xFF) ? MAX_GROUP_CHANNELS : (int)channel_idx;
|
||||
if (notifSlot >= 0 && notifSlot < (int)sizeof(_node_prefs->channel_notif)) {
|
||||
uint8_t pref = _node_prefs->channel_notif[notifSlot];
|
||||
if (pref == NOTIF_NONE) {
|
||||
suppressNotif = true;
|
||||
} else if (pref == NOTIF_MENTIONS) {
|
||||
// Check for @nodename or @[nodename] in message text (case-insensitive).
|
||||
// MeshCore companion app sends mentions as @[node name] with brackets.
|
||||
suppressNotif = true; // Suppress unless mention found
|
||||
if (_node_prefs->node_name[0] != '\0') {
|
||||
char tagPlain[36];
|
||||
char tagBracket[38];
|
||||
snprintf(tagPlain, sizeof(tagPlain), "@%s", _node_prefs->node_name);
|
||||
snprintf(tagBracket, sizeof(tagBracket), "@[%s]", _node_prefs->node_name);
|
||||
int lenPlain = strlen(tagPlain);
|
||||
int lenBracket = strlen(tagBracket);
|
||||
const char* p = text;
|
||||
while (*p) {
|
||||
if (strncasecmp(p, tagBracket, lenBracket) == 0 ||
|
||||
strncasecmp(p, tagPlain, lenPlain) == 0) {
|
||||
suppressNotif = false; // Mentioned -- notify
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set the flag for notify() which is called immediately after newMsg()
|
||||
s_lastMsgSuppressed = suppressNotif;
|
||||
|
||||
// Add to channel history screen with channel index, path data, and SNR
|
||||
// For DMs (channel_idx == 0xFF):
|
||||
@@ -1541,15 +1594,15 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
if (isRoomMsg) {
|
||||
// Room server: text already has "Poster: message" format — store as-is
|
||||
// Tag with room server name for conversation filtering
|
||||
((ChannelScreen *) channel_screen)->addMessage(channel_idx, path_len, from_name, text, path, snr, from_name);
|
||||
((ChannelScreen *) channel_screen)->addMessage(channel_idx, path_len, from_name, text, path, snr, from_name, suppressNotif);
|
||||
} else {
|
||||
// Regular DM: prefix with sender name
|
||||
char dmFormatted[CHANNEL_MSG_TEXT_LEN];
|
||||
snprintf(dmFormatted, sizeof(dmFormatted), "%s: %s", from_name, text);
|
||||
((ChannelScreen *) channel_screen)->addMessage(channel_idx, path_len, from_name, dmFormatted, path, snr);
|
||||
((ChannelScreen *) channel_screen)->addMessage(channel_idx, path_len, from_name, dmFormatted, path, snr, nullptr, suppressNotif);
|
||||
}
|
||||
} else {
|
||||
((ChannelScreen *) channel_screen)->addMessage(channel_idx, path_len, from_name, text, path, snr);
|
||||
((ChannelScreen *) channel_screen)->addMessage(channel_idx, path_len, from_name, text, path, snr, nullptr, suppressNotif);
|
||||
}
|
||||
|
||||
// If user is currently viewing this channel on the device, or companion
|
||||
@@ -1572,11 +1625,11 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Don't interrupt user with popup - just show brief notification
|
||||
// Messages are stored in channel history, accessible via tile/key
|
||||
// Suppress toasts for room server messages (bulk sync would spam toasts)
|
||||
if (!isOnRepeaterAdmin() && !isRoomMsg) {
|
||||
if (!isOnRepeaterAdmin() && !isRoomMsg && !suppressNotif) {
|
||||
char alertBuf[40];
|
||||
snprintf(alertBuf, sizeof(alertBuf), "New: %s", from_name);
|
||||
showAlert(alertBuf, 2000);
|
||||
@@ -1586,7 +1639,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
forceRefresh();
|
||||
}
|
||||
|
||||
if (_display != NULL) {
|
||||
if (_display != NULL && !suppressNotif) {
|
||||
if (!_display->isOn() && !hasConnection()) {
|
||||
_display->turnOn();
|
||||
}
|
||||
@@ -1602,9 +1655,9 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard flash notification (suppress for room sync)
|
||||
// Keyboard flash notification (suppress for room sync and muted channels)
|
||||
#ifdef KB_BL_PIN
|
||||
if (_node_prefs->kb_flash_notify && !isRoomMsg) {
|
||||
if (_node_prefs->kb_flash_notify && !isRoomMsg && !suppressNotif) {
|
||||
digitalWrite(KB_BL_PIN, HIGH);
|
||||
_kb_flash_off_at = millis() + 200; // 200ms flash
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user