mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-14 15:16:40 +00:00
fix(ui): confirm destructive actions, retire last Hold-Enter-cancel, de-dup labels
Continuing the consistency review: sweep for the same three defect shapes
elsewhere in ui-new/ (own read pass plus two parallel research agents),
verified against source before acting.
Three destructive actions fired on a single Enter with no way back, unlike
contact-delete's existing confirm-defaulted-to-Cancel popup: Trail's "Reset
trail" (wipes the whole recorded route, no undo short of a prior manual
Save -- reuses Trail's own multi-level menu machinery, alongside its
GPS-off confirm), Messages' channel Delete, and RadioPresetPicker's saved-
preset delete (shared by Settings > Radio and Tools > Repeater, so one fix
covers both). All three now confirm the same way, defaulting to Cancel.
KeyboardWidget was the one place Hold-Enter still doubled as Cancel: Shift,
Backspace and a Latin letter's accent popup already have real, kept
meanings under a hold, but every other special-row cell (Space, OK/Done,
the {} placeholder) fell through to a bare CANCELLED, closing the keyboard
exactly like the real Cancel key. Now a no-op there too, matching the "only
Back closes it" rule already applied to popups and screens.
MessagesScreen defined the same two label arrays (Notif states, melody
slots) four times over, once per context-menu handler. Hoisted to one
pair of static class members -- constexpr wasn't enough to get the linker
to emit them on this toolchain, so they follow the same declare-in-class/
define-out-of-class shape NearbyScreen::FILTER_LABELS already uses.
Alert text: "Advert sent!"/"Advert failed.." and "Sent!" were the only
toasts anywhere with trailing punctuation; normalized to the plain style
every other confirmation uses. Unpinning from the Favourites Dial reported
the freed slot number from the Messages screens but not from Nodes or the
dial's own tile menu; now consistent everywhere pinning already was.
DiagnosticsScreen's Live/System/Font tab renderers hand-rolled the same
scroll-clamp/loop/indicator skeleton drawList() already bundles; switched
both to drawList (passing the screen's own _scroll as its `sel` too, since
neither tab has a row cursor -- makes drawList's internal clamp a no-op and
leaves clampScroll() as the only thing bounding it, unchanged). Pure
internal tidy, no behavior change.
This commit is contained in:
@@ -148,7 +148,7 @@ In the **Rooms** list the context menu instead offers:
|
||||
| Fav: ON / OFF | Add or remove this channel from favourites — **LEFT/RIGHT** or **Enter** to toggle |
|
||||
| Pin to dial / Unpin (slot N) | Pin this channel to a [Favourites Dial](../favourites_dial/favourites_dial.md) slot |
|
||||
| Edit | Opens the Add/Edit form below, pre-filled with the channel's name |
|
||||
| Delete | Removes the channel immediately (no confirm prompt) |
|
||||
| Delete | Removes the channel — confirms first (defaults to Cancel) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ Lists all available home screen pages. For each entry:
|
||||
| Setting | Options | Notes |
|
||||
| --------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| TX Pwr | 2–22 dBm | LEFT/RIGHT. With **Auto pwr** on this is the *ceiling* — the radio may transmit lower. |
|
||||
| Preset | named presets | LEFT/RIGHT cycles community RF presets (region frequency + bandwidth/SF/CR). **Enter** opens a popup to pick one, save the current settings as a named preset, or delete a saved one. Applies frequency, bandwidth, SF and CR together. |
|
||||
| Preset | named presets | LEFT/RIGHT cycles community RF presets (region frequency + bandwidth/SF/CR). **Enter** opens a popup to pick one, save the current settings as a named preset, or delete a saved one — deleting confirms first (defaults to Cancel). Applies frequency, bandwidth, SF and CR together. |
|
||||
| Freq | chip range | **Enter** opens a digit-by-digit editor: LEFT/RIGHT moves between decimal places, UP/DOWN steps that digit. Bounds come from the radio chip's own validated range, so a value the radio would reject can't be entered. |
|
||||
| SF | 5–12 | LEFT/RIGHT. Spreading factor. |
|
||||
| BW | 7.8–500 kHz | LEFT/RIGHT cycles the standard LoRa bandwidths. |
|
||||
|
||||
@@ -123,7 +123,7 @@ Cycle views with **LEFT / RIGHT**:
|
||||
| Load trail | Restore flash trail into RAM |
|
||||
| Export (live) | Stream live RAM trail as GPX 1.1 over USB Serial |
|
||||
| Export (saved) | Stream saved flash trail as GPX 1.1 over USB Serial |
|
||||
| Reset trail | Clear RAM ring and elapsed time |
|
||||
| Reset trail | Clear RAM ring and elapsed time — confirms first (defaults to Cancel) since there's no way back short of a prior **Save trail** |
|
||||
|
||||
**Settings…** (values cycle with **LEFT/RIGHT** or **Enter**; shown only where they apply):
|
||||
|
||||
|
||||
@@ -196,39 +196,35 @@ class DiagnosticsScreen : public UIScreen {
|
||||
addLine("Sym @#&*()[]{}/\\+=");
|
||||
}
|
||||
|
||||
// Shared scrollable renderer for the label/value Live tab.
|
||||
// Shared scrollable renderer for the label/value Live tab. Neither tab has a
|
||||
// row cursor -- UP/DOWN move _scroll directly -- so this passes _scroll as
|
||||
// drawList()'s `sel` too: its internal clamp-toward-sel is then a no-op
|
||||
// (sel == scroll always), leaving clampScroll() below as the only thing
|
||||
// that actually bounds _scroll, same as before.
|
||||
void renderRows(DisplayDriver& display) {
|
||||
const int item_h = display.lineStep();
|
||||
const int start_y = display.listStart();
|
||||
const int item_h = display.lineStep();
|
||||
int visible = display.listVisible(item_h);
|
||||
if (visible < 1) visible = 1;
|
||||
clampScroll(_row_count, visible);
|
||||
|
||||
const int reserve = scrollIndicatorReserve(display, _row_count, visible);
|
||||
for (int i = 0; i < visible && (_scroll + i) < _row_count; i++) {
|
||||
const Row& r = _rows[_scroll + i];
|
||||
int y = start_y + i * item_h;
|
||||
display.setCursor(2, y);
|
||||
display.print(r.label);
|
||||
display.drawTextRightAlign(display.width() - reserve - 2, y, r.value);
|
||||
}
|
||||
drawScrollIndicator(display, start_y, visible * item_h, _row_count, visible, _scroll);
|
||||
drawList(display, _row_count, _scroll, _scroll,
|
||||
[&](int idx, int y, bool, int reserve) {
|
||||
const Row& r = _rows[idx];
|
||||
display.setCursor(2, y);
|
||||
display.print(r.label);
|
||||
display.drawTextRightAlign(display.width() - reserve - 2, y, r.value);
|
||||
});
|
||||
}
|
||||
|
||||
// Shared scrollable renderer for the full-width System / Font tabs.
|
||||
void renderLines(DisplayDriver& display) {
|
||||
const int item_h = display.lineStep();
|
||||
const int start_y = display.listStart();
|
||||
const int item_h = display.lineStep();
|
||||
int visible = display.listVisible(item_h);
|
||||
if (visible < 1) visible = 1;
|
||||
clampScroll(_line_count, visible);
|
||||
|
||||
const int reserve = scrollIndicatorReserve(display, _line_count, visible);
|
||||
for (int i = 0; i < visible && (_scroll + i) < _line_count; i++) {
|
||||
int y = start_y + i * item_h;
|
||||
display.drawTextEllipsized(2, y, display.width() - reserve - 4, _lines[_scroll + i]);
|
||||
}
|
||||
drawScrollIndicator(display, start_y, visible * item_h, _line_count, visible, _scroll);
|
||||
drawList(display, _line_count, _scroll, _scroll,
|
||||
[&](int idx, int y, bool, int reserve) {
|
||||
display.drawTextEllipsized(2, y, display.width() - reserve - 4, _lines[idx]);
|
||||
});
|
||||
}
|
||||
|
||||
void clampScroll(int total, int visible) {
|
||||
|
||||
@@ -911,17 +911,16 @@ struct KeyboardWidget {
|
||||
const int rows = gridRows();
|
||||
const int cols = gridCols();
|
||||
|
||||
// Hold-Enter is normally "cancel", but three places give it a more useful
|
||||
// meaning instead: Shift -> toggle a persistent caps-lock (a plain tap is
|
||||
// one-shot -- see the commit sites below); Backspace -> clear the whole
|
||||
// field in one action instead of holding it down; a Latin-page letter cell
|
||||
// with accented variants -> open the accent popup (see accent_active
|
||||
// above). Every other special-row cell keeps hold-to-cancel; any other
|
||||
// letter/symbol cell (a plain letter with no accents, or any T9/alt-
|
||||
// alphabet/symbols cell) is a silent no-op instead, so it can't
|
||||
// accidentally close the keyboard. Cursor mode itself moved off Hold-Enter
|
||||
// entirely -- see the KEY_UP block below, where UP from row 0 now enters
|
||||
// it instead.
|
||||
// Hold-Enter has a distinct meaning on three cells -- Shift -> toggle a
|
||||
// persistent caps-lock (a plain tap is one-shot -- see the commit sites
|
||||
// below); Backspace -> clear the whole field in one action instead of
|
||||
// holding it down; a Latin-page letter cell with accented variants ->
|
||||
// open the accent popup (see accent_active above) -- and is a silent
|
||||
// no-op everywhere else (any other special-row cell, a plain letter with
|
||||
// no accents, or any T9/alt-alphabet/symbols cell), matching the "only
|
||||
// Back closes it" rule every other screen's popups/menus follow. Cursor
|
||||
// mode itself moved off Hold-Enter entirely -- see the KEY_UP block
|
||||
// below, where UP from row 0 now enters it instead.
|
||||
if (c == KEY_CONTEXT_MENU) {
|
||||
if (row == rows && col == 0) { // Shift
|
||||
caps_lock = !caps_lock;
|
||||
@@ -941,7 +940,7 @@ struct KeyboardWidget {
|
||||
}
|
||||
return NONE; // no variants for this cell, or T9/non-Latin/symbols page
|
||||
}
|
||||
return CANCELLED;
|
||||
return NONE; // any other special-row cell (Space, OK/Done, placeholder)
|
||||
}
|
||||
|
||||
if (c == KEY_UP) {
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
class MessagesScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
|
||||
// Shared by every Notif/Melody value row -- contact, room and channel
|
||||
// context menus alike -- so the wording only needs to agree in one place.
|
||||
// Defined at file scope below (see NearbyScreen::FILTER_LABELS for the same
|
||||
// pattern).
|
||||
static const char* const NOTIF_LABELS[3];
|
||||
static const char* const MELODY_LABELS[3];
|
||||
|
||||
enum Phase { MODE_SELECT, CONTACT_PICK, DM_HIST, MSG_PICK, CHANNEL_PICK, CHANNEL_HIST, KEYBOARD };
|
||||
Phase _phase;
|
||||
|
||||
@@ -71,6 +78,7 @@ class MessagesScreen : public UIScreen {
|
||||
bool _pin_picker_active; // true while the slot-picker submenu is open
|
||||
int _pick_fav_slot = -1; // >=0 = browsing to fill that Favourites dial slot
|
||||
int _pin_slot_ch_idx = -1; // >=0 = that submenu is pinning this channel, not a contact
|
||||
bool _ch_delete_confirm_active = false; // true while the Delete/Cancel submenu is open
|
||||
bool _direct_entry; // entered a history view straight from the Favourites dial;
|
||||
// CANCEL returns home instead of to the picker
|
||||
char _reply_prefix[36]; // "@[nick] " built when reply is triggered
|
||||
@@ -534,7 +542,7 @@ class MessagesScreen : public UIScreen {
|
||||
_history.setChUnread(_sel_channel_idx, 0);
|
||||
_unread_at_entry = 0;
|
||||
_viewing_max_seen = 0;
|
||||
_task->showAlert("Sent!", 600);
|
||||
_task->showAlert("Sent", 600);
|
||||
} else if (ok) {
|
||||
NodePrefs* np = _task->getNodePrefs();
|
||||
uint8_t resends = np ? np->dm_resend_count : 0;
|
||||
@@ -543,7 +551,7 @@ class MessagesScreen : public UIScreen {
|
||||
_dm_hist_sel = 0;
|
||||
_dm_hist_scroll = 0;
|
||||
_phase = DM_HIST;
|
||||
_task->showAlert("Sent!", 600);
|
||||
_task->showAlert("Sent", 600);
|
||||
} else {
|
||||
_task->showAlert("Send failed", 1500);
|
||||
_task->gotoHomeScreen();
|
||||
@@ -732,8 +740,6 @@ class MessagesScreen : public UIScreen {
|
||||
// Advance one of the contact menu's value rows. dir is +1 for RIGHT and for
|
||||
// Enter (which PopupMenu reports as VALUE_NEXT on a value row), -1 for LEFT.
|
||||
void cycleContactCtxValue(int sel, int dir) {
|
||||
static const char* NOTIF_LABELS[] = { "Default", "OFF", "ON" };
|
||||
static const char* ML[] = { "Global", "M1", "M2" };
|
||||
ContactInfo ci;
|
||||
if (_num_contacts <= 0 || !the_mesh.getContactByIdx(_sorted[_contact_sel], ci)) return;
|
||||
if (sel == 1) {
|
||||
@@ -746,7 +752,7 @@ class MessagesScreen : public UIScreen {
|
||||
uint8_t v = dmMelodySlot(ci.id.pub_key);
|
||||
v = (dir > 0) ? (v + 1) % 3 : (v + 2) % 3;
|
||||
setDmMelody(ci.id.pub_key, v);
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s", ML[v]);
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s", MELODY_LABELS[v]);
|
||||
_ctx_dirty = true;
|
||||
} else if (sel == _ctx_fav_idx) {
|
||||
toggleContactFav(ci);
|
||||
@@ -764,8 +770,6 @@ class MessagesScreen : public UIScreen {
|
||||
// the fav-only filter on, un-favouriting removes this channel from the list,
|
||||
// and rebuilding under the open menu would shift _channel_sel onto another one.
|
||||
void cycleChannelCtxValue(int sel, int dir) {
|
||||
static const char* NOTIF_LABELS[] = { "Default", "OFF", "ON" };
|
||||
static const char* ML[] = { "Global", "M1", "M2" };
|
||||
if (_num_channels <= 0) return;
|
||||
uint8_t ch_idx = _ctx_ch_idx; // frozen at menu open -- see declaration
|
||||
if (sel == 1) {
|
||||
@@ -778,7 +782,7 @@ class MessagesScreen : public UIScreen {
|
||||
uint8_t v = chNotifMelody(ch_idx);
|
||||
v = (dir > 0) ? (v + 1) % 3 : (v + 2) % 3;
|
||||
setChNotifMelody(ch_idx, v);
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s", ML[v]);
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s", MELODY_LABELS[v]);
|
||||
_ctx_dirty = true;
|
||||
} else if (sel == _ctx_fav_idx) {
|
||||
NodePrefs* p2 = _task->getNodePrefs();
|
||||
@@ -1118,6 +1122,7 @@ public:
|
||||
_pick_bot_room = false;
|
||||
_pin_picker_active = false;
|
||||
_pin_slot_ch_idx = -1;
|
||||
_ch_delete_confirm_active = false;
|
||||
_pick_fav_slot = -1;
|
||||
_direct_entry = false;
|
||||
_unread_at_entry = 0;
|
||||
@@ -1995,14 +2000,12 @@ public:
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_CONTEXT_MENU && _num_contacts > 0 && !_room_mode) {
|
||||
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" };
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s",
|
||||
ML[dmMelodySlot(ci.id.pub_key)]); }
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s",
|
||||
MELODY_LABELS[dmMelodySlot(ci.id.pub_key)]);
|
||||
int pinned_slot = _task->findFavouriteSlot(ci.id.pub_key);
|
||||
if (pinned_slot >= 0) snprintf(_ctx_pin_item, sizeof(_ctx_pin_item), "Unpin (slot %d)", pinned_slot + 1);
|
||||
else snprintf(_ctx_pin_item, sizeof(_ctx_pin_item), "Pin to dial");
|
||||
@@ -2024,7 +2027,7 @@ public:
|
||||
if (_ctx_menu.active) {
|
||||
// LEFT/RIGHT -- and Enter, via VALUE_NEXT below -- cycle Notif/Melody/Fav
|
||||
// in place; the menu stays open and only Back closes it.
|
||||
if (!_pin_picker_active && (keyIsPrev(c) || keyIsNext(c))) {
|
||||
if (!_pin_picker_active && !_ch_delete_confirm_active && (keyIsPrev(c) || keyIsNext(c))) {
|
||||
cycleChannelCtxValue(_ctx_menu.selectedIndex(), keyIsNext(c) ? 1 : -1);
|
||||
return true;
|
||||
}
|
||||
@@ -2041,6 +2044,22 @@ public:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (_ch_delete_confirm_active) {
|
||||
// Delete/Cancel sub-menu, defaults to Cancel (see where it's opened).
|
||||
if (res == PopupMenu::SELECTED && _ctx_menu.selectedIndex() == 0) { // "Delete"
|
||||
ChannelDetails ch;
|
||||
memset(&ch, 0, sizeof(ch));
|
||||
the_mesh.setChannelLocal(_ctx_ch_idx, ch);
|
||||
_task->showAlert("Channel deleted", 1000);
|
||||
}
|
||||
if (res != PopupMenu::NONE) {
|
||||
_ch_delete_confirm_active = false;
|
||||
_task->savePrefsIfDirty(_ctx_dirty);
|
||||
buildChannelList();
|
||||
if (_channel_sel >= _num_channels) _channel_sel = _num_channels > 0 ? _num_channels - 1 : 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (res == PopupMenu::VALUE_NEXT) {
|
||||
cycleChannelCtxValue(_ctx_menu.selectedIndex(), 1);
|
||||
return true; // still open -- the save/rebuild below waits for close
|
||||
@@ -2071,11 +2090,13 @@ public:
|
||||
} else if (sel == 5) { // Edit
|
||||
ChannelDetails ch;
|
||||
if (the_mesh.getChannel(ch_idx, ch)) _ch_view.openEdit(ch_idx, ch.name);
|
||||
} else if (sel == 6) { // Delete
|
||||
ChannelDetails ch;
|
||||
memset(&ch, 0, sizeof(ch));
|
||||
the_mesh.setChannelLocal(ch_idx, ch);
|
||||
_task->showAlert("Channel deleted", 1000);
|
||||
} else if (sel == 6) { // Delete -- confirm first (destructive)
|
||||
_ctx_menu.begin("Delete channel?", 2);
|
||||
_ctx_menu.addItem("Delete");
|
||||
_ctx_menu.addItem("Cancel");
|
||||
_ctx_menu.setSelected(1);
|
||||
_ch_delete_confirm_active = true;
|
||||
return true; // list rebuild below would close the submenu
|
||||
}
|
||||
// sel 1/2/3 are value rows -- Enter never selects them
|
||||
// (see cycleChannelCtxValue).
|
||||
@@ -2125,12 +2146,10 @@ 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" };
|
||||
snprintf(_ctx_notif_item, sizeof(_ctx_notif_item), "Notif: %s",
|
||||
NOTIF_LABELS[chNotifState(ch_idx)]);
|
||||
{ static const char* ML[] = { "Global", "M1", "M2" };
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s",
|
||||
ML[chNotifMelody(ch_idx)]); }
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s",
|
||||
MELODY_LABELS[chNotifMelody(ch_idx)]);
|
||||
{ NodePrefs* p2 = _task->getNodePrefs();
|
||||
bool is_fav = p2 && (p2->ch_fav_bitmask & (1ULL << ch_idx));
|
||||
snprintf(_ctx_fav_item, sizeof(_ctx_fav_item), is_fav ? "Fav: ON" : "Fav: OFF"); }
|
||||
@@ -2398,3 +2417,6 @@ public:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const char* const MessagesScreen::NOTIF_LABELS[3] = { "Default", "OFF", "ON" };
|
||||
const char* const MessagesScreen::MELODY_LABELS[3] = { "Global", "M1", "M2" };
|
||||
|
||||
@@ -446,7 +446,9 @@ class NearbyScreen : public UIScreen {
|
||||
if (slot >= 0) {
|
||||
_task->clearFavouriteSlot(slot);
|
||||
the_mesh.savePrefs();
|
||||
_task->showAlert("Unpinned", 1000);
|
||||
char alert[24];
|
||||
snprintf(alert, sizeof(alert), "Unpinned (slot %d)", slot + 1);
|
||||
_task->showAlert(alert, 1000);
|
||||
return;
|
||||
}
|
||||
for (int s = 0; s < NodePrefs::FAVOURITES_COUNT; s++) {
|
||||
|
||||
@@ -39,6 +39,7 @@ struct RadioPresetPicker {
|
||||
int user_count = 0;
|
||||
bool saving = false; // keyboard is open to name a new preset
|
||||
bool deleting = false; // menu is showing the delete sub-list
|
||||
int confirm_slot = -1; // >=0 = confirming deletion of this NodePrefs slot
|
||||
|
||||
static bool matches(const Target& t, float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
return radioParamsMatchPreset(*t.freq, *t.bw, *t.sf, *t.cr, freq, bw, sf, cr);
|
||||
@@ -124,19 +125,39 @@ struct RadioPresetPicker {
|
||||
deleting = true;
|
||||
}
|
||||
|
||||
// Third level, reached by picking a name off the delete sub-list: a plain
|
||||
// Delete/Cancel confirm, defaulting to Cancel like every other destructive
|
||||
// action's popup (see NearbyScreen's contact-delete confirm). Whichever row
|
||||
// is picked, onSelected() below is terminal -- the whole picker closes,
|
||||
// same as it already does after a built-in/user preset pick.
|
||||
void openConfirm(uint8_t slot) {
|
||||
menu.begin("Delete preset?", 2);
|
||||
menu.addItem("Delete");
|
||||
menu.addItem("Cancel");
|
||||
menu.setSelected(1);
|
||||
confirm_slot = slot;
|
||||
deleting = false;
|
||||
}
|
||||
|
||||
// Handle the index the popup reports as SELECTED. Mutates target fields on a
|
||||
// built-in/user pick; deletes a slot in the delete sub-list. See Result.
|
||||
// built-in/user pick; opens the delete confirm from the delete sub-list, or
|
||||
// resolves that confirm if it's the level currently showing. See Result.
|
||||
Result onSelected(int idx, NodePrefs* p, const Target& t) {
|
||||
if (!p) return NONE;
|
||||
if (deleting) {
|
||||
if (confirm_slot >= 0) {
|
||||
Result r = NONE;
|
||||
if (idx >= 0 && idx < user_count) {
|
||||
p->user_radio_presets[user_slot[idx]].name[0] = '\0';
|
||||
if (idx == 0) { // "Delete"
|
||||
p->user_radio_presets[confirm_slot].name[0] = '\0';
|
||||
r = DELETED;
|
||||
}
|
||||
deleting = false;
|
||||
confirm_slot = -1;
|
||||
return r;
|
||||
}
|
||||
if (deleting) {
|
||||
if (idx >= 0 && idx < user_count) openConfirm(user_slot[idx]);
|
||||
deleting = false;
|
||||
return NONE;
|
||||
}
|
||||
const int builtin_base = 1;
|
||||
const int user_base = builtin_base + RADIO_PRESET_COUNT;
|
||||
const int delete_idx = user_base + user_count;
|
||||
|
||||
@@ -144,7 +144,7 @@ public:
|
||||
void onShow() override {
|
||||
_dirty = false; _sel = 0; _scroll = 0;
|
||||
_picker.menu.active = false; _editor.freq.active = false;
|
||||
_picker.saving = false; _picker.deleting = false;
|
||||
_picker.saving = false; _picker.deleting = false; _picker.confirm_slot = -1;
|
||||
_editing_scope = false;
|
||||
}
|
||||
|
||||
@@ -234,6 +234,7 @@ public:
|
||||
}
|
||||
} else if (res == PopupMenu::CANCELLED) {
|
||||
_picker.deleting = false;
|
||||
_picker.confirm_slot = -1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -849,6 +849,7 @@ public:
|
||||
}
|
||||
} else if (res == PopupMenu::CANCELLED) {
|
||||
_picker.deleting = false;
|
||||
_picker.confirm_slot = -1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class TrailScreen : public UIScreen {
|
||||
// correctly (settings rows cycle with LEFT/RIGHT; everything else is Enter).
|
||||
// Live-share *config* lives in its own tool (Tools › Live Share); the map only
|
||||
// keeps the one-shot "Share my pos" action.
|
||||
enum MenuLevel { ML_MAIN, ML_FILE, ML_SETTINGS, ML_CONFIRM_GPS };
|
||||
enum MenuLevel { ML_MAIN, ML_FILE, ML_SETTINGS, ML_CONFIRM_GPS, ML_CONFIRM_RESET };
|
||||
PopupMenu _action_menu;
|
||||
uint8_t _menu_level = ML_MAIN;
|
||||
uint8_t _act_map[16]; // max rows on any one level; pushAction guards the cap
|
||||
@@ -177,6 +177,13 @@ public:
|
||||
_menu_level = ML_MAIN; // popup already closed by handleInput()
|
||||
return true;
|
||||
}
|
||||
// Reset confirmation popup: same shape, but defaults to Cancel (row 1)
|
||||
// since unlike the GPS prompt above, this one is destructive.
|
||||
if (_menu_level == ML_CONFIRM_RESET) {
|
||||
if (_action_menu.selectedIndex() == 0) handleReset(); // "Reset"
|
||||
_menu_level = ML_MAIN;
|
||||
return true;
|
||||
}
|
||||
int sel = _action_menu.selectedIndex();
|
||||
ActionId act = (sel >= 0 && sel < _act_count) ? (ActionId)_act_map[sel] : ACT_TOGGLE;
|
||||
switch (act) {
|
||||
@@ -205,7 +212,7 @@ public:
|
||||
case ACT_TRACKBACK: _wp.startTrackBack(); break;
|
||||
case ACT_SAVE: handleSave(); break;
|
||||
case ACT_LOAD: handleLoad(); break;
|
||||
case ACT_RESET: handleReset(); break;
|
||||
case ACT_RESET: buildResetConfirmMenu(); return true;
|
||||
case ACT_EXPORT: handleExport(); break;
|
||||
case ACT_EXPORT_SAVED: handleExportSaved(); break;
|
||||
}
|
||||
@@ -391,6 +398,19 @@ private:
|
||||
_action_menu.addItem("Cancel");
|
||||
}
|
||||
|
||||
// Confirmation shown when "Reset trail" is chosen -- wipes the whole
|
||||
// recorded route with no way back short of a prior manual Save, unlike the
|
||||
// GPS prompt above this defaults to Cancel (row 1), same idiom as
|
||||
// NearbyScreen's contact-delete confirm.
|
||||
void buildResetConfirmMenu() {
|
||||
_menu_level = ML_CONFIRM_RESET;
|
||||
_act_count = 0;
|
||||
_action_menu.begin("Reset trail?", 2);
|
||||
_action_menu.addItem("Reset");
|
||||
_action_menu.addItem("Cancel");
|
||||
_action_menu.setSelected(1);
|
||||
}
|
||||
|
||||
// Trail-file submenu — only the operations that make sense right now.
|
||||
void buildFileMenu() {
|
||||
_menu_level = ML_FILE;
|
||||
|
||||
@@ -1226,7 +1226,9 @@ public:
|
||||
}
|
||||
_task->clearFavouriteSlot(_pin_target_slot);
|
||||
the_mesh.savePrefs();
|
||||
_task->showAlert("Unpinned", 800);
|
||||
char alert[24];
|
||||
snprintf(alert, sizeof(alert), "Unpinned (slot %d)", _pin_target_slot + 1);
|
||||
_task->showAlert(alert, 800);
|
||||
}
|
||||
if (res != PopupMenu::NONE) _pin_target_slot = -1;
|
||||
return true;
|
||||
@@ -1295,9 +1297,9 @@ public:
|
||||
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
|
||||
_task->notify(UIEventType::ack);
|
||||
if (the_mesh.advert()) {
|
||||
_task->showAlert("Advert sent!", 1000);
|
||||
_task->showAlert("Advert sent", 1000);
|
||||
} else {
|
||||
_task->showAlert("Advert failed..", 1000);
|
||||
_task->showAlert("Advert failed", 1000);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ MINI_ICON(ICON_KEY, 5, // padlock — remote admin (privileged/password-gated
|
||||
packRow("#...#"),
|
||||
packRow("#...#"),
|
||||
packRow("#####"),
|
||||
packRow("..#.."),
|
||||
packRow("##.##"),
|
||||
packRow("#####"));
|
||||
MINI_ICON(ICON_PINS, 5, // 3-pin header — GPIO
|
||||
packRow("#.#.#"),
|
||||
|
||||
+3
-1
@@ -20,7 +20,9 @@
|
||||
- **A location shared in a message could be navigated to and saved as a waypoint, but not set as the Locator target** — the one row that Nodes and Waypoints both offer for a coordinate. **Set as target** now joins Navigate and Save waypoint in the message Options menu.
|
||||
- **Tools › Nodes refused to make a Locator target out of a node whose full public key it didn't have**, even with a perfectly good position on screen — the row you get when someone shares their location on a channel and isn't in your contacts, which is exactly the group-outing case the feature is for. One flag was standing in for two different things: "can be pinged" (needs the whole 32-byte key) and "can be identified" (needs only a 6-byte prefix, which is all a person target ever uses). Those are separate now, so **Set as target** is offered whenever the node has a position: with an identity it follows them as they move, and without one — a channel share, matched by name — it pins the place they were last seen, the same way a waypoint or a location out of a message does. **Navigate** was never restricted and is unchanged.
|
||||
- **Tools › Locator's target picker listed the people pinned to the Favourites Dial as its top tier**, which stopped making sense once pinning and favouriting became separate things — pinning puts something on a home page, and has nothing to say about who you'd geofence. The picker now leads with your **favourites** instead (still offered before they have a known position, so you can arm ahead of time); everyone else with a resolvable position follows, as before.
|
||||
- **Hold Enter no longer doubles as a second Back key.** On Tools, Locator, Live Share, Repeater, Remote Bot, Auto-Advert, GPIO, Compass, the Dashboard config and the Messages navigate view it quietly meant "go back", while on other screens the same gesture opens a context menu — so the same long press did two unrelated things depending on where you were. It now only ever opens a menu (or does nothing where there is none), and Back is the single way back. This also applies inside an open popup, which Hold Enter used to dismiss.
|
||||
- **Hold Enter no longer doubles as a second Back key.** On Tools, Locator, Live Share, Repeater, Remote Bot, Auto-Advert, GPIO, Compass, the Dashboard config and the Messages navigate view it quietly meant "go back", while on other screens the same gesture opens a context menu — so the same long press did two unrelated things depending on where you were. It now only ever opens a menu (or does nothing where there is none), and Back is the single way back. This also applies inside an open popup, which Hold Enter used to dismiss — and, last of all, inside the on-screen keyboard: Hold Enter on Space, OK/Done or the `{}` placeholder cell used to close the keyboard exactly like Cancel; it's now a no-op there too, alongside its real jobs on Shift (caps-lock) and Backspace (clear field) and its accent popup on a Latin letter.
|
||||
- **Deleting a channel, deleting a saved radio preset, and Trail's "Reset trail" all fired immediately on a single Enter, with no way back.** Deleting a contact already asks first and defaults to Cancel; these three didn't, and Reset trail was the worst of them — it wipes the entire recorded GPS trail with no undo short of a prior manual Save, more destructive than the GPS-off prompt already sitting one menu over in the same screen. All three now confirm the same way contact-delete does.
|
||||
- **The one alert after a successful advert or message send didn't match the rest of the app's toasts.** `"Advert sent!"` / `"Advert failed.."` and `"Sent!"` were the only alerts anywhere with trailing punctuation; every other confirmation (`"Preset saved"`, `"Contact added"`, `"Target set"`, …) is bare. Also, unpinning something from the Favourites Dial reported which slot it came out of on some screens but not others — pinning always said so, unpinning only did from the Messages screens. Both now match the rest.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user