diff --git a/docs/solo_features/message_screen/message_screen.md b/docs/solo_features/message_screen/message_screen.md index 635dab2c..990bd920 100644 --- a/docs/solo_features/message_screen/message_screen.md +++ b/docs/solo_features/message_screen/message_screen.md @@ -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) | --- diff --git a/docs/solo_features/settings_screen/settings_screen.md b/docs/solo_features/settings_screen/settings_screen.md index 2ac96cb2..59b3789a 100644 --- a/docs/solo_features/settings_screen/settings_screen.md +++ b/docs/solo_features/settings_screen/settings_screen.md @@ -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. | diff --git a/docs/solo_features/tools_screen/tools_screen.md b/docs/solo_features/tools_screen/tools_screen.md index e2bd4639..b9f4ee3d 100644 --- a/docs/solo_features/tools_screen/tools_screen.md +++ b/docs/solo_features/tools_screen/tools_screen.md @@ -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): diff --git a/examples/companion_radio/ui-new/DiagnosticsScreen.h b/examples/companion_radio/ui-new/DiagnosticsScreen.h index dbfd5d42..f94b9222 100644 --- a/examples/companion_radio/ui-new/DiagnosticsScreen.h +++ b/examples/companion_radio/ui-new/DiagnosticsScreen.h @@ -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) { diff --git a/examples/companion_radio/ui-new/KeyboardWidget.h b/examples/companion_radio/ui-new/KeyboardWidget.h index 6f606b15..1b907ba3 100644 --- a/examples/companion_radio/ui-new/KeyboardWidget.h +++ b/examples/companion_radio/ui-new/KeyboardWidget.h @@ -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) { diff --git a/examples/companion_radio/ui-new/MessagesScreen.h b/examples/companion_radio/ui-new/MessagesScreen.h index af7d2505..dafba392 100644 --- a/examples/companion_radio/ui-new/MessagesScreen.h +++ b/examples/companion_radio/ui-new/MessagesScreen.h @@ -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" }; diff --git a/examples/companion_radio/ui-new/NearbyScreen.h b/examples/companion_radio/ui-new/NearbyScreen.h index 676b2a86..20712a54 100644 --- a/examples/companion_radio/ui-new/NearbyScreen.h +++ b/examples/companion_radio/ui-new/NearbyScreen.h @@ -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++) { diff --git a/examples/companion_radio/ui-new/RadioPresetPicker.h b/examples/companion_radio/ui-new/RadioPresetPicker.h index dd0af15d..d13cc677 100644 --- a/examples/companion_radio/ui-new/RadioPresetPicker.h +++ b/examples/companion_radio/ui-new/RadioPresetPicker.h @@ -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; diff --git a/examples/companion_radio/ui-new/RepeaterScreen.h b/examples/companion_radio/ui-new/RepeaterScreen.h index bffcda4b..84cb9705 100644 --- a/examples/companion_radio/ui-new/RepeaterScreen.h +++ b/examples/companion_radio/ui-new/RepeaterScreen.h @@ -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; } diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 5dedeb30..6217df17 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -849,6 +849,7 @@ public: } } else if (res == PopupMenu::CANCELLED) { _picker.deleting = false; + _picker.confirm_slot = -1; } return true; } diff --git a/examples/companion_radio/ui-new/TrailScreen.h b/examples/companion_radio/ui-new/TrailScreen.h index 3e139df6..f3f16bf9 100644 --- a/examples/companion_radio/ui-new/TrailScreen.h +++ b/examples/companion_radio/ui-new/TrailScreen.h @@ -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; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 8465b047..dc54fe13 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -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; } diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index 37a21a9c..49e21c59 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -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("#.#.#"), diff --git a/release-notes.md b/release-notes.md index 2b9f0a50..5094689c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -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. ---