mirror of
https://github.com/pelgraine/Meck.git
synced 2026-08-05 16:22:45 +02:00
roomserver additions stage 2 and dm ui functionality updates
This commit is contained in:
@@ -737,6 +737,13 @@ bool MyMesh::uiLoginToRepeater(uint32_t contact_idx, const char* password, uint3
|
||||
uint8_t save_path_len = recipient->out_path_len;
|
||||
recipient->out_path_len = OUT_PATH_UNKNOWN;
|
||||
|
||||
// For room servers: reset sync_since to zero so the server pushes ALL posts.
|
||||
// The device has no persistent DM storage, so every session needs full history.
|
||||
// sync_since naturally updates as messages arrive (BaseChatMesh::onPeerDataRecv).
|
||||
if (recipient->type == ADV_TYPE_ROOM) {
|
||||
recipient->sync_since = 0;
|
||||
}
|
||||
|
||||
Serial.printf("[uiLogin] Sending login to '%s' (idx=%d, path was 0x%02X, now 0x%02X, hash_mode=%d)\n",
|
||||
recipient->name, contact_idx, save_path_len, recipient->out_path_len, _prefs.path_hash_mode);
|
||||
|
||||
|
||||
@@ -2369,17 +2369,29 @@ void handleKeyboardInput() {
|
||||
if (key == '\r') {
|
||||
// Enter - send the message
|
||||
Serial.println("Compose: Enter pressed, sending...");
|
||||
bool composeWasSent = false;
|
||||
if (composePos > 0) {
|
||||
sendComposedMessage();
|
||||
composeWasSent = true; // sendComposedMessage shows its own alert
|
||||
}
|
||||
bool wasDM = composeDM;
|
||||
int savedDMIdx = composeDMContactIdx;
|
||||
char savedDMName[32];
|
||||
if (wasDM) strncpy(savedDMName, composeDMName, sizeof(savedDMName));
|
||||
composeMode = false;
|
||||
emojiPickerMode = false;
|
||||
composeDM = false;
|
||||
composeDMContactIdx = -1;
|
||||
composeBuffer[0] = '\0';
|
||||
composePos = 0;
|
||||
if (wasDM) {
|
||||
if (wasDM && savedDMIdx >= 0) {
|
||||
// Return to DM conversation to see sent message
|
||||
ChannelScreen* chScr = (ChannelScreen*)ui_task.getChannelScreen();
|
||||
uint8_t savedPerms = (chScr && chScr->isDMConversation()) ? chScr->getDMContactPerms() : 0;
|
||||
ui_task.gotoDMConversation(savedDMName, savedDMIdx, savedPerms);
|
||||
// Re-show alert after navigation (setCurrScreen clears prior alerts)
|
||||
if (composeWasSent) ui_task.showAlert("DM sent!", 1500);
|
||||
} else if (wasDM) {
|
||||
ui_task.gotoContactsScreen();
|
||||
} else {
|
||||
ui_task.gotoChannelScreen();
|
||||
@@ -2393,13 +2405,20 @@ void handleKeyboardInput() {
|
||||
// Shift+Backspace = Cancel (works anytime)
|
||||
Serial.println("Compose: Shift+Backspace, cancelling...");
|
||||
bool wasDM = composeDM;
|
||||
int savedDMIdx = composeDMContactIdx;
|
||||
char savedDMName[32];
|
||||
if (wasDM) strncpy(savedDMName, composeDMName, sizeof(savedDMName));
|
||||
composeMode = false;
|
||||
emojiPickerMode = false;
|
||||
composeDM = false;
|
||||
composeDMContactIdx = -1;
|
||||
composeBuffer[0] = '\0';
|
||||
composePos = 0;
|
||||
if (wasDM) {
|
||||
if (wasDM && savedDMIdx >= 0) {
|
||||
ChannelScreen* chScr = (ChannelScreen*)ui_task.getChannelScreen();
|
||||
uint8_t savedPerms = (chScr && chScr->isDMConversation()) ? chScr->getDMContactPerms() : 0;
|
||||
ui_task.gotoDMConversation(savedDMName, savedDMIdx, savedPerms);
|
||||
} else if (wasDM) {
|
||||
ui_task.gotoContactsScreen();
|
||||
} else {
|
||||
ui_task.gotoChannelScreen();
|
||||
@@ -2699,22 +2718,34 @@ void handleKeyboardInput() {
|
||||
RepeaterAdminScreen::AdminState astate = admin->getState();
|
||||
bool shiftDel = (key == '\b' && keyboard.wasShiftConsumed());
|
||||
|
||||
// Helper: exit admin — room servers go to DM conversation if logged in, otherwise contacts
|
||||
auto exitAdmin = [&]() {
|
||||
int cidx = admin->getContactIdx();
|
||||
uint8_t perms = admin->getPermissions() & 0x03;
|
||||
ContactInfo ci;
|
||||
if (cidx >= 0 && perms > 0 && the_mesh.getContactByIdx(cidx, ci) && ci.type == ADV_TYPE_ROOM) {
|
||||
ui_task.gotoDMConversation(ci.name, cidx, perms);
|
||||
Serial.printf("Nav: Admin -> conversation for %s\n", ci.name);
|
||||
} else {
|
||||
ui_task.gotoContactsScreen();
|
||||
Serial.println("Nav: Admin -> contacts");
|
||||
}
|
||||
};
|
||||
|
||||
// In password entry: Shift+Del exits, all other keys pass through normally
|
||||
if (astate == RepeaterAdminScreen::STATE_PASSWORD_ENTRY) {
|
||||
if (shiftDel) {
|
||||
Serial.println("Nav: Back to contacts from admin login");
|
||||
ui_task.gotoContactsScreen();
|
||||
exitAdmin();
|
||||
} else {
|
||||
ui_task.injectKey(key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// In category menu (top level): Shift+Del exits to contacts, C opens compose
|
||||
// In category menu (top level): Shift+Del exits, C opens compose
|
||||
if (astate == RepeaterAdminScreen::STATE_CATEGORY_MENU) {
|
||||
if (shiftDel) {
|
||||
Serial.println("Nav: Back to contacts from admin menu");
|
||||
ui_task.gotoContactsScreen();
|
||||
exitAdmin();
|
||||
return;
|
||||
}
|
||||
// C key: allow entering compose mode from admin menu
|
||||
@@ -3270,10 +3301,6 @@ void handleKeyboardInput() {
|
||||
// L = Login/Admin — from DM conversation, open repeater admin with auto-login
|
||||
if (ui_task.isOnChannelScreen()) {
|
||||
ChannelScreen* chScr = (ChannelScreen*)ui_task.getChannelScreen();
|
||||
Serial.printf("[L key] onChannelScreen=1, isDMConv=%d, perms=%d, cidx=%d\n",
|
||||
chScr ? chScr->isDMConversation() : -1,
|
||||
chScr ? chScr->getDMContactPerms() : -1,
|
||||
chScr ? chScr->getDMContactIdx() : -1);
|
||||
if (chScr && chScr->isDMConversation() && chScr->getDMContactPerms() > 0) {
|
||||
int cidx = chScr->getDMContactIdx();
|
||||
if (cidx >= 0) {
|
||||
@@ -3515,6 +3542,8 @@ void sendComposedMessage() {
|
||||
// Direct message to a specific contact
|
||||
if (composeDMContactIdx >= 0) {
|
||||
if (the_mesh.uiSendDirectMessage((uint32_t)composeDMContactIdx, utf8Buf)) {
|
||||
// Add to channel screen so sent DM appears in conversation view
|
||||
ui_task.addSentDM(composeDMName, the_mesh.getNodePrefs()->node_name, utf8Buf);
|
||||
ui_task.showAlert("DM sent!", 1500);
|
||||
} else {
|
||||
ui_task.showAlert("DM failed!", 1500);
|
||||
|
||||
@@ -59,11 +59,19 @@ public:
|
||||
uint8_t path_len;
|
||||
uint8_t channel_idx; // Which channel this message belongs to
|
||||
int8_t snr; // Receive SNR × 4 (0 if locally sent or unknown)
|
||||
uint32_t dm_peer_hash; // DM peer name hash (for conversation filtering)
|
||||
uint8_t path[MSG_PATH_MAX]; // Repeater hop hashes
|
||||
char text[CHANNEL_MSG_TEXT_LEN];
|
||||
bool valid;
|
||||
};
|
||||
|
||||
// Simple hash for DM peer matching
|
||||
static uint32_t peerHash(const char* s) {
|
||||
uint32_t h = 5381;
|
||||
while (*s) { h = ((h << 5) + h) ^ (uint8_t)*s++; }
|
||||
return h;
|
||||
}
|
||||
|
||||
private:
|
||||
UITask* _task;
|
||||
mesh::RTCClock* _rtc;
|
||||
@@ -100,13 +108,10 @@ private:
|
||||
if (_viewChannelIdx != 0xFF) {
|
||||
return msg.channel_idx == _viewChannelIdx;
|
||||
}
|
||||
// DM tab in conversation mode: filter by sender name
|
||||
// DM tab in conversation mode: filter by peer hash
|
||||
if (!_dmInboxMode && _dmFilterName[0] != '\0') {
|
||||
if (msg.channel_idx != 0xFF) return false;
|
||||
int nameLen = strlen(_dmFilterName);
|
||||
if (strncmp(msg.text, _dmFilterName, nameLen) != 0) return false;
|
||||
if (msg.text[nameLen] != ':') return false;
|
||||
return true;
|
||||
return msg.dm_peer_hash == peerHash(_dmFilterName);
|
||||
}
|
||||
// Inbox mode or no filter — match all DMs
|
||||
return msg.channel_idx == 0xFF;
|
||||
@@ -127,6 +132,7 @@ public:
|
||||
// Initialize all messages as invalid
|
||||
for (int i = 0; i < CHANNEL_MSG_HISTORY_SIZE; i++) {
|
||||
_messages[i].valid = false;
|
||||
_messages[i].dm_peer_hash = 0;
|
||||
memset(_messages[i].path, 0, MSG_PATH_MAX);
|
||||
}
|
||||
// Initialize unread counts
|
||||
@@ -136,8 +142,9 @@ public:
|
||||
void setSDReady(bool ready) { _sdReady = ready; }
|
||||
|
||||
// Add a new message to the history
|
||||
// peer_name: for DMs, the contact this message belongs to (sender for received, recipient for sent)
|
||||
void addMessage(uint8_t channel_idx, uint8_t path_len, const char* sender, const char* text,
|
||||
const uint8_t* path_bytes = nullptr, int8_t snr = 0) {
|
||||
const uint8_t* path_bytes = nullptr, int8_t snr = 0, const char* peer_name = nullptr) {
|
||||
// Move to next slot in circular buffer
|
||||
_newestIdx = (_newestIdx + 1) % CHANNEL_MSG_HISTORY_SIZE;
|
||||
|
||||
@@ -148,6 +155,13 @@ public:
|
||||
msg->snr = snr;
|
||||
msg->valid = true;
|
||||
|
||||
// Set DM peer hash for conversation filtering
|
||||
if (channel_idx == 0xFF) {
|
||||
msg->dm_peer_hash = peerHash(peer_name ? peer_name : sender);
|
||||
} else {
|
||||
msg->dm_peer_hash = 0;
|
||||
}
|
||||
|
||||
// Store path hop hashes
|
||||
memset(msg->path, 0, MSG_PATH_MAX);
|
||||
if (path_bytes && path_len > 0 && path_len != 0xFF) {
|
||||
@@ -226,8 +240,6 @@ public:
|
||||
_dmContactIdx = contactIdx;
|
||||
_dmContactPerms = perms;
|
||||
_scrollPos = 0;
|
||||
Serial.printf("[ChannelScreen] openConversation: name=%s, idx=%d, perms=%d\n",
|
||||
contactName, contactIdx, perms);
|
||||
}
|
||||
|
||||
int getDMContactIdx() const { return _dmContactIdx; }
|
||||
@@ -1376,8 +1388,6 @@ public:
|
||||
display.setCursor(display.width() - display.getTextWidth(rightText) - 2, footerY);
|
||||
display.print(rightText);
|
||||
} else if (_viewChannelIdx == 0xFF) {
|
||||
Serial.printf("[DM Footer] perms=%d, contactIdx=%d, inboxMode=%d\n",
|
||||
_dmContactPerms, _dmContactIdx, _dmInboxMode);
|
||||
if (_dmContactPerms > 0) {
|
||||
display.print("Q:Exit L:Admin");
|
||||
} else {
|
||||
|
||||
@@ -475,6 +475,7 @@ public:
|
||||
|
||||
int getContactIdx() const { return _contactIdx; }
|
||||
AdminState getState() const { return _state; }
|
||||
uint8_t getPermissions() const { return _permissions; }
|
||||
|
||||
void onLoginResult(bool success, uint8_t permissions, uint32_t server_time) {
|
||||
_waitingForLogin = false;
|
||||
|
||||
@@ -1287,7 +1287,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
if (_dedup[i].name_hash == nameH && _dedup[i].text_hash == textH &&
|
||||
(now - _dedup[i].millis) < MSG_DEDUP_WINDOW_MS) {
|
||||
// Duplicate — suppress UI notification but still queued for BLE sync
|
||||
Serial.printf("[Dedup] Suppressed duplicate from %s\n", from_name);
|
||||
Serial.println("[Dedup] Suppressed duplicate");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1963,12 +1963,26 @@ void UITask::onVKBSubmit() {
|
||||
case VKB_DM: {
|
||||
if (strlen(text) == 0) break;
|
||||
|
||||
bool dmSuccess = false;
|
||||
if (the_mesh.uiSendDirectMessage((uint32_t)idx, text)) {
|
||||
showAlert("DM sent!", 1500);
|
||||
} else {
|
||||
showAlert("DM failed!", 1500);
|
||||
// Add to channel screen so sent DM appears in conversation view
|
||||
ContactInfo dmRecipient;
|
||||
if (the_mesh.getContactByIdx(idx, dmRecipient)) {
|
||||
addSentDM(dmRecipient.name, the_mesh.getNodePrefs()->node_name, text);
|
||||
}
|
||||
dmSuccess = true;
|
||||
}
|
||||
if (_screenBeforeVKB) setCurrScreen(_screenBeforeVKB);
|
||||
// Return to DM conversation if we have contact info
|
||||
ContactInfo dmContact;
|
||||
if (the_mesh.getContactByIdx(idx, dmContact)) {
|
||||
ChannelScreen* cs = (ChannelScreen*)channel_screen;
|
||||
uint8_t savedPerms = (cs && cs->isDMConversation()) ? cs->getDMContactPerms() : 0;
|
||||
gotoDMConversation(dmContact.name, idx, savedPerms);
|
||||
} else if (_screenBeforeVKB) {
|
||||
setCurrScreen(_screenBeforeVKB);
|
||||
}
|
||||
// Show alert AFTER navigation (setCurrScreen clears prior alerts)
|
||||
showAlert(dmSuccess ? "DM sent!" : "DM failed!", 1500);
|
||||
break;
|
||||
}
|
||||
case VKB_ADMIN_PASSWORD: {
|
||||
@@ -2219,11 +2233,14 @@ bool UITask::isHomeOnRecentPage() const {
|
||||
}
|
||||
|
||||
void UITask::gotoChannelScreen() {
|
||||
((ChannelScreen *) channel_screen)->resetScroll();
|
||||
ChannelScreen* cs = (ChannelScreen*)channel_screen;
|
||||
// If currently showing DM view, reset to channel 0
|
||||
if (cs->getViewChannelIdx() == 0xFF) {
|
||||
cs->setViewChannelIdx(0);
|
||||
}
|
||||
cs->resetScroll();
|
||||
// Mark the currently viewed channel as read
|
||||
((ChannelScreen *) channel_screen)->markChannelRead(
|
||||
((ChannelScreen *) channel_screen)->getViewChannelIdx()
|
||||
);
|
||||
cs->markChannelRead(cs->getViewChannelIdx());
|
||||
setCurrScreen(channel_screen);
|
||||
if (_display != NULL && !_display->isOn()) {
|
||||
_display->turnOn();
|
||||
@@ -2362,6 +2379,14 @@ void UITask::addSentChannelMessage(uint8_t channel_idx, const char* sender, cons
|
||||
((ChannelScreen *) channel_screen)->addMessage(channel_idx, 0, sender, formattedMsg);
|
||||
}
|
||||
|
||||
void UITask::addSentDM(const char* recipientName, const char* sender, const char* text) {
|
||||
// Format as "Sender: message" and tag with recipient's peer hash
|
||||
char formattedMsg[CHANNEL_MSG_TEXT_LEN];
|
||||
snprintf(formattedMsg, sizeof(formattedMsg), "%s: %s", sender, text);
|
||||
((ChannelScreen *) channel_screen)->addMessage(0xFF, 0, sender, formattedMsg,
|
||||
nullptr, 0, recipientName);
|
||||
}
|
||||
|
||||
void UITask::markChannelReadFromBLE(uint8_t channel_idx) {
|
||||
((ChannelScreen *) channel_screen)->markChannelRead(channel_idx);
|
||||
// If clearing DMs, also zero all per-contact DM counts
|
||||
@@ -2505,8 +2530,6 @@ void UITask::onAdminLoginResult(bool success, uint8_t permissions, uint32_t serv
|
||||
ContactInfo contact;
|
||||
if (the_mesh.getContactByIdx(cidx, contact) && contact.type == ADV_TYPE_ROOM) {
|
||||
uint8_t maskedPerms = permissions & 0x03;
|
||||
Serial.printf("[Admin] Room login (raw=%d, masked=%d) — opening conversation for %s\n",
|
||||
permissions, maskedPerms, contact.name);
|
||||
gotoDMConversation(contact.name, cidx, maskedPerms);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -262,6 +262,7 @@ public:
|
||||
|
||||
// Add a sent message to the channel screen history
|
||||
void addSentChannelMessage(uint8_t channel_idx, const char* sender, const char* text) override;
|
||||
void addSentDM(const char* recipientName, const char* sender, const char* text);
|
||||
|
||||
// Mark channel as read when BLE companion app syncs messages
|
||||
void markChannelReadFromBLE(uint8_t channel_idx) override;
|
||||
|
||||
Reference in New Issue
Block a user