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:
@@ -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("#.#.#"),
|
||||
|
||||
Reference in New Issue
Block a user