mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
fix(ui): revert corner-anchored context menus, centre unread badge, add room logout
- Context menus (PopupMenu, Nearby/QuickMsg call sites) go back to centring on screen; the header's discoverable-menu glyph stays, but dropping the popup out of its corner looked bad on some screens. - Unread pill badge digit wasn't centred: Adafruit_GFX's classic built-in font (SH1106/SSD1306) always pads a measured string by one trailing advance column regardless of the glyph drawn, so centring on the raw width left 1px more slack on the right than the left. New DisplayDriver:: textWidthTrailingGap() (0 by default) corrects for it on those two backends. - On-device room Logout: mirrors the app's CMD_LOGOUT (drops keep-alive tracking, forgets the saved password) so a room can be deliberately signed out of from the Room options menu, not just re-logged-in. Not build-verified — no PlatformIO toolchain available this session.
This commit is contained in:
@@ -230,6 +230,16 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// On-device UI logout: drops the local keep-alive tracking (mirrors the app's
|
||||
// CMD_LOGOUT) and forgets the saved password, so the next room open prompts
|
||||
// for credentials again instead of silently re-using them. No packet is sent
|
||||
// to the server -- the room ACL has no session state to tear down, this is
|
||||
// purely local "forget this login" bookkeeping.
|
||||
void logoutRoom(const uint8_t* pub_key) {
|
||||
stopConnection(pub_key);
|
||||
forgetRoomPassword(pub_key);
|
||||
}
|
||||
|
||||
// On-device-saved room/repeater login passwords, persisted to flash (own
|
||||
// small file, independent of /contacts3) so a room that's already been
|
||||
// logged into doesn't need its password retyped after reboot.
|
||||
|
||||
@@ -415,7 +415,7 @@ class NearbyScreen : public UIScreen {
|
||||
void startDeleteConfirm() {
|
||||
const Entry* e = selected();
|
||||
if (!e || !e->has_key || !entryIsContact(e)) return;
|
||||
_confirm.begin("Delete contact?", 2, true);
|
||||
_confirm.begin("Delete contact?", 2);
|
||||
_confirm.addItem("Delete");
|
||||
_confirm.addItem("Cancel");
|
||||
_confirm.setSelected(1);
|
||||
@@ -451,7 +451,7 @@ class NearbyScreen : public UIScreen {
|
||||
|
||||
void rebuildPingMenu() {
|
||||
int keep = _ping_menu.selectedIndex(); // preserve selection across a rebuild
|
||||
_ping_menu.begin("Ping", 4, true);
|
||||
_ping_menu.begin("Ping", 4);
|
||||
_ping_menu.addItem("Send");
|
||||
if (_ping_time_str[0]) _ping_menu.addItem(_ping_time_str);
|
||||
if (_ping_snr_out_str[0]) _ping_menu.addItem(_ping_snr_out_str);
|
||||
@@ -543,7 +543,7 @@ class NearbyScreen : public UIScreen {
|
||||
|
||||
buildSortLabel();
|
||||
_menu_action_count = 0;
|
||||
_menu.begin("Options", 10, true);
|
||||
_menu.begin("Options", 10);
|
||||
auto add = [&](const char* label, Action a) {
|
||||
_menu.addItem(label);
|
||||
_menu_actions[_menu_action_count++] = a;
|
||||
|
||||
@@ -17,16 +17,14 @@ struct PopupMenu {
|
||||
int _cap; // actual visible cap, updated each render()
|
||||
bool active;
|
||||
const char* _title;
|
||||
bool anchor_tr = false; // opt-in: drop from the top-right ≡ hint instead of centring
|
||||
|
||||
enum Result { NONE, SELECTED, CANCELLED };
|
||||
|
||||
PopupMenu() : _count(0), _sel(0), _scroll(0), _visible(3), _cap(3), active(false), _title(nullptr) {}
|
||||
|
||||
void begin(const char* title, int visible = 3, bool anchor_top_right = false) {
|
||||
void begin(const char* title, int visible = 3) {
|
||||
_count = 0; _sel = 0; _scroll = 0;
|
||||
_visible = visible; _cap = visible; active = true; _title = title;
|
||||
anchor_tr = anchor_top_right;
|
||||
}
|
||||
|
||||
void addItem(const char* item) {
|
||||
@@ -88,17 +86,8 @@ struct PopupMenu {
|
||||
int bh = title_h + vis * item_h + 1; // +1 keeps the last row off the bottom border
|
||||
|
||||
// Centre on screen (clamped to the margins on small displays).
|
||||
int bx, by;
|
||||
if (anchor_tr) {
|
||||
// Drop out of the top-right ≡ hint: right-aligned under the header, so the
|
||||
// menu reads as emerging from the glyph that spawned it.
|
||||
bx = display.width() - bw - margin;
|
||||
by = display.headerH();
|
||||
if (by + bh > display.height() - margin) by = display.height() - margin - bh;
|
||||
} else {
|
||||
bx = (display.width() - bw) / 2;
|
||||
by = (display.height() - bh) / 2;
|
||||
}
|
||||
int bx = (display.width() - bw) / 2;
|
||||
int by = (display.height() - bh) / 2;
|
||||
if (bx < margin) bx = margin;
|
||||
if (by < margin) by = margin;
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ class QuickMsgScreen : public UIScreen {
|
||||
int n = (reply_allowed ? 1 : 0) + (has_loc ? 2 : 0);
|
||||
if (n == 0) return;
|
||||
_fs_act_n = 0;
|
||||
_ctx_menu.begin("Options", n, true);
|
||||
_ctx_menu.begin("Options", n);
|
||||
if (reply_allowed) { _ctx_menu.addItem("Reply"); _fs_act[_fs_act_n++] = FS_REPLY; }
|
||||
if (has_loc) { _ctx_menu.addItem("Navigate"); _fs_act[_fs_act_n++] = FS_NAV;
|
||||
_ctx_menu.addItem("Save waypoint"); _fs_act[_fs_act_n++] = FS_SAVE; }
|
||||
@@ -602,6 +602,24 @@ public:
|
||||
memcpy(_room_login_prefix[pos], pub_key, 4);
|
||||
}
|
||||
|
||||
// Reverses markRoomLoggedIn() on explicit Logout -- shifts the ring buffer
|
||||
// closed over the removed slot so isRoomLoggedIn() goes back to false and
|
||||
// the next room open prompts for a password instead of skipping it.
|
||||
void forgetRoomLoggedIn(const uint8_t* pub_key) {
|
||||
for (int i = 0; i < _room_login_count; i++) {
|
||||
int pos = (_room_login_head + i) % ROOM_LOGIN_TABLE_SIZE;
|
||||
if (memcmp(_room_login_prefix[pos], pub_key, 4) == 0) {
|
||||
for (int j = i; j < _room_login_count - 1; j++) {
|
||||
int from = (_room_login_head + j + 1) % ROOM_LOGIN_TABLE_SIZE;
|
||||
int to = (_room_login_head + j) % ROOM_LOGIN_TABLE_SIZE;
|
||||
memcpy(_room_login_prefix[to], _room_login_prefix[from], 4);
|
||||
}
|
||||
_room_login_count--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password of the room-login attempt currently in flight -- set right
|
||||
// before sendRoomLogin(), read back in onRoomLoginResult() so a successful
|
||||
// attempt can be persisted (see MyMesh::saveRoomPassword()).
|
||||
@@ -1200,7 +1218,7 @@ public:
|
||||
if (c == KEY_CONTEXT_MENU) {
|
||||
// PopupMenu stores the title pointer verbatim — use static strings.
|
||||
static const char* MODE_TITLES[] = { "DM options", "Channel options", "Room options" };
|
||||
_ctx_menu.begin(MODE_TITLES[_mode_sel < 3 ? _mode_sel : 0], 1, true);
|
||||
_ctx_menu.begin(MODE_TITLES[_mode_sel < 3 ? _mode_sel : 0], 1);
|
||||
_ctx_menu.addItem("Mark all read");
|
||||
return true;
|
||||
}
|
||||
@@ -1224,10 +1242,18 @@ public:
|
||||
auto res = _ctx_menu.handleInput(c);
|
||||
if (res == PopupMenu::SELECTED && _num_contacts > 0) {
|
||||
if (the_mesh.getContactByIdx(_sorted[_contact_sel], _sel_contact)) {
|
||||
_login_mode = true;
|
||||
_kb->begin("", 15); // room/repeater password: max 15 chars
|
||||
_kb->clearPlaceholders(); // {loc}/{time} are for messages, not a password
|
||||
_phase = KEYBOARD;
|
||||
int sel = _ctx_menu.selectedIndex();
|
||||
if (sel == 0) {
|
||||
_login_mode = true;
|
||||
_kb->begin("", 15); // room/repeater password: max 15 chars
|
||||
_kb->clearPlaceholders(); // {loc}/{time} are for messages, not a password
|
||||
_phase = KEYBOARD;
|
||||
} else {
|
||||
// Logout: only reachable when isRoomLoggedIn() added this item.
|
||||
the_mesh.logoutRoom(_sel_contact.id.pub_key);
|
||||
forgetRoomLoggedIn(_sel_contact.id.pub_key);
|
||||
_task->showAlert("Logged out", 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -1314,7 +1340,7 @@ public:
|
||||
snprintf(_pin_slot_labels[s], sizeof(_pin_slot_labels[s]), "Slot %d: %s", s + 1, nm);
|
||||
}
|
||||
}
|
||||
_ctx_menu.begin("Pick slot", 3, true);
|
||||
_ctx_menu.begin("Pick slot", 3);
|
||||
for (int s = 0; s < NodePrefs::FAVOURITES_COUNT; s++) _ctx_menu.addItem(_pin_slot_labels[s]);
|
||||
_pin_picker_active = true;
|
||||
}
|
||||
@@ -1355,8 +1381,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_CONTEXT_MENU && _num_contacts > 0 && _room_mode) {
|
||||
_ctx_menu.begin("Room options", 1, true);
|
||||
ContactInfo ci;
|
||||
bool logged_in = the_mesh.getContactByIdx(_sorted[_contact_sel], ci) && isRoomLoggedIn(ci.id.pub_key);
|
||||
_ctx_menu.begin("Room options", logged_in ? 2 : 1);
|
||||
_ctx_menu.addItem("Login...");
|
||||
if (logged_in) _ctx_menu.addItem("Logout");
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_CONTEXT_MENU && _num_contacts > 0 && !_room_mode) {
|
||||
@@ -1371,7 +1400,7 @@ public:
|
||||
int pinned_slot = _task->findFavouriteSlot(ci.id.pub_key);
|
||||
if (pinned_slot >= 0) snprintf(_ctx_pin_item, sizeof(_ctx_pin_item), "Unpin (slot %d)", pinned_slot + 1);
|
||||
else snprintf(_ctx_pin_item, sizeof(_ctx_pin_item), "Pin to dial");
|
||||
_ctx_menu.begin("Contact options", 3, true);
|
||||
_ctx_menu.begin("Contact options", 3);
|
||||
_ctx_menu.addItem("Mark as read");
|
||||
_ctx_menu.addItem(_ctx_notif_item);
|
||||
_ctx_menu.addItem(_ctx_melody_item);
|
||||
@@ -1471,7 +1500,7 @@ public:
|
||||
{ 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 ? "Fav: yes" : "Fav: no"); }
|
||||
_ctx_menu.begin("Channel options", 4, true);
|
||||
_ctx_menu.begin("Channel options", 4);
|
||||
_ctx_menu.addItem("Mark all read");
|
||||
_ctx_menu.addItem(_ctx_notif_item);
|
||||
_ctx_menu.addItem(_ctx_melody_item);
|
||||
|
||||
Reference in New Issue
Block a user