fix: use @[nick] format for replies and fix fullscreen message display

- Reply prefix changed to "@[nick] " format — bracket delimiter handles
  nicks with spaces unambiguously
- FullscreenMsgView: parse "@[nick] body" to show "To: nick" header;
  fall back gracefully if format not matched
- Reduce FS_CHARS 21→20 to prevent scroll arrows overlapping last char
- Transliterate nick before displaying in "To:" header and "RE:" title
  so non-ASCII characters render correctly on the OLED font

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-05-18 18:18:45 +02:00
co-authored by Claude Sonnet 4.6
parent b21bcb5ca4
commit 34cfd27b8a
2 changed files with 22 additions and 14 deletions
@@ -3,7 +3,7 @@
#include <helpers/ui/DisplayDriver.h> #include <helpers/ui/DisplayDriver.h>
#include <Arduino.h> #include <Arduino.h>
static const int FS_CHARS = 21; static const int FS_CHARS = 20;
static const int FS_LINE_H = 8; static const int FS_LINE_H = 8;
static const int FS_START_Y = 12; static const int FS_START_Y = 12;
static const int FS_VISIBLE = (64 - FS_START_Y) / FS_LINE_H; static const int FS_VISIBLE = (64 - FS_START_Y) / FS_LINE_H;
@@ -46,14 +46,14 @@ struct FullscreenMsgView {
// detect @recipient at start of message // detect @recipient at start of message
char to_nick[32] = ""; char to_nick[32] = "";
const char* body = text; const char* body = text;
if (text[0] == '@') { if (text[0] == '@' && text[1] == '[') {
const char* sp = strchr(text, ' '); const char* close = strchr(text + 2, ']');
if (sp && sp[1]) { if (close && close[1] == ' ' && close[2]) {
int len = (int)(sp - text) - 1; int len = (int)(close - text) - 2;
if (len >= (int)sizeof(to_nick)) len = sizeof(to_nick) - 1; if (len >= (int)sizeof(to_nick)) len = sizeof(to_nick) - 1;
memcpy(to_nick, text + 1, len); memcpy(to_nick, text + 2, len);
to_nick[len] = '\0'; to_nick[len] = '\0';
body = sp + 1; body = close + 2;
} }
} }
@@ -66,8 +66,9 @@ struct FullscreenMsgView {
display.setColor(DisplayDriver::DARK); display.setColor(DisplayDriver::DARK);
display.drawTextEllipsized(2, 1, display.width() - 4, sender); display.drawTextEllipsized(2, 1, display.width() - 4, sender);
if (to_nick[0]) { if (to_nick[0]) {
char to_label[36]; char trans_nick[32], to_label[36];
snprintf(to_label, sizeof(to_label), "To: %s", to_nick); display.translateUTF8ToBlocks(trans_nick, to_nick, sizeof(trans_nick));
snprintf(to_label, sizeof(to_label), "To: %s", trans_nick);
display.drawTextEllipsized(2, 11, display.width() - 4, to_label); display.drawTextEllipsized(2, 11, display.width() - 4, to_label);
} }
display.setColor(DisplayDriver::LIGHT); display.setColor(DisplayDriver::LIGHT);
@@ -46,7 +46,7 @@ class QuickMsgScreen : public UIScreen {
bool _ctx_dirty; bool _ctx_dirty;
char _ctx_notif_item[22]; char _ctx_notif_item[22];
char _ctx_melody_item[20]; char _ctx_melody_item[20];
char _reply_prefix[36]; // "@nick " built when reply is triggered char _reply_prefix[36]; // "@[nick] " built when reply is triggered
bool _reply_mode; // true while composing a reply (prefix is prepended) bool _reply_mode; // true while composing a reply (prefix is prepended)
struct ChHistEntry { uint8_t ch_idx; char text[140]; }; struct ChHistEntry { uint8_t ch_idx; char text[140]; };
@@ -96,7 +96,8 @@ class QuickMsgScreen : public UIScreen {
int slen = (int)(sep - text); int slen = (int)(sep - text);
if (slen == 2 && strncmp(text, "Me", 2) == 0) return false; if (slen == 2 && strncmp(text, "Me", 2) == 0) return false;
if (slen > 32) slen = 32; if (slen > 32) slen = 32;
snprintf(_reply_prefix, sizeof(_reply_prefix), "@%.*s ", slen, text); if (slen > 31) slen = 31;
snprintf(_reply_prefix, sizeof(_reply_prefix), "@[%.*s] ", slen, text);
return true; return true;
} }
@@ -780,7 +781,13 @@ public:
} else { // MSG_PICK } else { // MSG_PICK
char title[24]; char title[24];
if (_reply_mode) { if (_reply_mode) {
snprintf(title, sizeof(title), "RE:%.20s", _reply_prefix + 1); // skip '@' int rlen = (int)strlen(_reply_prefix) - 4; // exclude "@[" and "] "
if (rlen < 0) rlen = 0;
if (rlen > 20) rlen = 20;
char nick_raw[32], nick_trans[32];
snprintf(nick_raw, sizeof(nick_raw), "%.*s", rlen, _reply_prefix + 2);
display.translateUTF8ToBlocks(nick_trans, nick_raw, sizeof(nick_trans));
snprintf(title, sizeof(title), "RE:%s", nick_trans);
} else if (_sending_to_channel) { } else if (_sending_to_channel) {
ChannelDetails ch; ChannelDetails ch;
the_mesh.getChannel(_sel_channel_idx, ch); the_mesh.getChannel(_sel_channel_idx, ch);
@@ -988,7 +995,7 @@ public:
} else if (res == FullscreenMsgView::REPLY) { } else if (res == FullscreenMsgView::REPLY) {
int ring_pos = dmHistEntryForContact(_sel_contact.id.pub_key, _dm_hist_sel); int ring_pos = dmHistEntryForContact(_sel_contact.id.pub_key, _dm_hist_sel);
if (ring_pos >= 0 && !_dm_hist[ring_pos].outgoing) { if (ring_pos >= 0 && !_dm_hist[ring_pos].outgoing) {
snprintf(_reply_prefix, sizeof(_reply_prefix), "@%.32s ", _sel_contact.name); snprintf(_reply_prefix, sizeof(_reply_prefix), "@[%.31s] ", _sel_contact.name);
_ctx_menu.begin("Options", 1); _ctx_menu.begin("Options", 1);
_ctx_menu.addItem("Reply"); _ctx_menu.addItem("Reply");
} }
@@ -1036,7 +1043,7 @@ public:
if (c == KEY_CONTEXT_MENU && _dm_hist_sel >= 0) { if (c == KEY_CONTEXT_MENU && _dm_hist_sel >= 0) {
int ring_pos = dmHistEntryForContact(_sel_contact.id.pub_key, _dm_hist_sel); int ring_pos = dmHistEntryForContact(_sel_contact.id.pub_key, _dm_hist_sel);
if (ring_pos >= 0 && !_dm_hist[ring_pos].outgoing) { if (ring_pos >= 0 && !_dm_hist[ring_pos].outgoing) {
snprintf(_reply_prefix, sizeof(_reply_prefix), "@%.32s ", _sel_contact.name); snprintf(_reply_prefix, sizeof(_reply_prefix), "@[%.31s] ", _sel_contact.name);
_ctx_menu.begin("Options", 1); _ctx_menu.begin("Options", 1);
_ctx_menu.addItem("Reply"); _ctx_menu.addItem("Reply");
} }