polish(ui): text/icon consistency pass across Tools, Settings, and menus

- RepeaterScreen: drop the redundant "Rpt " prefix on the profile fields
  (Preset/Freq/SF/BW/CR), matching Settings > Radio's own terminology --
  the screen is already dedicated to the repeater's own profile, so the
  prefix disambiguated nothing.
- Channel context menu: "Fav: yes/no" -> "Fav: ON/OFF", matching every
  other toggle in the app.
- Settings (System tab): six labels left as raw concatenated identifiers
  (AutoOff/AutoLock/TimeZone/LowBat/BattDisp/BzrVol) now read like their
  space-separated neighbours (Auto pwr/Pwr save/DM sound/GPS pwr), and
  BzrVol no longer clashes with "Buzzer" one row above it for the same
  feature. Three value-label arrays also had one mismatched-case entry
  fixed to match its siblings: Auto-off's "never" -> "OFF", Batt display's
  "icon" -> "Icon", Sound's "built-in" -> "Built-in".
- ToolsScreen: re-enabled the mini-icons next to each tool, which had
  been commented out ("don't fit visually"). Root cause: the screen's own
  drawIcon() centred against lineStep() (line height + inter-row gap)
  instead of getLineHeight() alone, 2px too generous for a near-full-
  height icon -- now delegates to the already-correct miniIconDraw() used
  elsewhere (e.g. the message-list ack checkmark). Also gave Admin and
  GPIO their own icons (padlock, 3-pin header) instead of both sharing
  System's cog.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-08-30 21:37:50 +02:00
co-authored by Claude Sonnet 5
parent b6dce2b7f0
commit 355816eafd
7 changed files with 55 additions and 35 deletions
@@ -137,7 +137,7 @@ In the **Rooms** list the context menu instead offers:
| Mark all read | Clears all unread for this channel |
| Notif: default / OFF / ON | Per-channel notification override — **LEFT/RIGHT** to cycle |
| Melody: global / M1 / M2 | Per-channel melody override — **LEFT/RIGHT** to cycle |
| Fav: yes / no | Add or remove this channel from favourites — **LEFT/RIGHT** to toggle |
| Fav: ON / OFF | Add or remove this channel from favourites — **LEFT/RIGHT** to toggle |
| Edit | Opens the Add/Edit form below, pre-filled with the channel's name |
| Delete | Removes the channel immediately (no confirm prompt) |
@@ -532,9 +532,9 @@ Navigate with **UP/DOWN**; change a value with **LEFT/RIGHT** (or **Enter** for
| -------------- | --------------- | -------------------------------------------------------------------------------------------------------------- |
| Repeater | ON / OFF | Master switch. The options below appear only while it is ON. |
| Network | Current / Custom | **Custom** _(default)_: enabling the repeater switches to a dedicated profile (below), disabling restores the companion's settings. A never-configured device seeds Custom from your own network's band (433/868/915 MHz region), not a flat default, so it can't land outside what's legal for your region. **Current**: relay on the companion's own frequency — opt-in, not the community norm. |
| Rpt preset | named presets | _(Custom only)_ **Enter** picks a community/saved preset for the repeater profile. |
| Rpt freq | chip range | _(Custom only)_ **Enter** opens the digit-by-digit editor (chip-validated bounds). |
| Rpt SF / BW / CR | 512 / 7.8500 kHz / 58 | _(Custom only)_ **LEFT/RIGHT** to adjust the profile's spreading factor, bandwidth, coding rate. |
| Preset | named presets | _(Custom only)_ **Enter** picks a community/saved preset for the repeater profile. |
| Freq | chip range | _(Custom only)_ **Enter** opens the digit-by-digit editor (chip-validated bounds). |
| SF / BW / CR | 512 / 7.8500 kHz / 58 | _(Custom only)_ **LEFT/RIGHT** to adjust the profile's spreading factor, bandwidth, coding rate. |
| Skip advert | ON / OFF | Don't re-flood **advert** packets (the highest-volume flood traffic); messages and acks still relay. |
| Max hops | OFF / 18 | Drop a flood packet once it has already travelled this many hops. |
| Yield | OFF / x2x9 | Scales the retransmit delay for **forwarded** floods only (your own sends are unaffected), so a mobile companion defers to better-sited fixed repeaters. Widens the window for **Suppress dup**. |
@@ -1622,8 +1622,8 @@ public:
bool left = keyIsPrev(c);
bool right = keyIsNext(c);
if (left || right) {
static const char* NOTIF_LABELS[] = { "default", "OFF", "ON" };
static const char* ML[] = { "global", "M1", "M2" };
static const char* NOTIF_LABELS[] = { "Default", "OFF", "ON" };
static const char* ML[] = { "Global", "M1", "M2" };
ContactInfo ci;
if (the_mesh.getContactByIdx(_sorted[_contact_sel], ci)) {
int sel = _ctx_menu.selectedIndex();
@@ -1777,12 +1777,12 @@ public:
return true;
}
if (c == KEY_CONTEXT_MENU && _num_contacts > 0 && !_room_mode) {
static const char* NOTIF_LABELS[] = { "default", "OFF", "ON" };
static const char* NOTIF_LABELS[] = { "Default", "OFF", "ON" };
ContactInfo ci;
the_mesh.getContactByIdx(_sorted[_contact_sel], ci);
snprintf(_ctx_notif_item, sizeof(_ctx_notif_item), "Notif: %s",
NOTIF_LABELS[dmNotifState(ci.id.pub_key)]);
{ static const char* ML[] = { "global", "M1", "M2" };
{ static const char* ML[] = { "Global", "M1", "M2" };
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s",
ML[dmMelodySlot(ci.id.pub_key)]); }
int pinned_slot = _task->findFavouriteSlot(ci.id.pub_key);
@@ -1805,8 +1805,8 @@ public:
bool left = keyIsPrev(c);
bool right = keyIsNext(c);
if (left || right) {
static const char* NOTIF_LABELS[] = { "default", "OFF", "ON" };
static const char* ML[] = { "global", "M1", "M2" };
static const char* NOTIF_LABELS[] = { "Default", "OFF", "ON" };
static const char* ML[] = { "Global", "M1", "M2" };
uint8_t ch_idx = _ctx_ch_idx; // frozen at menu open — see declaration
int sel = _ctx_menu.selectedIndex();
if (sel == 1) {
@@ -1826,7 +1826,7 @@ public:
if (p2) {
p2->ch_fav_bitmask ^= (1ULL << ch_idx);
bool is_fav = (p2->ch_fav_bitmask & (1ULL << ch_idx));
snprintf(_ctx_ch_fav_item, sizeof(_ctx_ch_fav_item), is_fav ? "Fav: yes" : "Fav: no");
snprintf(_ctx_ch_fav_item, sizeof(_ctx_ch_fav_item), is_fav ? "Fav: ON" : "Fav: OFF");
_ctx_dirty = true;
// List rebuild is deferred to menu close: with the fav-only
// filter on, un-favouriting this channel removes it from the
@@ -1900,15 +1900,15 @@ public:
if (c == KEY_CONTEXT_MENU && _num_channels > 0 && _channel_sel < _num_channels) {
uint8_t ch_idx = _channel_indices[_channel_sel];
_ctx_ch_idx = ch_idx; // freeze the menu's target channel
static const char* NOTIF_LABELS[] = { "default", "OFF", "ON" };
static const char* NOTIF_LABELS[] = { "Default", "OFF", "ON" };
snprintf(_ctx_notif_item, sizeof(_ctx_notif_item), "Notif: %s",
NOTIF_LABELS[chNotifState(ch_idx)]);
{ static const char* ML[] = { "global", "M1", "M2" };
{ static const char* ML[] = { "Global", "M1", "M2" };
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s",
ML[chNotifMelody(ch_idx)]); }
{ NodePrefs* p2 = _task->getNodePrefs();
bool is_fav = p2 && (p2->ch_fav_bitmask & (1ULL << ch_idx));
snprintf(_ctx_ch_fav_item, sizeof(_ctx_ch_fav_item), is_fav ? "Fav: yes" : "Fav: no"); }
snprintf(_ctx_ch_fav_item, sizeof(_ctx_ch_fav_item), is_fav ? "Fav: ON" : "Fav: OFF"); }
_ctx_menu.begin("Channel options", 6);
_ctx_menu.addItem("Mark all read");
_ctx_menu.addItem(_ctx_notif_item);
@@ -74,15 +74,19 @@ class RepeaterScreen : public UIScreen {
if (_sel < 0) _sel = 0;
}
// Freq/SF/BW/CR match Settings > Radio's own terminology exactly -- no "Rpt "
// prefix needed since this whole screen is already the repeater's own
// profile (Settings' radio screen is a separate screen for the companion's
// own params), so the prefix was just noise, not disambiguation.
static const char* itemLabel(int item) {
switch (item) {
case IT_REPEATER: return "Repeater";
case IT_NETWORK: return "Network";
case IT_RPRESET: return "Rpt preset";
case IT_RFREQ: return "Rpt freq";
case IT_RSF: return "Rpt SF";
case IT_RBW: return "Rpt BW";
case IT_RCR: return "Rpt CR";
case IT_RPRESET: return "Preset";
case IT_RFREQ: return "Freq";
case IT_RSF: return "SF";
case IT_RBW: return "BW";
case IT_RCR: return "CR";
case IT_SKIP: return "Skip advert";
case IT_HOPS: return "Max hops";
case IT_YIELD: return "Yield";
@@ -467,7 +467,7 @@ class SettingsScreen : public UIScreen {
display.print("N/A");
#endif
} else if (item == BUZZER_VOLUME) {
display.print("BzrVol");
display.print("Buzzer vol");
#ifdef PIN_BUZZER
renderBar(display, valCol(display), y, _task->getBuzzerVolume() + 1, 5);
#else
@@ -568,16 +568,16 @@ class SettingsScreen : public UIScreen {
if (sel && r > 0) mq_delay = r;
#if AUTO_OFF_MILLIS > 0
} else if (item == AUTO_OFF) {
display.print("AutoOff");
display.print("Auto off");
display.setCursor(valCol(display), y);
display.print(AUTO_OFF_LABELS[autoOffIndex()]);
#endif
} else if (item == AUTO_LOCK) {
display.print("AutoLock");
display.print("Auto lock");
display.setCursor(valCol(display), y);
display.print((p && p->auto_lock) ? "ON" : "OFF");
} else if (item == TIMEZONE) {
display.print("TimeZone");
display.print("Time zone");
char buf[8];
int8_t tz = p ? p->tz_offset_hours : 0;
if (tz >= 0) snprintf(buf, sizeof(buf),"UTC+%d", (int)tz);
@@ -585,7 +585,7 @@ class SettingsScreen : public UIScreen {
display.setCursor(valCol(display), y);
display.print(buf);
} else if (item == LOW_BAT) {
display.print("LowBat");
display.print("Low batt");
display.setCursor(valCol(display), y);
display.print(LOW_BAT_LABELS[lowBatIndex()]);
#if ENV_INCLUDE_GPS == 1
@@ -624,7 +624,7 @@ class SettingsScreen : public UIScreen {
display.print((p && p->keyboard_cardkb_compact) ? "Compact" : "Full");
#endif
} else if (item == BATT_DISPLAY) {
display.print("BattDisp");
display.print("Batt disp");
display.setCursor(valCol(display), y);
uint8_t mode = p ? p->batt_display_mode : 0;
display.print(BATT_DISPLAY_LABELS[mode < BATT_DISPLAY_COUNT ? mode : 0]);
@@ -1118,7 +1118,7 @@ public:
#if AUTO_OFF_MILLIS > 0
const uint16_t SettingsScreen::AUTO_OFF_OPTS[5] = { 5, 15, 30, 60, 0 };
const char* SettingsScreen::AUTO_OFF_LABELS[5] = { "5s", "15s", "30s", "60s", "never" };
const char* SettingsScreen::AUTO_OFF_LABELS[5] = { "5s", "15s", "30s", "60s", "OFF" };
#endif
const uint16_t SettingsScreen::LOW_BAT_OPTS[7] = { 0, 3000, 3100, 3200, 3300, 3400, 3500 };
const char* SettingsScreen::LOW_BAT_LABELS[7] = { "OFF", "3.0V", "3.1V", "3.2V", "3.3V", "3.4V", "3.5V" };
@@ -1126,8 +1126,8 @@ const char* SettingsScreen::LOW_BAT_LABELS[7] = { "OFF", "3.0V", "3.1V", "3.
const uint32_t SettingsScreen::GPS_DUTY_OPTS[6] = { 0, 60, 300, 900, 1800, 3600 };
const char* SettingsScreen::GPS_DUTY_LABELS[6] = { "OFF", "1 min", "5 min", "15 min", "30 min", "1 h" };
#endif
const char* SettingsScreen::BATT_DISPLAY_LABELS[3] = { "icon", "%", "V" };
const char* SettingsScreen::SOUND_LABELS[4] = { "built-in", "M1", "M2", "None" };
const char* SettingsScreen::BATT_DISPLAY_LABELS[3] = { "Icon", "%", "V" };
const char* SettingsScreen::SOUND_LABELS[4] = { "Built-in", "M1", "M2", "None" };
const char* SettingsScreen::AD_SCOPE_LABELS[2] = { "All", "Zero-hop" };
#if FEAT_FULL_REFRESH_SETTING
const char* SettingsScreen::EINK_FULL_REFRESH_LABELS[5] = { "OFF", "5", "10", "20", "30" };
+12 -7
View File
@@ -35,11 +35,16 @@ class ToolsScreen : public UIScreen {
// for the widest icon (the 7px cog) at the current font scale.
static int gutter(DisplayDriver& d) { return 7 * miniIconScale(d) + 2; }
// Was hand-rolling its own vertical centring against lineStep() (line height
// + the 2px inter-row gap) instead of just the line height itself -- 2px too
// much slack pushed a nearly-full-height icon (e.g. the 7px cog) low enough
// to clip against the row below, which is what "don't fit visually" meant.
// miniIconDraw() (icons.h) already centres correctly against getLineHeight()
// alone -- the same call drawAckGlyph uses for the message-list checkmark --
// so just delegate to it instead of keeping a second, subtly-wrong copy.
static void drawIcon(DisplayDriver& d, int x, int y, const MiniIcon* ic) {
if (!ic) return;
const int s = miniIconScale(d);
const int top = (y - 1) + ((d.lineStep() - 1) - ic->h * s) / 2;
miniIconDrawTop(d, x, top, *ic);
miniIconDraw(d, x, y, *ic);
}
void dispatch(Action a) {
@@ -87,7 +92,7 @@ public:
display.setCursor(2, y);
display.print(collapsed ? "+" : "-");
const int icon_x = 2 + cw + 2;
// drawIcon(display, icon_x, y, SECTIONS[sec].icon); // icons disabled for now, don't fit visually
drawIcon(display, icon_x, y, SECTIONS[sec].icon);
display.setCursor(icon_x + g, y);
display.print(SECTIONS[sec].name);
},
@@ -95,7 +100,7 @@ public:
[&](int sec, int item, int y, bool sel, int reserve) {
drawRowSelection(display, y, sel, reserve);
const int icon_x = 2 + cw + 2; // align item icons under the header icon
// drawIcon(display, icon_x, y, SECTIONS[sec].tools[item].icon); // icons disabled for now, don't fit visually
drawIcon(display, icon_x, y, SECTIONS[sec].tools[item].icon);
display.setCursor(icon_x + g, y);
display.print(SECTIONS[sec].tools[item].label);
});
@@ -128,14 +133,14 @@ const ToolsScreen::Tool ToolsScreen::COMMS_TOOLS[] = {
{ "Remote Bot", &ICON_BOT, ACT_BOT },
{ "Auto-Advert", &ICON_ADVERT, ACT_AUTOADVERT },
{ "Repeater", &ICON_REPEATER, ACT_REPEATER },
{ "Admin", &ICON_GEAR, ACT_ADMIN },
{ "Admin", &ICON_KEY, ACT_ADMIN },
};
const ToolsScreen::Tool ToolsScreen::SYSTEM_TOOLS[] = {
{ "Clock Tools", &ICON_ALARM, ACT_CLOCK },
{ "Ringtone Editor", &ICON_NOTE, ACT_RINGTONE },
{ "Diagnostics", &ICON_CHART, ACT_DIAGNOSTICS },
#if defined(PIN_GPIO1)
{ "GPIO", &ICON_GEAR, ACT_GPIO },
{ "GPIO", &ICON_PINS, ACT_GPIO },
#endif
};
const ToolsScreen::Section ToolsScreen::SECTIONS[] = {
+11
View File
@@ -301,6 +301,17 @@ MINI_ICON(ICON_GEAR, 7, // ⚙ cog with hub hole — system
packRow("#######"),
packRow(".#####."),
packRow("..#.#.."));
MINI_ICON(ICON_KEY, 5, // padlock — remote admin (privileged/password-gated access)
packRow(".###."),
packRow("#...#"),
packRow("#...#"),
packRow("#####"),
packRow("..#.."),
packRow("#####"));
MINI_ICON(ICON_PINS, 5, // 3-pin header — GPIO
packRow("#.#.#"),
packRow("#.#.#"),
packRow("#####"));
// Home-carousel page glyphs — a uniform 5x5 set, deliberately smaller than the
// menu/status icons above, used in place of the page-indicator dots. One per