Browse Source

ui fixes

pull/2568/head
Christos Themelis 1 month ago
parent
commit
f3a3e0bdfb
  1. 1
      .gitignore
  2. 2
      examples/companion_radio/MyMesh.cpp
  3. 10
      examples/simple_secure_chat/main.cpp
  4. 44
      examples/simple_secure_chat_ui/main.cpp
  5. 14
      examples/simple_secure_chat_ui/task_clock.cpp
  6. 95
      examples/simple_secure_chat_ui/uiManager.cpp
  7. 1
      include/uiManager.h
  8. 2
      include/uiVars.h
  9. 5
      src/helpers/BaseChatMesh.cpp
  10. 20
      src/helpers/CommonCLI.cpp

1
.gitignore

@ -17,3 +17,4 @@ compile_commands.json
.venv/
venv/
examples/simple_secure_chat_ui/UI_PLAN.md
clock_sync_phase1_summary.md

2
examples/companion_radio/MyMesh.cpp

@ -357,7 +357,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
memcpy(p->path, path, p->path_len);
}
if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[]
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // contact is in contacts[], schedule lazy write
}
static int sort_by_recent(const void *a, const void *b) {

10
examples/simple_secure_chat/main.cpp

@ -158,13 +158,13 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
}
void setClock(uint32_t timestamp) {
uint32_t curr = getRTCClock()->getCurrentTime();
if (timestamp > curr) {
//uint32_t curr = getRTCClock()->getCurrentTime();
//if (timestamp > curr) {
getRTCClock()->setCurrentTime(timestamp);
Serial.println(" (OK - clock set!)");
} else {
Serial.println(" (ERR: clock cannot go backwards)");
}
//} else {
// Serial.println(" (ERR: clock cannot go backwards)");
//}
}
void importCard(const char* command) {

44
examples/simple_secure_chat_ui/main.cpp

@ -1,5 +1,7 @@
#include <Arduino.h> // needed for PlatformIO
#include <Mesh.h>
#include <time.h>
#include <sys/time.h>
#if defined(NRF52_PLATFORM)
#include <InternalFileSystem.h>
@ -81,6 +83,8 @@ UIManager *uiManager;
SemaphoreHandle_t semaphoreData;
volatile bool g_clock_synced = false;
TwoWire I2Cone = TwoWire(0);
#ifndef SEEED_SENSECAP_INDICATOR
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &I2Cone, OLED_RESET);
@ -374,14 +378,13 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
ContactInfo c;
uint8_t pub_key[32];
uint8_t unused;
uint32_t reserved;
bool success = (file.read(pub_key, 32) == 32);
success = success && (file.read((uint8_t *) &c.name, 32) == 32);
success = success && (file.read(&c.type, 1) == 1);
success = success && (file.read(&c.flags, 1) == 1);
success = success && (file.read(&unused, 1) == 1);
success = success && (file.read((uint8_t *) &reserved, 4) == 4);
success = success && (file.read((uint8_t *) &c.lastmod, 4) == 4);
success = success && (file.read((uint8_t *) &c.out_path_len, 1) == 1);
success = success && (file.read((uint8_t *) &c.last_advert_timestamp, 4) == 4);
success = success && (file.read(c.out_path, 64) == 64);
@ -390,7 +393,6 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
if (!success) break; // EOF
c.id = mesh::Identity(pub_key);
c.lastmod = 0;
uiManager->addContactToUI(c);
if (!addContact(c)) full = true;
}
@ -412,7 +414,6 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
ContactsIterator iter;
ContactInfo c;
uint8_t unused = 0;
uint32_t reserved = 0;
while (iter.hasNext(this, c)) {
bool success = (file.write(c.id.pub_key, 32) == 32);
@ -420,7 +421,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
success = success && (file.write(&c.type, 1) == 1);
success = success && (file.write(&c.flags, 1) == 1);
success = success && (file.write(&unused, 1) == 1);
success = success && (file.write((uint8_t *) &reserved, 4) == 4);
success = success && (file.write((uint8_t *) &c.lastmod, 4) == 4);
success = success && (file.write((uint8_t *) &c.out_path_len, 1) == 1);
success = success && (file.write((uint8_t *) &c.last_advert_timestamp, 4) == 4);
success = success && (file.write(c.out_path, 64) == 64);
@ -428,17 +429,20 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
if (!success) break; // write failed
}
file.close();
uiManager->addContactToUI(c);
}
}
void setClock(uint32_t timestamp) {
uint32_t curr = getRTCClock()->getCurrentTime();
if (timestamp > curr) {
getRTCClock()->setCurrentTime(timestamp);
Serial.println(" (OK - clock set!)");
} else {
Serial.println(" (ERR: clock cannot go backwards)");
getRTCClock()->setCurrentTime(timestamp);
struct timeval tv = { .tv_sec = (time_t)timestamp, .tv_usec = 0 };
settimeofday(&tv, nullptr);
g_clock_synced = true;
Serial.println(" (OK - clock set!)");
if (uiManager) {
time_t ts = (time_t)timestamp;
struct tm t;
localtime_r(&ts, &t);
uiManager->updateDateTime(t);
}
}
@ -478,12 +482,15 @@ protected:
}
void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) override {
// TODO: if not in favs, prompt to add as fav(?)
Serial.printf("ADVERT from -> %s\n", contact.name);
Serial.printf(" type: %s\n", getTypeName(contact.type));
Serial.print(" public key: "); mesh::Utils::printHex(Serial, contact.id.pub_key, PUB_KEY_SIZE); Serial.println();
if (is_new) {
uiManager->addContactToUI(contact);
} else {
uiManager->updateContactLastSeen(contact.id.pub_key, contact.lastmod);
}
saveContacts();
}
@ -511,6 +518,8 @@ protected:
Serial.printf("(%s) MSG -> from %s\n", pkt->isRouteDirect() ? "DIRECT" : "FLOOD", from.name);
Serial.printf(" %s\n", text);
setClock(sender_timestamp);
if (strcmp(text, "clock sync") == 0) { // special text command
setClock(sender_timestamp + 1);
}
@ -537,6 +546,8 @@ protected:
}
Serial.printf(" %s\n", text);
setClock(timestamp);
// Only public?
// if (strcmp(channel.secret, PUBLIC_GROUP_PSK) != 0)
// return;
@ -1055,6 +1066,11 @@ void initializeMesh() {
void setup() {
Serial.begin(115200);
// Greece: UTC+2 standard (EET), UTC+3 summer (EEST)
// Change this string to match your timezone if needed.
setenv("TZ", "EET-2EEST,M3.5.0/3,M10.5.0/4", 1);
tzset();
#ifdef PIN_USER_BTN
pinMode(PIN_USER_BTN, INPUT_PULLUP);
#endif

14
examples/simple_secure_chat_ui/task_clock.cpp

@ -1,4 +1,5 @@
#include <Arduino.h>
#include <time.h>
#include "esp_log.h"
#include "uiDefines.h"
@ -8,18 +9,17 @@
void clock_task(void *pvParameters) {
vTaskSuspend(NULL);
ESP_LOGI(TAG, "Clock manager: Task running on core %d", xPortGetCoreID());
uiManager->clearDateTime();
// TODO: sync clock
while (1) {
// uiManager->updateDateTime(
// myClock->getTimeStruct()
// );
// uiManager->updateValues();
if (g_clock_synced) {
time_t now = time(nullptr);
struct tm t;
localtime_r(&now, &t);
uiManager->updateDateTime(t);
}
vTaskDelay(DELAY_CLOCK_TASK / portTICK_PERIOD_MS);
}
}

95
examples/simple_secure_chat_ui/uiManager.cpp

@ -31,6 +31,7 @@ struct UIContactInfo {
ContactInfo info;
lv_obj_t* badge = nullptr;
lv_obj_t* badge_label = nullptr;
lv_obj_t* label_lastseen = nullptr;
uint32_t unread = 0;
};
@ -201,11 +202,11 @@ void UIManager::addChatBubble(const char *time_str, const char *sender, const ch
lv_obj_set_style_bg_color(bubble,
is_self ? lv_color_hex(0x1E88E5) : lv_color_hex(0x2C2C2C), 0);
// IMPORTANT: vertical layout inside bubble
// Vertical layout inside bubble; cross-align follows bubble side
lv_obj_set_flex_flow(bubble, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(bubble,
LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_START,
is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_START);
// Header row (sender + time)
@ -219,8 +220,8 @@ void UIManager::addChatBubble(const char *time_str, const char *sender, const ch
lv_obj_set_flex_flow(hdr, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(hdr,
LV_FLEX_ALIGN_SPACE_BETWEEN,
LV_FLEX_ALIGN_START, //LV_FLEX_ALIGN_CENTER, // Ευθυγράμμιση ονόματος/ώρας στον κάθετο άξονα
is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_START);
lv_obj_t *lbl_sender = lv_label_create(hdr);
@ -239,6 +240,7 @@ void UIManager::addChatBubble(const char *time_str, const char *sender, const ch
lv_label_set_text(lbl_msg, msg);
lv_label_set_long_mode(lbl_msg, LV_LABEL_LONG_WRAP);
lv_obj_set_width(lbl_msg, lv_pct(100));
lv_obj_set_style_text_align(lbl_msg, is_self ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_LEFT, 0);
lv_obj_set_style_text_color(lbl_msg, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl_msg, &lv_font_arial_26, 0);
@ -253,11 +255,11 @@ void UIManager::addChatBubble(const char *time_str, const char *sender, const ch
}
void UIManager::addPrivateChatBubble(const char *time_str, const char *msg, bool is_self) {
lv_obj_set_style_pad_bottom(ui_ContactMessages, 20, 0);
// 1. Row container
lv_obj_t* row = LvObj(ui_ContactMessages)
// 1. Row container – pushes bubble to the correct side
lv_obj_t* row = LvObj(ui_ContactMessages)
.width(lv_pct(100))
.height(LV_SIZE_CONTENT)
.bgOpa(0)
@ -266,59 +268,54 @@ void UIManager::addPrivateChatBubble(const char *time_str, const char *msg, bool
.scrollable(false)
.flexFlow(LV_FLEX_FLOW_ROW)
.flexAlign(
is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START,
is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START, // sent=right, received=left
LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_START
);
// 2. Aligner (Column) - label and time
lv_obj_t* aligner = LvObj(row)
.width(LV_SIZE_CONTENT)
.height(LV_SIZE_CONTENT)
.bgOpa(0)
.border(0)
.padAll(0)
.scrollable(false)
.flexFlow(LV_FLEX_FLOW_ROW)
.flexAlign(
is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_START
);
lv_obj_set_style_pad_row(aligner, 4, 0);
// 3. Το Label-Bubble (Ενοποιημένο)
lv_obj_t *lbl_msg = lv_label_create(aligner);
// 2. Bubble column: message text + timestamp below
lv_obj_t* aligner = LvObj(row)
.width(LV_SIZE_CONTENT)
.height(LV_SIZE_CONTENT)
.bgOpa(0)
.border(0)
.padAll(0)
.scrollable(false)
.flexFlow(LV_FLEX_FLOW_COLUMN)
.flexAlign(
LV_FLEX_ALIGN_START,
is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_START
);
lv_obj_set_style_pad_row(aligner, 2, 0);
// Long mode για wrap
// 3. Message bubble label
lv_obj_t *lbl_msg = lv_label_create(aligner);
lv_label_set_long_mode(lbl_msg, LV_LABEL_LONG_WRAP);
lv_label_set_text(lbl_msg, msg);
lv_obj_set_style_text_font(lbl_msg, &lv_font_arial_22, 0);
// Fixed max width για wrap
lv_obj_set_width(lbl_msg, 270); // max πλάτος
lv_obj_set_height(lbl_msg, LV_SIZE_CONTENT); // αυτόματο ύψος
// Bubble style
lv_obj_set_width(lbl_msg, 175); // max width – fits in 310px panel with room to shift
lv_obj_set_height(lbl_msg, LV_SIZE_CONTENT);
lv_obj_set_style_text_align(lbl_msg, is_self ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_LEFT, 0);
lv_obj_set_style_bg_opa(lbl_msg, LV_OPA_COVER, 0);
lv_obj_set_style_radius(lbl_msg, 12, 0);
lv_obj_set_style_pad_all(lbl_msg, 12, 0);
lv_obj_set_style_pad_all(lbl_msg, 10, 0);
if(is_self) {
lv_obj_set_style_bg_color(lbl_msg, lv_color_hex(0x1E88E5), 0);
lv_obj_set_style_text_color(lbl_msg, lv_color_hex(0xFFFFFF), 0);
if (is_self) {
lv_obj_set_style_bg_color(lbl_msg, lv_color_hex(0x1E88E5), 0);
lv_obj_set_style_text_color(lbl_msg, lv_color_hex(0xFFFFFF), 0);
} else {
lv_obj_set_style_bg_color(lbl_msg, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_color(lbl_msg, lv_color_hex(0x000000), 0);
lv_obj_set_style_bg_color(lbl_msg, lv_color_hex(0x2C2C2C), 0);
lv_obj_set_style_text_color(lbl_msg, lv_color_hex(0xFFFFFF), 0);
}
// 4. Η Ώρα
// 4. Timestamp below the bubble
lv_obj_t *lbl_time = lv_label_create(aligner);
lv_label_set_text(lbl_time, time_str);
lv_obj_set_style_text_color(lbl_time, lv_color_hex(0x808080), 0);
lv_obj_set_style_text_font(lbl_time, &lv_font_arial_14, 0);
lv_obj_scroll_to_view(row, LV_ANIM_ON);
lv_obj_scroll_to_view(row, LV_ANIM_OFF);
}
void UIManager::getInitials(const char *name, char *out) {
@ -511,13 +508,15 @@ void UIManager::addContactToUI(ContactInfo c)
char lastSeen[32];
formatLastSeen(c.lastmod, lastSeen, sizeof(lastSeen));
LvLabel(text_col)
lv_obj_t* lbl_ls = LvLabel(text_col)
.text(lastSeen)
.position(0, PAD + 32)
.width(text_w)
.font(&lv_font_arial_16)
.textColor(0x888888)
.wrap(false);
.wrap(false)
.raw();
store->label_lastseen = lbl_ls;
// ============================
// Unread badge (top-right corner of row)
@ -551,6 +550,16 @@ void UIManager::addContactToUI(ContactInfo c)
LvObj(text_col, true).clickable(false);
}
void UIManager::updateContactLastSeen(const uint8_t* pub_key, uint32_t lastmod)
{
UIContactInfo* uic = findContactByPubKey(ui_Contacts, pub_key);
if (!uic || !uic->label_lastseen) return;
uic->info.lastmod = lastmod;
char buf[32];
formatLastSeen(lastmod, buf, sizeof(buf));
lv_label_set_text(uic->label_lastseen, buf);
}
void UIManager::onShowKeyboard()
{
LvKeyboard(ui_Keyboard, true).show(true);

1
include/uiManager.h

@ -126,6 +126,7 @@ class UIManager {
void addPrivateChatBubble(const char *time_str, const char *msg, bool is_self);
void addChatBubble(const char *time_str, const char *sender, const char *msg,bool is_self);
void addContactToUI(ContactInfo c);
void updateContactLastSeen(const uint8_t* pub_key, uint32_t lastmod);
void handleContactClick(lv_event_t *e);
void setNightMode(bool night);
};

2
include/uiVars.h

@ -17,4 +17,6 @@ extern SemaphoreHandle_t semaphoreData;
extern UIManager *uiManager;
extern lv_obj_t * ui_MainTabView;
extern volatile bool g_clock_synced;
#endif

5
src/helpers/BaseChatMesh.cpp

@ -133,7 +133,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
}
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
bool is_new = false; // true = not in contacts[], false = exists in contacts[]
bool is_new = false; // true = newly discovered (not previously in contacts[]), false = existing contact
if (from == NULL) {
if (!shouldAutoAddContactType(parser.getType())) {
ContactInfo ci;
@ -151,10 +151,11 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
MESH_DEBUG_PRINTLN("onAdvertRecv: unable to allocate contact slot for new contact");
return;
}
populateContactFromAdvert(*from, id, parser, timestamp);
from->sync_since = 0;
from->shared_secret_valid = false;
is_new = true; // slot was just allocated for a brand-new contact
}
// update
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));

20
src/helpers/CommonCLI.cpp

@ -205,15 +205,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
_callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first
strcpy(reply, "OK - Advert sent");
} else if (memcmp(command, "clock sync", 10) == 0) {
uint32_t curr = getRTCClock()->getCurrentTime();
if (sender_timestamp > curr) {
//uint32_t curr = getRTCClock()->getCurrentTime();
//if (sender_timestamp > curr) {
getRTCClock()->setCurrentTime(sender_timestamp + 1);
uint32_t now = getRTCClock()->getCurrentTime();
DateTime dt = DateTime(now);
sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
} else {
strcpy(reply, "ERR: clock cannot go backwards");
}
//} else {
// strcpy(reply, "ERR: clock cannot go backwards");
//}
} else if (memcmp(command, "start ota", 9) == 0) {
if (!_board->startOTAUpdate(_prefs->node_name, reply)) {
strcpy(reply, "Error");
@ -224,15 +224,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
} else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds)
uint32_t secs = _atoi(&command[5]);
uint32_t curr = getRTCClock()->getCurrentTime();
if (secs > curr) {
//int32_t curr = getRTCClock()->getCurrentTime();
//if (secs > curr) {
getRTCClock()->setCurrentTime(secs);
uint32_t now = getRTCClock()->getCurrentTime();
DateTime dt = DateTime(now);
sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
} else {
strcpy(reply, "(ERR: clock cannot go backwards)");
}
//} else {
// strcpy(reply, "(ERR: clock cannot go backwards)");
//}
} else if (memcmp(command, "neighbors", 9) == 0) {
_callbacks->formatNeighborsReply(reply);
} else if (memcmp(command, "neighbor.remove ", 16) == 0) {

Loading…
Cancel
Save