mirror of
https://github.com/pelgraine/Meck.git
synced 2026-08-06 08:42:48 +02:00
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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user