mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
feat(contacts): channel favourites — filter in Settings + context menu
Add per-channel favourite marking and a Settings filter (all/fav) for
the channel list, symmetric with the existing DM and Room filters.
NodePrefs:
- ch_fav_bitmask (uint64_t): bit i = channel i is marked favourite
- ch_fav_only (uint8_t): 0=show all channels, 1=favourites only
- SCHEMA_SENTINEL bumped to 0xC0DE0005
DataStore: save/load both new fields before the sentinel.
SettingsScreen: new CH_FILTER item ('Channels: all/fav') in the
Contacts section, positioned between DM_FILTER and ROOM_FILTER.
QuickMsgScreen:
- buildChannelList() skips non-favourite channels when ch_fav_only=1
- Channel context menu gains a 4th option 'Fav'/'Unfav' that toggles
ch_fav_bitmask for the selected channel and rebuilds the list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -105,11 +105,14 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t trail_min_delta_idx; // indexes TrailStore::minDeltaMeters (5/10/25/100 m)
|
||||
uint8_t trail_units_idx; // indexes TrailStore::unitLabel (km/h, mph, min/km, min/mi)
|
||||
|
||||
uint64_t ch_fav_bitmask; // bit i = channel i is marked as favourite
|
||||
uint8_t ch_fav_only; // 0=show all channels (default), 1=show favourites only
|
||||
|
||||
// Tail sentinel written at the end of /new_prefs. Bump the low byte when
|
||||
// adding/removing/reordering fields in DataStore::savePrefs/loadPrefsInt so
|
||||
// older saves are detected on load and skipped (zero-init defaults kept).
|
||||
// High 24 bits identify the file format; low byte is the schema revision.
|
||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0004;
|
||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0005;
|
||||
|
||||
// Bit-index for each home page. Used by page_order (entries store bit+1) and
|
||||
// by home_pages_mask. Single source of truth — both HomeScreen::pageBit/bitToPage
|
||||
|
||||
@@ -47,6 +47,7 @@ class QuickMsgScreen : public UIScreen {
|
||||
char _ctx_notif_item[22];
|
||||
char _ctx_melody_item[20];
|
||||
char _ctx_pin_item[28]; // "Pin to dial" or "Unpin (slot N)"
|
||||
char _ctx_ch_fav_item[12]; // "Fav" or "Unfav"
|
||||
char _pin_slot_labels[NodePrefs::FAVOURITES_COUNT][22]; // per-slot picker labels
|
||||
bool _pin_picker_active; // true while the slot-picker submenu is open
|
||||
bool _dm_direct_entry; // entered DM_HIST via Favourites shortcut; CANCEL returns home
|
||||
@@ -271,14 +272,16 @@ class QuickMsgScreen : public UIScreen {
|
||||
}
|
||||
|
||||
void buildChannelList() {
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
bool fav_only = (p && p->ch_fav_only);
|
||||
_num_channels = 0;
|
||||
for (int i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
ChannelDetails ch;
|
||||
if (the_mesh.getChannel(i, ch) && ch.name[0] != '\0') {
|
||||
if (!the_mesh.getChannel(i, ch) || ch.name[0] == '\0') continue;
|
||||
if (fav_only && !(p->ch_fav_bitmask & (1ULL << i))) continue;
|
||||
_channel_indices[_num_channels++] = (uint8_t)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns per-channel notification state: 0=follow global, 1=muted, 2=force-on
|
||||
uint8_t chNotifState(uint8_t ch_idx) const {
|
||||
@@ -1083,10 +1086,19 @@ public:
|
||||
uint8_t nstate = chNotifState(ch_idx);
|
||||
setChNotifState(ch_idx, (nstate + 1) % 3);
|
||||
_ctx_dirty = true;
|
||||
} else {
|
||||
} else if (_ctx_menu.selectedIndex() == 2) {
|
||||
uint8_t slot = chNotifMelody(ch_idx);
|
||||
setChNotifMelody(ch_idx, (slot + 1) % 3);
|
||||
_ctx_dirty = true;
|
||||
} else {
|
||||
// Toggle favourite
|
||||
NodePrefs* p2 = _task->getNodePrefs();
|
||||
if (p2) {
|
||||
p2->ch_fav_bitmask ^= (1ULL << ch_idx);
|
||||
_ctx_dirty = true;
|
||||
buildChannelList(); // refilter if ch_fav_only is active
|
||||
if (_channel_sel >= _num_channels) _channel_sel = _num_channels > 0 ? _num_channels - 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (res != PopupMenu::NONE && _ctx_dirty) {
|
||||
@@ -1124,10 +1136,14 @@ public:
|
||||
{ static const char* ML[] = { "global", "M1", "M2" };
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s",
|
||||
ML[chNotifMelody(ch_idx)]); }
|
||||
_ctx_menu.begin("Channel options", 3);
|
||||
{ NodePrefs* p2 = _task->getNodePrefs();
|
||||
bool is_fav = p2 && (p2->ch_fav_bitmask & (1ULL << ch_idx));
|
||||
snprintf(_ctx_ch_fav_item, sizeof(_ctx_ch_fav_item), is_fav ? "Unfav" : "Fav"); }
|
||||
_ctx_menu.begin("Channel options", 4);
|
||||
_ctx_menu.addItem("Mark all read");
|
||||
_ctx_menu.addItem(_ctx_notif_item);
|
||||
_ctx_menu.addItem(_ctx_melody_item);
|
||||
_ctx_menu.addItem(_ctx_ch_fav_item);
|
||||
_ctx_dirty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class SettingsScreen : public UIScreen {
|
||||
TIMEZONE,
|
||||
LOW_BAT,
|
||||
// Contacts section
|
||||
SECTION_CONTACTS, DM_FILTER, ROOM_FILTER,
|
||||
SECTION_CONTACTS, DM_FILTER, CH_FILTER, ROOM_FILTER,
|
||||
// Messages section
|
||||
SECTION_MESSAGES,
|
||||
MSG_SLOT_0, MSG_SLOT_1, MSG_SLOT_2, MSG_SLOT_3, MSG_SLOT_4,
|
||||
@@ -533,6 +533,10 @@ class SettingsScreen : public UIScreen {
|
||||
display.print("DM");
|
||||
display.setCursor(display.valCol(), y);
|
||||
display.print((p && p->dm_show_all) ? "all" : "fav");
|
||||
} else if (item == CH_FILTER) {
|
||||
display.print("Channels");
|
||||
display.setCursor(display.valCol(), y);
|
||||
display.print((p && p->ch_fav_only) ? "fav" : "all");
|
||||
} else if (item == ROOM_FILTER) {
|
||||
display.print("Rooms");
|
||||
display.setCursor(display.valCol(), y);
|
||||
@@ -783,6 +787,11 @@ public:
|
||||
_dirty = true;
|
||||
return true;
|
||||
}
|
||||
if (_selected == CH_FILTER && p && (left || right || enter)) {
|
||||
p->ch_fav_only = p->ch_fav_only ? 0 : 1;
|
||||
_dirty = true;
|
||||
return true;
|
||||
}
|
||||
if (_selected == ROOM_FILTER && p && (left || right || enter)) {
|
||||
p->room_fav_only = p->room_fav_only ? 0 : 1;
|
||||
_dirty = true;
|
||||
|
||||
Reference in New Issue
Block a user