feat(ui): Favourites phase 2 — pin from Contact options

Adds a fourth item to the QuickMsg Contact options popup that toggles
between "Pin to dial" (when the contact isn't pinned) and
"Unpin (slot N)" (when it is). Pinning opens a six-slot picker submenu
labeled "Slot N: <name>" or "Slot N: empty"; the chosen slot stores the
6-byte pub_key prefix in NodePrefs::favourite_contacts and savePrefs
commits to flash with a brief confirmation alert.

New UITask helpers backing the flow:
- findFavouriteSlot(pub_key) — returns 0..5 if pinned, -1 otherwise
- isFavouriteSlotEmpty(slot)
- setFavouriteSlot(slot, pub_key) / clearFavouriteSlot(slot)

PopupMenu reuse: a single _ctx_menu instance hosts both the Contact
options menu and the slot picker, gated by _pin_picker_active so input
routes correctly. Reset on screen reset() and on any non-NONE menu
result.

Slot labels live in a member buffer (6×22 B) because PopupMenu stores
the string pointers verbatim.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-05-24 23:30:16 +02:00
co-authored by Claude Sonnet 4.6
parent aedb7f6f5d
commit bee8e7c3dd
3 changed files with 93 additions and 4 deletions
+29
View File
@@ -139,6 +139,35 @@ public:
void clearAllDMUnread() { memset(_dm_unread_table, 0, sizeof(_dm_unread_table)); }
bool hasDisplay() const { return _display != NULL; }
DisplayDriver* getDisplay() const { return _display; }
// Favourites dial helpers. Slot index 0..FAVOURITES_COUNT-1.
int findFavouriteSlot(const uint8_t* pub_key) const {
if (!_node_prefs || !pub_key) return -1;
for (int i = 0; i < NodePrefs::FAVOURITES_COUNT; i++) {
if (memcmp(_node_prefs->favourite_contacts[i], pub_key, NodePrefs::FAVOURITE_PREFIX_LEN) == 0) {
// All-zero prefix is "empty" — never matches a real key.
bool any = false;
for (uint8_t b = 0; b < NodePrefs::FAVOURITE_PREFIX_LEN; b++)
if (_node_prefs->favourite_contacts[i][b]) { any = true; break; }
if (any) return i;
}
}
return -1;
}
bool isFavouriteSlotEmpty(int slot) const {
if (!_node_prefs || slot < 0 || slot >= NodePrefs::FAVOURITES_COUNT) return true;
for (uint8_t b = 0; b < NodePrefs::FAVOURITE_PREFIX_LEN; b++)
if (_node_prefs->favourite_contacts[slot][b]) return false;
return true;
}
void setFavouriteSlot(int slot, const uint8_t* pub_key) {
if (!_node_prefs || slot < 0 || slot >= NodePrefs::FAVOURITES_COUNT || !pub_key) return;
memcpy(_node_prefs->favourite_contacts[slot], pub_key, NodePrefs::FAVOURITE_PREFIX_LEN);
}
void clearFavouriteSlot(int slot) {
if (!_node_prefs || slot < 0 || slot >= NodePrefs::FAVOURITES_COUNT) return;
memset(_node_prefs->favourite_contacts[slot], 0, NodePrefs::FAVOURITE_PREFIX_LEN);
}
bool isButtonPressed() const;
bool isBuzzerQuiet() {