feat(ui): make favourites mean one thing across every list

Three names had grown around one idea. "Favourite" was a filter in
Settings, an invisible app-only flag on a contact, a device-settable bit on
a channel, and — on the Nodes screen — a menu row that actually pinned to
the Favourites dial. Nothing marked a favourite on screen, and the dial
only took chat contacts.

A favourite is now the starred flag (ContactInfo::flags bit 0 for contacts
and rooms, ch_fav_bitmask for channels), settable on the device everywhere
via a Fav: ON/OFF row, marked with a star on its row, and sorted to the top
of the list — in Messages, Tools > Nodes and the Locator target picker.
Settings > Contacts > "Favs top" turns the sorting off; it defaults on, and
is stored inverted so an upgraded prefs file reads back as on rather than
off.

MyMesh::setContactFavourite() writes the same bit the app sets and bumps
lastmod, so the two stay in sync. The DMs/Rooms = Fav list filters no
longer depend on having starred someone in the app first.

Pinning is now separate and explicit, and the dial takes contacts, room
servers and channels. Slots carry a kind (NodePrefs::favourite_kinds,
schema 0xC0DE0029) — a channel slot holds an index, so emptiness is decided
by the kind first, since channel 0's payload is all zeroes. Choosing what
to pin reuses the Messages screen's own Direct/Channels/Rooms browse
instead of a second picker built on the dial, which drops that picker, its
key/label tables and the now-unused getRecentDMContacts(). A filled tile
gained Unpin/Replace under Hold Enter.

Fixes a room server being pinnable and then unremovable: the picker's
recent-conversations tier didn't filter by contact type and room posts
share the DM history, opening one from the dial skipped the login
handshake, and Unpin only existed in the chat contact list that rooms never
appear in.

Also: PopupMenu::_visible was written and never read (render recomputes the
cap from display height), which is why menus with more items than the
"visible" argument always worked; Settings' all/fav values and the
dm_show_all comment said things that were not true.

Built for Heltec V3, Wio Tracker L1, Wio Tracker L1 e-ink and T-Echo Card.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-08-31 15:02:01 +02:00
co-authored by Claude Opus 5
parent 355816eafd
commit 57851627cf
18 changed files with 660 additions and 338 deletions
@@ -25,13 +25,13 @@ Filter by category with **LEFT/RIGHT** (one coherent axis — type only):
| Filter | Shows |
| ------ | ------------------------------ |
| All | All known nodes |
| Fav | Upstream-starred contacts only |
| Fav | Favourites only (★) |
| Comp | Companion (chat) nodes |
| Rpt | Repeaters |
| Room | Room servers |
| Snsr | Sensors |
Select a node to see its coordinates, distance, bearing with cardinal direction, type, and last-heard time. A node that is **broadcasting its position** via Live Share is marked with a **♦ diamond** beside its name in the list (the same marker the map uses), and its detail shows `Sharing pos:` with the share age and whether it's DM-verified or channel-only.
Select a node to see its coordinates, distance, bearing with cardinal direction, type, and last-heard time. A node that is **broadcasting its position** via Live Share is marked with a **♦ diamond** beside its name in the list (the same marker the map uses), and its detail shows `Sharing pos:` with the share age and whether it's DM-verified or channel-only. A **★ star** marks a favourite, which is also sorted to the top of the list unless **Settings Contacts Favs top** is off.
**Hold Enter** opens the same **Options** menu everywhere (list and detail), in a fixed order — only the actions that apply appear:
@@ -41,6 +41,8 @@ Select a node to see its coordinates, distance, bearing with cardinal direction,
| Ping | a public key is known for the node |
| Save waypoint | selected node has GPS |
| Set as target | selected node has GPS **and** a public key — pins it as the active **Locator/Nav target** right away (see **Locator**) |
| Fav: ON / OFF | selected node is a saved contact — the same starred flag the Messages lists use, shared with the companion app |
| Pin to dial / Unpin (slot N) | selected node is a saved contact — puts it on the [Favourites Dial](../favourites_dial/favourites_dial.md), taking the first free slot |
| Admin | selected node is a saved **repeater or room server** contact — opens **Tools Admin** for it directly (see **Admin**) |
| Sort: Dist/Recent | browsing stored nodes — **LEFT/RIGHT** on the row flips distance ↔ last-heard in place |
| Discover scan / Rescan | always (live `NODE_DISCOVER_REQ` scan) |
@@ -272,7 +274,7 @@ Navigate with **UP/DOWN**, change a value with **LEFT/RIGHT** (or **Enter**); **
| Setting | Options | Notes |
| ------- | -------------------------------- | -------------------------------------------------------------------------------------- |
| Alert | ON / OFF | Master switch. Enabling without a target prompts you to pick one. |
| Target | none / person / waypoint | **Enter** opens a picker — **None** first (clears the target), then **favourites** (offered even with no known position yet, so you can arm ahead of time), then any other contact with a currently-resolvable position (live-sharing *or* just last-advertised, e.g. a repeater), then waypoints; **UP/DOWN** + **Enter** to choose. **LEFT/RIGHT** quick-cycles the same set in place, including back to **None**. A person is shown with an `@` prefix, plus a compact **age tag** (e.g. `@Bob (5m)`) when the position is last-advertised rather than a live share. Shows `none` until set. |
| Target | none / person / waypoint | **Enter** opens a picker — **None** first (clears the target), then **dial-pinned people** (offered even with no known position yet, so you can arm ahead of time), then any other contact with a currently-resolvable position (live-sharing *or* just last-advertised, e.g. a repeater), then waypoints; **UP/DOWN** + **Enter** to choose. **LEFT/RIGHT** quick-cycles the same set in place, including back to **None**. A person is shown with an `@` prefix, a ★ if they're a favourite, plus a compact **age tag** (e.g. `@Bob (5m)`) when the position is last-advertised rather than a live share. Shows `none` until set. |
| Radius | 50 / 100 / 250 / 500 m / 1 km | Geofence size. |
| Mode | Arrive / Leave / Both | Which crossing fires the alert — entering the radius, leaving it, or both. |
| Beeper | ON / OFF | Optional homing tone — shown only in **Arrive** / **Both** modes (see below). |