mirror of
https://github.com/pelgraine/Meck.git
synced 2026-08-07 09:12:44 +02:00
Delete t-echo card and t-echo lite folders as no longer working on those; support json import & export config; new method for creating private and hashtag channels
This commit is contained in:
@@ -661,6 +661,34 @@ void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t
|
||||
_voiceEnvHandler(from.name, text);
|
||||
}
|
||||
|
||||
// Intercept channel share messages before BLE gets them
|
||||
if (text && strncmp(text, MECK_CH_PREFIX, MECK_CH_PREFIX_LEN) == 0) {
|
||||
const char* payload = text + MECK_CH_PREFIX_LEN;
|
||||
const char* sep = strchr(payload, '|');
|
||||
if (sep && (sep - payload) > 0 && (sep - payload) < 32) {
|
||||
char chName[32];
|
||||
int nameLen = sep - payload;
|
||||
memcpy(chName, payload, nameLen);
|
||||
chName[nameLen] = '\0';
|
||||
|
||||
// Parse hex secret (32 hex chars = 16 bytes)
|
||||
const char* hexStr = sep + 1;
|
||||
int hexLen = strlen(hexStr);
|
||||
if (hexLen >= 32) {
|
||||
uint8_t secret[16];
|
||||
mesh::Utils::fromHex(secret, 16, hexStr);
|
||||
addPendingInvite(chName, secret, from.name);
|
||||
Serial.printf("Channel invite from %s: '%s'\n", from.name, chName);
|
||||
}
|
||||
|
||||
// Sanitise display text
|
||||
char sanitised[64];
|
||||
snprintf(sanitised, sizeof(sanitised), "Shared channel: %s", chName);
|
||||
queueMessage(from, TXT_TYPE_PLAIN, pkt, sender_timestamp, NULL, 0, sanitised);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
queueMessage(from, TXT_TYPE_PLAIN, pkt, sender_timestamp, NULL, 0, text);
|
||||
}
|
||||
|
||||
@@ -1327,6 +1355,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key));
|
||||
memset(_sent_track, 0, sizeof(_sent_track));
|
||||
_sent_track_idx = 0;
|
||||
memset(_pendingInvites, 0, sizeof(_pendingInvites));
|
||||
_admin_contact_idx = -1;
|
||||
_discoveredCount = 0;
|
||||
_discoveryActive = false;
|
||||
|
||||
@@ -96,10 +96,22 @@ struct AdvertPath {
|
||||
struct DiscoveredNode {
|
||||
ContactInfo contact;
|
||||
uint8_t path_len;
|
||||
int8_t snr; // SNR × 4 from active discovery response (0 if pre-seeded)
|
||||
int8_t snr; // SNR x 4 from active discovery response (0 if pre-seeded)
|
||||
bool already_in_contacts; // true if contact was auto-added or already known
|
||||
};
|
||||
|
||||
// Channel invite received via DM -- stored in RAM until accepted/dismissed
|
||||
#define MAX_PENDING_INVITES 8
|
||||
#define MECK_CH_PREFIX "[MECK:CH]"
|
||||
#define MECK_CH_PREFIX_LEN 9
|
||||
|
||||
struct PendingChannelInvite {
|
||||
char name[32]; // channel name
|
||||
uint8_t secret[16]; // channel secret (CIPHER_KEY_SIZE bytes)
|
||||
char senderName[32]; // who shared it
|
||||
bool active; // is this slot in use
|
||||
};
|
||||
|
||||
class MyMesh : public BaseChatMesh, public DataStoreHost {
|
||||
public:
|
||||
MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui=NULL);
|
||||
@@ -257,6 +269,48 @@ public:
|
||||
_store->saveMainIdentity(self_id);
|
||||
}
|
||||
|
||||
// --- Pending channel invites (received via DM) ---
|
||||
int getPendingInviteCount() const {
|
||||
int count = 0;
|
||||
for (int i = 0; i < MAX_PENDING_INVITES; i++) {
|
||||
if (_pendingInvites[i].active) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
const PendingChannelInvite* getPendingInvite(int idx) const {
|
||||
int seen = 0;
|
||||
for (int i = 0; i < MAX_PENDING_INVITES; i++) {
|
||||
if (_pendingInvites[i].active) {
|
||||
if (seen == idx) return &_pendingInvites[i];
|
||||
seen++;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
bool addPendingInvite(const char* name, const uint8_t* secret, const char* senderName) {
|
||||
for (int i = 0; i < MAX_PENDING_INVITES; i++) {
|
||||
if (!_pendingInvites[i].active) {
|
||||
strncpy(_pendingInvites[i].name, name, 31);
|
||||
_pendingInvites[i].name[31] = '\0';
|
||||
memcpy(_pendingInvites[i].secret, secret, 16);
|
||||
strncpy(_pendingInvites[i].senderName, senderName, 31);
|
||||
_pendingInvites[i].senderName[31] = '\0';
|
||||
_pendingInvites[i].active = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // no free slots
|
||||
}
|
||||
void removePendingInvite(int idx) {
|
||||
int seen = 0;
|
||||
for (int i = 0; i < MAX_PENDING_INVITES; i++) {
|
||||
if (_pendingInvites[i].active) {
|
||||
if (seen == idx) { _pendingInvites[i].active = false; return; }
|
||||
seen++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void writeOKFrame();
|
||||
void writeErrFrame(uint8_t err_code);
|
||||
@@ -282,6 +336,7 @@ private:
|
||||
mutable bool _forceNextImport = false;
|
||||
bool _deferSaves = false;
|
||||
unsigned long _lastUserInput = 0; // millis() of last keypress -- defer saves until idle
|
||||
PendingChannelInvite _pendingInvites[MAX_PENDING_INVITES]; // RAM-only pending channel shares
|
||||
uint32_t pending_login;
|
||||
uint32_t pending_status;
|
||||
uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ
|
||||
|
||||
@@ -4591,6 +4591,37 @@ void handleKeyboardInput() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Check for channel share request from the settings screen
|
||||
if (settings->isShareRequested()) {
|
||||
int contactIdx = settings->getShareContactIdx();
|
||||
uint8_t channelIdx = settings->getShareChannelIdx();
|
||||
settings->clearShareRequest();
|
||||
|
||||
ChannelDetails ch;
|
||||
ContactInfo contact;
|
||||
if (the_mesh.getChannel(channelIdx, ch) && ch.name[0] != '\0'
|
||||
&& the_mesh.getContactByIdx(contactIdx, contact)) {
|
||||
// Build share message: [MECK:CH]name|secret_hex
|
||||
char shareMsg[128];
|
||||
char hexSecret[33];
|
||||
mesh::Utils::toHex(hexSecret, ch.channel.secret, 16);
|
||||
snprintf(shareMsg, sizeof(shareMsg), "%s%s|%s",
|
||||
MECK_CH_PREFIX, ch.name, hexSecret);
|
||||
|
||||
if (the_mesh.uiSendDirectMessage((uint32_t)contactIdx, shareMsg)) {
|
||||
// Add sanitised version to DM conversation view
|
||||
char displayMsg[64];
|
||||
snprintf(displayMsg, sizeof(displayMsg), "Shared channel: %s", ch.name);
|
||||
ui_task.addSentDM(contact.name, the_mesh.getNodePrefs()->node_name, displayMsg);
|
||||
|
||||
char alertBuf[48];
|
||||
snprintf(alertBuf, sizeof(alertBuf), "Shared with %s", contact.name);
|
||||
ui_task.showAlert(alertBuf, 2000);
|
||||
} else {
|
||||
ui_task.showAlert("Share failed", 1500);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,8 @@ enum SettingsRowType : uint8_t {
|
||||
ROW_CHANNELS_SUBMENU, // Folder row → enters Channels sub-screen
|
||||
ROW_CH_HEADER, // "--- Channels ---" separator
|
||||
ROW_CHANNEL, // A channel entry (dynamic, index stored separately)
|
||||
ROW_ADD_CHANNEL, // "+ Add Hashtag Channel"
|
||||
ROW_ADD_CHANNEL, // "+ Add Channel (# = public)"
|
||||
ROW_PENDING_INVITE, // Pending channel invite (param = invite index)
|
||||
#ifdef HAS_SDCARD
|
||||
ROW_EXPORT_IMPORT_SUBMENU, // Folder row: "Export/Import >>"
|
||||
ROW_EXPORT_TO_SD, // "Export to SD >>" (enters flags sub-screen)
|
||||
@@ -201,6 +202,7 @@ enum EditMode : uint8_t {
|
||||
EDIT_NUMBER, // W/S adjusts value (freq, BW, SF, CR, TX, UTC)
|
||||
EDIT_CONFIRM, // Confirmation dialog (delete channel, apply radio)
|
||||
EDIT_NOTIF_SOUND, // Sound picker for per-channel notification tone
|
||||
EDIT_SHARE_PICK, // Contact picker for channel sharing
|
||||
#ifdef MECK_WIFI_COMPANION
|
||||
EDIT_WIFI, // WiFi scan/select/password flow
|
||||
#endif
|
||||
@@ -307,6 +309,16 @@ private:
|
||||
bool _importRequested; // set by key handler, cleared by main.cpp after calling import
|
||||
#endif
|
||||
|
||||
// Channel share picker state
|
||||
#define SHARE_MAX_CONTACTS 32
|
||||
uint8_t _shareChannelIdx; // channel being shared
|
||||
int _shareContacts[SHARE_MAX_CONTACTS]; // indices of DM-capable contacts
|
||||
int _shareContactCount; // number of entries in _shareContacts
|
||||
int _sharePickerIdx; // highlighted item in picker
|
||||
int _sharePickerScroll; // scroll offset in picker
|
||||
bool _shareRequested; // flag for main.cpp
|
||||
int _shareContactIdx; // selected contact index (for main.cpp)
|
||||
|
||||
// Dirty flag for radio params  prompt to apply
|
||||
bool _radioChanged;
|
||||
|
||||
@@ -433,6 +445,10 @@ private:
|
||||
}
|
||||
}
|
||||
addRow(ROW_ADD_CHANNEL);
|
||||
// Pending channel invites (received via DM)
|
||||
for (int pi = 0; pi < the_mesh.getPendingInviteCount(); pi++) {
|
||||
addRow(ROW_PENDING_INVITE, pi);
|
||||
}
|
||||
#ifdef MECK_OTA_UPDATE
|
||||
} else if (_subScreen == SUB_OTA_TOOLS) {
|
||||
// --- OTA Tools sub-screen ---
|
||||
@@ -564,28 +580,33 @@ private:
|
||||
// Hashtag channel creation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void createHashtagChannel(const char* name) {
|
||||
// Build channel name with # prefix if not already present
|
||||
char chanName[32];
|
||||
if (name[0] == '#') {
|
||||
strncpy(chanName, name, sizeof(chanName));
|
||||
} else {
|
||||
chanName[0] = '#';
|
||||
strncpy(&chanName[1], name, sizeof(chanName) - 1);
|
||||
}
|
||||
chanName[31] = '\0';
|
||||
|
||||
// Generate 128-bit PSK from SHA-256 of channel name
|
||||
void createChannel(const char* name) {
|
||||
ChannelDetails newCh;
|
||||
memset(&newCh, 0, sizeof(newCh));
|
||||
strncpy(newCh.name, chanName, sizeof(newCh.name));
|
||||
newCh.name[31] = '\0';
|
||||
|
||||
// SHA-256 the channel name → first 16 bytes become the secret
|
||||
uint8_t hash[32];
|
||||
mesh::Utils::sha256(hash, 32, (const uint8_t*)chanName, strlen(chanName));
|
||||
memcpy(newCh.channel.secret, hash, 16);
|
||||
// Upper 16 bytes left as zero → setChannel uses 128-bit mode
|
||||
if (name[0] == '#') {
|
||||
// Public hashtag channel -- derive secret from SHA-256 of name
|
||||
strncpy(newCh.name, name, sizeof(newCh.name));
|
||||
newCh.name[31] = '\0';
|
||||
|
||||
uint8_t hash[32];
|
||||
mesh::Utils::sha256(hash, 32, (const uint8_t*)name, strlen(name));
|
||||
memcpy(newCh.channel.secret, hash, 16);
|
||||
Serial.printf("Settings: Creating public channel '%s'\n", name);
|
||||
} else {
|
||||
// Private channel -- random 16-byte secret
|
||||
strncpy(newCh.name, name, sizeof(newCh.name));
|
||||
newCh.name[31] = '\0';
|
||||
|
||||
uint8_t secret[16];
|
||||
uint32_t r;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (i % 4 == 0) r = esp_random();
|
||||
secret[i] = (r >> ((i % 4) * 8)) & 0xFF;
|
||||
}
|
||||
memcpy(newCh.channel.secret, secret, 16);
|
||||
Serial.printf("Settings: Creating private channel '%s'\n", name);
|
||||
}
|
||||
|
||||
// Find next empty slot
|
||||
for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
@@ -593,13 +614,14 @@ private:
|
||||
if (!the_mesh.getChannel(i, existing) || existing.name[0] == '\0') {
|
||||
if (the_mesh.setChannel(i, newCh)) {
|
||||
the_mesh.saveChannels();
|
||||
Serial.printf("Settings: Created hashtag channel '%s' at idx %d\n", chanName, i);
|
||||
Serial.printf("Settings: Channel '%s' created at idx %d\n", newCh.name, i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void deleteChannel(uint8_t idx) {
|
||||
// Clear the channel by writing an empty ChannelDetails
|
||||
// Then compact: shift all channels above it down by one
|
||||
@@ -656,6 +678,12 @@ public:
|
||||
_exportRequested = false;
|
||||
_importRequested = false;
|
||||
#endif
|
||||
_shareChannelIdx = 0;
|
||||
_shareContactCount = 0;
|
||||
_sharePickerIdx = 0;
|
||||
_sharePickerScroll = 0;
|
||||
_shareRequested = false;
|
||||
_shareContactIdx = -1;
|
||||
#ifdef MECK_OTA_UPDATE
|
||||
_otaServer = nullptr;
|
||||
_otaPhase = OTA_PHASE_CONFIRM;
|
||||
@@ -686,6 +714,9 @@ public:
|
||||
_exportRequested = false;
|
||||
_importRequested = false;
|
||||
#endif
|
||||
_shareRequested = false;
|
||||
_shareContactIdx = -1;
|
||||
_shareContactCount = 0;
|
||||
#ifdef HAS_4G_MODEM
|
||||
_modemEnabled = ModemManager::loadEnabledConfig();
|
||||
#endif
|
||||
@@ -885,6 +916,12 @@ public:
|
||||
void clearImportRequest() { _importRequested = false; }
|
||||
#endif
|
||||
|
||||
// Channel share request -- checked and cleared by main.cpp
|
||||
bool isShareRequested() const { return _shareRequested; }
|
||||
int getShareContactIdx() const { return _shareContactIdx; }
|
||||
uint8_t getShareChannelIdx() const { return _shareChannelIdx; }
|
||||
void clearShareRequest() { _shareRequested = false; _shareContactIdx = -1; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OTA firmware update
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -2111,14 +2148,24 @@ public:
|
||||
|
||||
case ROW_ADD_CHANNEL:
|
||||
if (editing && _editMode == EDIT_TEXT) {
|
||||
snprintf(tmp, sizeof(tmp), "# %s_", _editBuf);
|
||||
snprintf(tmp, sizeof(tmp), "> %s_", _editBuf);
|
||||
} else {
|
||||
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN);
|
||||
strcpy(tmp, "+ Add Hashtag Channel");
|
||||
strcpy(tmp, "+ Add Channel (# = public)");
|
||||
}
|
||||
display.print(tmp);
|
||||
break;
|
||||
|
||||
case ROW_PENDING_INVITE: {
|
||||
const PendingChannelInvite* inv = the_mesh.getPendingInvite(_rows[i].param);
|
||||
if (inv) {
|
||||
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::YELLOW);
|
||||
snprintf(tmp, sizeof(tmp), "Pending: %s", inv->name);
|
||||
display.print(tmp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef MECK_OTA_UPDATE
|
||||
case ROW_OTA_TOOLS_SUBMENU:
|
||||
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN);
|
||||
@@ -2568,6 +2615,69 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
// === Share contact picker overlay ===
|
||||
if (_editMode == EDIT_SHARE_PICK) {
|
||||
int bx = 2, by = 14, bw = display.width() - 4;
|
||||
int bh = display.height() - 28;
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
display.fillRect(bx, by, bw, bh);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(bx, by, bw, bh);
|
||||
|
||||
display.setTextSize(_prefs->smallTextSize());
|
||||
int lineH = _prefs->smallLineH();
|
||||
|
||||
// Header
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setCursor(bx + 4, by + 3);
|
||||
display.print("Share with contact:");
|
||||
|
||||
if (_shareContactCount == 0) {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setCursor(bx + 4, by + 16);
|
||||
display.print("No contacts available");
|
||||
} else {
|
||||
int listTop = by + 14;
|
||||
int listBot = by + bh - 14;
|
||||
int maxVisible = (listBot - listTop) / lineH;
|
||||
if (maxVisible < 3) maxVisible = 3;
|
||||
|
||||
// Scroll to keep selection visible
|
||||
if (_sharePickerIdx < _sharePickerScroll) _sharePickerScroll = _sharePickerIdx;
|
||||
if (_sharePickerIdx >= _sharePickerScroll + maxVisible) _sharePickerScroll = _sharePickerIdx - maxVisible + 1;
|
||||
|
||||
for (int vi = 0; vi < maxVisible && (_sharePickerScroll + vi) < _shareContactCount; vi++) {
|
||||
int ci = _sharePickerScroll + vi;
|
||||
int iy = listTop + vi * lineH;
|
||||
bool sel = (ci == _sharePickerIdx);
|
||||
|
||||
if (sel) {
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.fillRect(bx + 1, iy, bw - 2, lineH);
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
} else {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
}
|
||||
|
||||
ContactInfo ci_info;
|
||||
if (the_mesh.getContactByIdx(_shareContacts[ci], ci_info)) {
|
||||
display.setCursor(bx + 4, iy + 1);
|
||||
display.print(ci_info.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Footer hint
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
display.setCursor(bx + 4, by + bh - 12);
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
display.print("Tap:Send Boot:Cancel");
|
||||
#else
|
||||
display.print("Enter:Send Q:Cancel");
|
||||
#endif
|
||||
display.setTextSize(1);
|
||||
}
|
||||
|
||||
// === Footer ===
|
||||
int footerY = display.height() - 12;
|
||||
display.drawRect(0, footerY - 2, display.width(), 1);
|
||||
@@ -2817,6 +2927,33 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
// --- Share contact picker ---
|
||||
if (_editMode == EDIT_SHARE_PICK) {
|
||||
if (c == 'w' || c == 'W' || c == 0xF2 || c == KEY_UP) {
|
||||
if (_sharePickerIdx > 0) _sharePickerIdx--;
|
||||
return true;
|
||||
}
|
||||
if (c == 's' || c == 'S' || c == 0xF1 || c == KEY_DOWN) {
|
||||
if (_sharePickerIdx < _shareContactCount - 1) _sharePickerIdx++;
|
||||
return true;
|
||||
}
|
||||
if (c == '\r' || c == 13) {
|
||||
if (_shareContactCount > 0) {
|
||||
_shareContactIdx = _shareContacts[_sharePickerIdx];
|
||||
_shareRequested = true;
|
||||
Serial.printf("Settings: share channel %d with contact %d\n",
|
||||
_shareChannelIdx, _shareContactIdx);
|
||||
}
|
||||
_editMode = EDIT_NONE;
|
||||
return true;
|
||||
}
|
||||
if (c == 'q' || c == 'Q' || c == '\b') {
|
||||
_editMode = EDIT_NONE;
|
||||
return true;
|
||||
}
|
||||
return true; // consume all keys in picker mode
|
||||
}
|
||||
|
||||
#ifdef MECK_OTA_UPDATE
|
||||
// --- OTA update flow ---
|
||||
if (_editMode == EDIT_OTA) {
|
||||
@@ -3015,7 +3152,7 @@ public:
|
||||
_editMode = EDIT_NONE;
|
||||
} else if (type == ROW_ADD_CHANNEL) {
|
||||
if (_editPos > 0) {
|
||||
createHashtagChannel(_editBuf);
|
||||
createChannel(_editBuf);
|
||||
rebuildRows();
|
||||
}
|
||||
_editMode = EDIT_NONE;
|
||||
@@ -3520,6 +3657,35 @@ public:
|
||||
case ROW_ADD_CHANNEL:
|
||||
startEditText("");
|
||||
break;
|
||||
|
||||
case ROW_PENDING_INVITE: {
|
||||
// Accept pending channel invite
|
||||
const PendingChannelInvite* inv = the_mesh.getPendingInvite(_rows[_cursor].param);
|
||||
if (inv) {
|
||||
ChannelDetails newCh;
|
||||
memset(&newCh, 0, sizeof(newCh));
|
||||
strncpy(newCh.name, inv->name, sizeof(newCh.name));
|
||||
newCh.name[31] = '\0';
|
||||
memcpy(newCh.channel.secret, inv->secret, 16);
|
||||
|
||||
// Find next empty slot
|
||||
bool added = false;
|
||||
for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
ChannelDetails existing;
|
||||
if (!the_mesh.getChannel(i, existing) || existing.name[0] == '\0') {
|
||||
if (the_mesh.setChannel(i, newCh)) {
|
||||
the_mesh.saveChannels();
|
||||
Serial.printf("Settings: Accepted channel '%s' at idx %d\n", inv->name, i);
|
||||
added = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
the_mesh.removePendingInvite(_rows[_cursor].param);
|
||||
rebuildRows();
|
||||
}
|
||||
break;
|
||||
}
|
||||
#ifdef MECK_OTA_UPDATE
|
||||
case ROW_OTA_TOOLS_SUBMENU:
|
||||
_savedTopCursor = _cursor;
|
||||
@@ -3609,12 +3775,19 @@ public:
|
||||
}
|
||||
|
||||
// X: delete channel (when on a channel row, idx > 0)
|
||||
// dismiss pending invite (when on a pending invite row)
|
||||
if (c == 'x' || c == 'X') {
|
||||
if (_rows[_cursor].type == ROW_CHANNEL && _rows[_cursor].param > 0) {
|
||||
_editMode = EDIT_CONFIRM;
|
||||
_confirmAction = 1;
|
||||
return true;
|
||||
}
|
||||
if (_rows[_cursor].type == ROW_PENDING_INVITE) {
|
||||
the_mesh.removePendingInvite(_rows[_cursor].param);
|
||||
rebuildRows();
|
||||
Serial.println("Settings: dismissed pending channel invite");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// N: cycle notification preference (All -> Mentions -> None -> All)
|
||||
@@ -3631,6 +3804,28 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// S: share channel with a contact via DM
|
||||
if (c == 's' || c == 'S') {
|
||||
if (_rows[_cursor].type == ROW_CHANNEL) {
|
||||
_shareChannelIdx = _rows[_cursor].param;
|
||||
// Populate contact list with DM-capable contacts
|
||||
_shareContactCount = 0;
|
||||
int numContacts = the_mesh.getNumContacts();
|
||||
for (int ci = 0; ci < numContacts && _shareContactCount < SHARE_MAX_CONTACTS; ci++) {
|
||||
ContactInfo contact;
|
||||
if (the_mesh.getContactByIdx(ci, contact) && contact.type == ADV_TYPE_CHAT) {
|
||||
_shareContacts[_shareContactCount++] = ci;
|
||||
}
|
||||
}
|
||||
_sharePickerIdx = 0;
|
||||
_sharePickerScroll = 0;
|
||||
_editMode = EDIT_SHARE_PICK;
|
||||
Serial.printf("Settings: sharing channel %d, %d contacts available\n",
|
||||
_shareChannelIdx, _shareContactCount);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// T: open notification tone picker
|
||||
#if defined(MECK_AUDIO_VARIANT) || defined(HAS_4G_MODEM)
|
||||
if (c == 't' || c == 'T') {
|
||||
|
||||
Reference in New Issue
Block a user