@@ -2462,26 +2462,35 @@ static void composerAutoGrowCb(lv_event_t* e) {
24622462// .cpp) so a hold doesn't auto-repeat — it cleanly long-presses instead.
24632463static const char* const kAccA[] = {"à","á","â","ä","ã","å"};
24642464static const char* const kAccA_u[] = {"À","Á","Â","Ä","Ã","Å"};
2465- static const char* const kAccE[] = {"è","é","ê","ë"};
2466- static const char* const kAccE_u[] = {"È","É","Ê","Ë"};
2465+ static const char* const kAccE[] = {"è","é","ê","ë","ě" };
2466+ static const char* const kAccE_u[] = {"È","É","Ê","Ë","Ě" };
24672467static const char* const kAccI[] = {"ì","í","î","ï"};
24682468static const char* const kAccI_u[] = {"Ì","Í","Î","Ï"};
24692469static const char* const kAccO[] = {"ò","ó","ô","ö","õ","ø"};
24702470static const char* const kAccO_u[] = {"Ò","Ó","Ô","Ö","Õ","Ø"};
2471- static const char* const kAccU[] = {"ù","ú","û","ü"};
2472- static const char* const kAccU_u[] = {"Ù","Ú","Û","Ü"};
2471+ static const char* const kAccU[] = {"ù","ú","û","ü","ů" };
2472+ static const char* const kAccU_u[] = {"Ù","Ú","Û","Ü","Ů" };
24732473static const char* const kAccN[] = {"ñ"};
24742474static const char* const kAccN_u[] = {"Ñ"};
2475- static const char* const kAccC[] = {"ç"};
2476- static const char* const kAccC_u[] = {"Ç"};
2475+ static const char* const kAccC[] = {"ç","č" };
2476+ static const char* const kAccC_u[] = {"Ç","Č" };
24772477static const char* const kAccS[] = {"ß","ś","š"};
24782478static const char* const kAccY[] = {"ý","ÿ"};
2479+ // Czech carons / ring — base letters that otherwise carry no Latin-1 accent.
2480+ static const char* const kAccT[] = {"ť"};
2481+ static const char* const kAccT_u[] = {"Ť"};
2482+ static const char* const kAccZ[] = {"ž"};
2483+ static const char* const kAccZ_u[] = {"Ž"};
2484+ static const char* const kAccR[] = {"ř"};
2485+ static const char* const kAccR_u[] = {"Ř"};
24792486struct AccentSet { char key; const char* const* v; uint8_t n; };
24802487static const AccentSet kAccentSets[] = {
2481- {'a',kAccA,6},{'A',kAccA_u,6},{'e',kAccE,4 },{'E',kAccE_u,4 },
2488+ {'a',kAccA,6},{'A',kAccA_u,6},{'e',kAccE,5 },{'E',kAccE_u,5 },
24822489 {'i',kAccI,4},{'I',kAccI_u,4},{'o',kAccO,6},{'O',kAccO_u,6},
2483- {'u',kAccU,4},{'U',kAccU_u,4},{'n',kAccN,1},{'N',kAccN_u,1},
2484- {'c',kAccC,1},{'C',kAccC_u,1},{'s',kAccS,3},{'y',kAccY,2},
2490+ {'u',kAccU,5},{'U',kAccU_u,5},{'n',kAccN,1},{'N',kAccN_u,1},
2491+ {'c',kAccC,2},{'C',kAccC_u,2},{'s',kAccS,3},{'y',kAccY,2},
2492+ {'t',kAccT,1},{'T',kAccT_u,1},{'z',kAccZ,1},{'Z',kAccZ_u,1},
2493+ {'r',kAccR,1},{'R',kAccR_u,1},
24852494};
24862495static const AccentSet* accentLookup(const char* key) {
24872496 if (!key || !key[0] || key[1]) return nullptr; // single ASCII-char keys only
@@ -3202,7 +3211,7 @@ static const char* const k_emoji_items[] = {
32023211 "\xF0\x9F\x92\xAF","\xE2\x9C\x85","\xE2\x9D\x8C","\xE2\x9D\x97",
32033212 "\xE2\x9D\x93","\xE2\x9A\xA0","\xF0\x9F\x92\xA9","\xE2\xAD\x90",
32043213 "\xF0\x9F\x8C\x9F","\xE2\x9A\xA1","\xE2\x98\x80","\xE2\x98\x81",
3205- "\xE2\x9D\x84","\xE2\x98\x94","\xE2\x98\x95","\xF0\x9F\x8D\xBB",
3214+ "\xE2\x9D\x84","\xE2\x98\x94","\xE2\x98\x95","\xF0\x9F\x8D\xBA","\xF0\x9F\x8D\ xBB",
32063215 "\xF0\x9F\x8D\x95","\xF0\x9F\x8E\x82","\xF0\x9F\x8E\x81","\xF0\x9F\x92\xAC",
32073216 "\xF0\x9F\x92\xA4","\xF0\x9F\x92\xA5",
32083217 // objects
@@ -5271,6 +5280,14 @@ static void buildRadioSettings() {
52715280 lv_obj_center(l);
52725281}
52735282
5283+ // New-contact toast toggle (Auto-add page). Persists immediately like the
5284+ // auto-add switches; default ON. Diag-log line + tab badge are unaffected — this
5285+ // only gates the low-key chip in UITask::notify().
5286+ static void toggleNewContactToastCb(lv_event_t* e) {
5287+ if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
5288+ touchPrefsSetNewContactToast(lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED));
5289+ }
5290+
52745291static void buildAutoAddSettings() {
52755292 lv_obj_t* body = createSettingsModal("Auto-add contacts", SettingsModalKind::AutoAdd);
52765293 NodePrefs* prefs = the_mesh.getNodePrefs();
@@ -5290,6 +5307,17 @@ static void buildAutoAddSettings() {
52905307 mk_switch("Overwrite oldest", &g_set_modal.auto_overwrite_sw);
52915308 g_set_modal.manual_add_sw = nullptr; // master "manual add" removed — per-type switches are authoritative now
52925309
5310+ // "Notify on new contact" — a UI pref (not a NodePrefs autoadd bit), so it gets
5311+ // its own callback + persistence rather than mk_switch's autoAddSwitchCb.
5312+ {
5313+ int h = settingsRowLabel(body, y, 6, "Notify on new contact", COLOR_SUB, nullptr, 56);
5314+ lv_obj_t* sw = lv_switch_create(body);
5315+ lv_obj_align(sw, LV_ALIGN_TOP_RIGHT, 0, y);
5316+ if (touchPrefsGetNewContactToast()) lv_obj_add_state(sw, LV_STATE_CHECKED);
5317+ lv_obj_add_event_cb(sw, toggleNewContactToastCb, LV_EVENT_VALUE_CHANGED, nullptr);
5318+ y += LV_MAX(34, h + 12);
5319+ }
5320+
52935321 lv_obj_t* hops_l = lv_label_create(body);
52945322 lv_label_set_text(hops_l, TR("Max hops (0..64)"));
52955323 lv_obj_set_style_text_color(hops_l, lv_color_hex(COLOR_SUB), LV_PART_MAIN);
@@ -13408,6 +13436,14 @@ static void homeUnreadClickedCb(lv_event_t* e) {
1340813436 goToTab(CHAT_INBOX_TAB_INDEX);
1340913437}
1341013438
13439+ // Status-bar unread badge (✉ N) → Chats inbox. Wired on the bar's left_label;
13440+ // only fires while updateGlobalStatusBar has flagged the badge state CLICKABLE,
13441+ // so non-badge left-zone states keep the bar's control-center / Back tap.
13442+ static void statusBarUnreadCb(lv_event_t* e) {
13443+ if (lv_event_get_code(e) != LV_EVENT_CLICKED) return;
13444+ goToTab(CHAT_INBOX_TAB_INDEX);
13445+ }
13446+
1341113447// ---- App drawer (full-screen grid launcher) ----
1341213448// A toggle-in alternative to the command-centre home. The full implementation
1341313449// lives below openControlCenter (it links to the tool openers defined there);
@@ -22920,6 +22956,11 @@ static void buildGlobalStatusBar() {
2292022956 lv_obj_set_style_text_color(g_statusbar.left_label, lv_color_hex(COLOR_TEXT), LV_PART_MAIN);
2292122957 lv_obj_set_style_text_font(g_statusbar.left_label, &g_font_14, LV_PART_MAIN);
2292222958 lv_obj_align(g_statusbar.left_label, LV_ALIGN_LEFT_MID, 6, 0);
22959+ // Tapping the unread badge (✉ N) jumps to the Chats inbox. The CLICKABLE flag is
22960+ // toggled per-tick in updateGlobalStatusBar so only the badge state intercepts the
22961+ // tap; every other left-zone state falls through to the bar's control-center tap.
22962+ lv_obj_set_ext_click_area(g_statusbar.left_label, 8);
22963+ lv_obj_add_event_cb(g_statusbar.left_label, statusBarUnreadCb, LV_EVENT_CLICKED, nullptr);
2292322964
2292422965 // Channel-settings gear — left of the thread name, shown only inside a channel
2292522966 // chat (updateGlobalStatusBar toggles it + shifts the name). Opens the
@@ -23088,6 +23129,7 @@ static void updateGlobalStatusBar() {
2308823129 }
2308923130
2309023131 // ---- Left zone ----
23132+ bool unread_badge = false; // set true only by the branches that render the ✉ count
2309123133 if (s_settings_open_cat >= 0) {
2309223134 // A settings detail sheet is open: the bar carries its Back chevron + the page
2309323135 // title (the sheet has no header of its own; tapping the bar goes Back).
@@ -23108,6 +23150,7 @@ static void updateGlobalStatusBar() {
2310823150 if (total_unread > 99) snprintf(ub, sizeof ub, "99+"); else snprintf(ub, sizeof ub, "%d", total_unread);
2310923151 snprintf(buf, sizeof(buf), LV_SYMBOL_ENVELOPE " %s %s", ub, s_chat_title);
2311023152 lv_label_set_text(g_statusbar.left_label, buf);
23153+ unread_badge = true;
2311123154 } else {
2311223155 lv_label_set_text(g_statusbar.left_label, s_chat_title);
2311323156 }
@@ -23164,6 +23207,7 @@ static void updateGlobalStatusBar() {
2316423207 if (total_unread > 99) snprintf(buf, sizeof(buf), LV_SYMBOL_ENVELOPE " 99+");
2316523208 else snprintf(buf, sizeof(buf), LV_SYMBOL_ENVELOPE " %d", total_unread);
2316623209 lv_label_set_text(g_statusbar.left_label, buf);
23210+ unread_badge = true;
2316723211 } else {
2316823212 // No unread → blank the left zone entirely. Operator complaint was
2316923213 // the envelope was always lit even with an empty inbox, which read
@@ -23173,6 +23217,14 @@ static void updateGlobalStatusBar() {
2317323217 }
2317423218 }
2317523219
23220+ // Activate the unread→Chats shortcut only while the ✉ count is shown; other
23221+ // left-zone states (Back chevron, profile name, OSM credit, blank) keep the
23222+ // bar's normal control-center / Back tap.
23223+ if (g_statusbar.left_label) {
23224+ if (unread_badge) lv_obj_add_flag(g_statusbar.left_label, LV_OBJ_FLAG_CLICKABLE);
23225+ else lv_obj_clear_flag(g_statusbar.left_label, LV_OBJ_FLAG_CLICKABLE);
23226+ }
23227+
2317623228 // ---- Connection icon ----
2317723229#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION)
2317823230 const bool wifi_up = (WiFi.status() == WL_CONNECTED);
@@ -27935,12 +27987,17 @@ void UITask::notify(UIEventType t) {
2793527987 }
2793627988 // Background traffic (also reflected in the tab badges): show a subtle, low-key
2793727989 // chip rather than the prominent centre alert toast, so it's less intrusive.
27938- strncpy(_alert, msg, sizeof(_alert) - 1);
27939- _alert[sizeof(_alert) - 1] = '\0';
27940- _alert_expiry = millis() + 1100UL;
27990+ // New-contact discovery can be spammy in a busy mesh, so its chip is opt-out in
27991+ // Settings -> Auto-add; when suppressed we still log the diag line + tab badge.
27992+ const bool toast = (t != UIEventType::newContactMessage) || touchPrefsGetNewContactToast();
27993+ if (toast) {
27994+ strncpy(_alert, msg, sizeof(_alert) - 1);
27995+ _alert[sizeof(_alert) - 1] = '\0';
27996+ _alert_expiry = millis() + 1100UL;
27997+ }
2794127998#if defined(HAS_TOUCH_UI)
2794227999 pushDiagLine(msg);
27943- showSubtleNotifyLvgl(msg, 1100);
28000+ if (toast) showSubtleNotifyLvgl(msg, 1100);
2794428001#endif
2794528002}
2794628003
0 commit comments