T-Watch = contacts screen overlay enabling favouriting/deleting on long press. Favourites toaster popup confirmation 1-3 seconds after favourite enabled

This commit is contained in:
pelgraine
2026-07-18 13:56:43 +10:00
parent 82982002be
commit c360850773
2 changed files with 146 additions and 29 deletions
+80 -29
View File
@@ -1581,6 +1581,81 @@ static void lastHeardToggleContact() {
if (ui_task.isOnContactsScreen()) {
ContactsScreen* cs = (ContactsScreen*)ui_task.getContactsScreen();
if (cs) {
#if defined(MECK_TWATCH)
if (cs->isActionOverlayOpen()) {
int btn = cs->actionOverlayButtonAtVY(vy);
if (btn == 0) {
// Open -- route by contact type (mirrors the pre-overlay long-press open)
cs->closeActionOverlay();
int idx = cs->getSelectedContactIdx();
uint8_t ctype = cs->getSelectedContactType();
if (idx >= 0 && ctype == ADV_TYPE_CHAT) {
if (ui_task.hasDMUnread(idx)) {
char cname[32];
cs->getSelectedContactName(cname, sizeof(cname));
ui_task.clearDMUnread(idx);
ui_task.gotoDMConversation(cname);
return 0;
}
ui_task.openTWatchKeyboard(1, idx); // TWKB_DM
return 0;
} else if (idx >= 0 && ctype == ADV_TYPE_REPEATER) {
ui_task.gotoRepeaterAdmin(idx);
return 0;
} else if (idx >= 0 && ctype == ADV_TYPE_ROOM) {
ui_task.gotoRepeaterAdmin(idx);
return 0;
} else if (idx >= 0 && ui_task.hasDMUnread(idx)) {
char cname[32];
cs->getSelectedContactName(cname, sizeof(cname));
ui_task.clearDMUnread(idx);
ui_task.gotoDMConversation(cname);
return 0;
}
ui_task.forceRefresh();
return 0;
} else if (btn == 1) {
// Favourite -- flip flags bit 0 on the selected contact and persist
int idx = cs->getSelectedContactIdx();
if (idx >= 0) {
ContactInfo tmp;
if (the_mesh.getContactByIdx(idx, tmp)) {
ContactInfo* cp = the_mesh.lookupContactByPubKey(tmp.id.pub_key, PUB_KEY_SIZE);
if (cp) {
cp->flags ^= 0x01;
the_mesh.saveContacts();
ui_task.showAlert((cp->flags & 0x01) ? "Favourited" : "Unfavourited", 1500);
}
}
}
cs->closeActionOverlay();
cs->invalidateCache();
ui_task.forceRefresh();
return 0;
} else if (btn == 2) {
// Delete -- remove the selected contact and persist
int idx = cs->getSelectedContactIdx();
if (idx >= 0) {
ContactInfo c;
if (the_mesh.getContactByIdx(idx, c)) {
if (the_mesh.removeContact(c)) {
the_mesh.saveContacts();
ui_task.showAlert("Contact deleted", 1500);
}
}
}
cs->closeActionOverlay();
cs->invalidateCache();
ui_task.forceRefresh();
return 0;
} else {
// Tap outside the buttons -- dismiss
cs->closeActionOverlay();
ui_task.forceRefresh();
return 0;
}
}
#endif
int result = cs->selectRowAtVY(vy);
if (result == 1) {
ui_task.forceRefresh();
@@ -2044,35 +2119,11 @@ static void lastHeardToggleContact() {
}
return 0;
#elif defined(MECK_TWATCH)
// Watch: long press = DM (tw-keyboard) / admin / room action.
// purpose ints match TWatchKeyboardScreen::Purpose: DM=1.
{
int idx = cs->getSelectedContactIdx();
uint8_t ctype = cs->getSelectedContactType();
if (idx >= 0 && ctype == ADV_TYPE_CHAT) {
if (ui_task.hasDMUnread(idx)) {
char cname[32];
cs->getSelectedContactName(cname, sizeof(cname));
ui_task.clearDMUnread(idx);
ui_task.gotoDMConversation(cname);
return 0;
}
ui_task.openTWatchKeyboard(1, idx); // TWKB_DM
return 0;
} else if (idx >= 0 && ctype == ADV_TYPE_REPEATER) {
ui_task.gotoRepeaterAdmin(idx);
return 0;
} else if (idx >= 0 && ctype == ADV_TYPE_ROOM) {
ui_task.gotoRepeaterAdmin(idx);
return 0;
} else if (idx >= 0 && ui_task.hasDMUnread(idx)) {
char cname[32];
cs->getSelectedContactName(cname, sizeof(cname));
ui_task.clearDMUnread(idx);
ui_task.gotoDMConversation(cname);
return 0;
}
}
// Watch: long press raises the contact action overlay
// ([Open] [Favourite] [Delete]). The tap handler dispatches the choice;
// "Open" reproduces the type-aware DM/admin routing this branch used to do.
cs->openActionOverlay();
ui_task.forceRefresh();
return 0;
#else
// T-Deck Pro: long press enters select mode
@@ -4,6 +4,10 @@
#include <helpers/ui/DisplayDriver.h>
#include <MeshCore.h>
#if defined(MECK_TWATCH)
#include <helpers/ui/LGFXDisplay.h> // blue rounded-rect overlay primitives (watch LCD)
#endif
// Timestamps before this (Jan 1 2026 UTC) are treated as invalid/unsynced
#define EPOCH_2026 1735689600UL
@@ -53,6 +57,18 @@ private:
bool _selectMode;
uint8_t* _selectedBits; // Bitfield: 1 bit per MAX_CONTACTS raw index
#if defined(MECK_TWATCH)
// --- Action overlay state (watch) ---
// Long-press raises a small [Open] [Favourite] [Delete] overlay. Buttons are
// three full-width bands defined in the 128-space touch grid; render() maps
// them to the 120-space display via display.height().
bool _actionOverlay = false;
static const int OVL_BTN_H = 26;
static const int OVL_BTN0_TOP = 21; // (128 - (3*26 + 2*4)) / 2
static const int OVL_BTN1_TOP = 51; // 21 + 26 + 4
static const int OVL_BTN2_TOP = 81; // 51 + 26 + 4
#endif
// --- helpers ---
static const char* filterLabel(FilterMode f) {
@@ -180,6 +196,9 @@ public:
void resetScroll() {
_scrollPos = 0;
_cacheValid = false;
#if defined(MECK_TWATCH)
_actionOverlay = false; // never carry a stale overlay across screen entry
#endif
}
FilterMode getFilter() const { return _filter; }
@@ -288,6 +307,22 @@ public:
return true;
}
#if defined(MECK_TWATCH)
// --- Action overlay (watch) ---
void openActionOverlay() { _actionOverlay = true; }
void closeActionOverlay() { _actionOverlay = false; }
bool isActionOverlayOpen() const { return _actionOverlay; }
// Hit-test a 128-space touch Y against the overlay buttons.
// Returns 0=Open, 1=Favourite, 2=Delete, -1=miss (tap outside dismisses).
int actionOverlayButtonAtVY(int vy) const {
if (vy >= OVL_BTN0_TOP && vy < OVL_BTN0_TOP + OVL_BTN_H) return 0;
if (vy >= OVL_BTN1_TOP && vy < OVL_BTN1_TOP + OVL_BTN_H) return 1;
if (vy >= OVL_BTN2_TOP && vy < OVL_BTN2_TOP + OVL_BTN_H) return 2;
return -1;
}
#endif
int render(DisplayDriver& display) override {
if (!_cacheValid) rebuildCache();
@@ -500,6 +535,37 @@ public:
}
#endif
#if defined(MECK_TWATCH)
// Action overlay -- drawn last so it sits on top of the contact list.
if (_actionOverlay) {
const int H = display.height(); // 120 on the watch
const int W = display.width(); // 120
int y0 = OVL_BTN0_TOP * H / 128;
int y1 = OVL_BTN1_TOP * H / 128;
int y2 = OVL_BTN2_TOP * H / 128;
int bh = OVL_BTN_H * H / 128;
int bx = 6;
int bw = W - 12;
// Dark backdrop to hide the contact list behind the overlay
display.setColor(DisplayDriver::DARK);
display.fillRect(bx - 2, y0 - 4, bw + 4, (y2 + bh) - y0 + 8);
// Blue rounded buttons with white centred labels, matching the watch's
// Notes/config style (0x231D = the home-tile blue, 0xFFFF = white).
LGFXDisplay* d = (LGFXDisplay*)&display;
d->setRawColor(0x231D);
d->drawRoundRect(bx, y0, bw, bh, 3);
d->drawRoundRect(bx, y1, bw, bh, 3);
d->drawRoundRect(bx, y2, bw, bh, 3);
d->setRawColor(0xFFFF);
display.drawTextCentered(W / 2, y0 + (bh - 8) / 2, "Open");
display.drawTextCentered(W / 2, y1 + (bh - 8) / 2, "Favourite");
display.drawTextCentered(W / 2, y2 + (bh - 8) / 2, "Delete");
}
#endif
return 5000; // e-ink: next render after 5s
}