refactor: one place each for "in view" and the keyboard's backspace

ui-new's messagesInView() and ui-lvgl's threadInView() hold the
lit-and-unlocked test the viewing checks repeated; the keyboard's three
copies of the backspace call deleteBeforeCursor(). A stale comment on the
cursor-mode hint goes, onDisplayOn() sits after the view helpers it calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-08 23:58:52 +02:00
co-authored by Claude Opus 5.5
parent 9e24abcbeb
commit d901a58424
7 changed files with 31 additions and 43 deletions
+3 -2
View File
@@ -3469,8 +3469,9 @@ void MyMesh::loop() {
if ((dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) if ((dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry))
|| (dirty_paths_expiry && millisHasNowPassed(dirty_paths_expiry))) { || (dirty_paths_expiry && millisHasNowPassed(dirty_paths_expiry))) {
if (millis() - _last_input_ms < USER_QUIET_MILLIS) { if (millis() - _last_input_ms < USER_QUIET_MILLIS) {
if (dirty_contacts_expiry) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); unsigned long later = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
if (dirty_paths_expiry) dirty_paths_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); if (dirty_contacts_expiry) dirty_contacts_expiry = later;
if (dirty_paths_expiry) dirty_paths_expiry = later;
} else { } else {
saveContacts(); saveContacts();
dirty_contacts_expiry = dirty_paths_expiry = 0; dirty_contacts_expiry = dirty_paths_expiry = 0;
+6 -2
View File
@@ -1914,12 +1914,16 @@ void UITask::onMessageArrived(const UiEvent& ev) {
} }
// On screen: a conversation left open under the lock or a dark display isn't read. // On screen: a conversation left open under the lock or a dark display isn't read.
bool UITask::threadInView() const {
return !_asleep && !locked() && _screen == SCR_THREAD;
}
bool UITask::isViewingChannel(uint8_t channel_idx) { bool UITask::isViewingChannel(uint8_t channel_idx) {
return !_asleep && !locked() && _screen == SCR_THREAD && _thread_is_channel && _thread_channel == channel_idx; return threadInView() && _thread_is_channel && _thread_channel == channel_idx;
} }
bool UITask::isViewingDM(const uint8_t* pub_key) { bool UITask::isViewingDM(const uint8_t* pub_key) {
return !_asleep && !locked() && _screen == SCR_THREAD && !_thread_is_channel && memcmp(_thread_key, pub_key, 4) == 0; return threadInView() && !_thread_is_channel && memcmp(_thread_key, pub_key, 4) == 0;
} }
// ── Status bar + toast (top layer, over every screen) ───────────────────────── // ── Status bar + toast (top layer, over every screen) ─────────────────────────
@@ -430,6 +430,7 @@ private:
void buildThread(); void buildThread();
void threadOpenAt(int first); void threadOpenAt(int first);
void reopenThreadIfUnread(); void reopenThreadIfUnread();
bool threadInView() const; // a thread on a lit, unlocked screen
void refreshThread(); void refreshThread();
void refreshThreadAges(); void refreshThreadAges();
uint32_t threadSignature() const; uint32_t threadSignature() const;
@@ -546,6 +546,7 @@ struct KeyboardWidget {
return p; return p;
} }
// Backspace: the codepoint before the cursor.
void deleteBeforeCursor() { void deleteBeforeCursor() {
t9_cell = -1; t9_cell = -1;
if (cursor_pos <= 0) return; if (cursor_pos <= 0) return;
@@ -600,9 +601,7 @@ struct KeyboardWidget {
// Compact mode only ever draws 2 short hint lines (no grid, no status line // Compact mode only ever draws 2 short hint lines (no grid, no status line
// -- see below), but reserves at least as much height as the smallest real // -- see below), but reserves at least as much height as the smallest real
// grid (T9's 3 rows) would need, rather than shrinking to just those 2 // grid (T9's 3 rows) would need, rather than shrinking to just those 2
// lines: cursor_mode's own 3-line hint (drawn in this same region, // lines, so the preview keeps the same height whichever mode is on.
// regardless of Compact, for a physical-button user) already relies on
// that floor and would otherwise get clipped.
// A tall screen (the 4.2" panel: 20 lines) gives the keys half a line more each. // A tall screen (the 4.2" panel: 20 lines) gives the keys half a line more each.
const int key_lh = display.height() >= 18 * lh ? lh * 3 / 2 : lh; const int key_lh = display.height() >= 18 * lh ? lh * 3 / 2 : lh;
const int kb_h = compact_ui ? (KB_T9_ROWS + 1) * lh : (rows + 1) * key_lh; const int kb_h = compact_ui ? (KB_T9_ROWS + 1) * lh : (rows + 1) * key_lh;
@@ -1077,13 +1076,7 @@ struct KeyboardWidget {
// no ambiguity to resolve at this layer -- pollCardKB() already decided. // no ambiguity to resolve at this layer -- pollCardKB() already decided.
if (c == KEY_KB_ENTER) return DONE; if (c == KEY_KB_ENTER) return DONE;
if (c == 0x08) { if (c == 0x08) {
t9_cell = -1; // invalidate any pending T9 cycle -- see the grid paths below deleteBeforeCursor(); // also ends any pending T9 cycle -- see the grid paths below
if (cursor_pos > 0) {
int n = kbUtf8LastCharBytes(buf, cursor_pos);
memmove(buf + cursor_pos - n, buf + cursor_pos, len - cursor_pos);
len -= n; cursor_pos -= n;
buf[len] = '\0';
}
return NONE; return NONE;
} }
if (c >= 0x20 && c <= 0x7E) { if (c >= 0x20 && c <= 0x7E) {
@@ -1140,13 +1133,7 @@ struct KeyboardWidget {
} }
// special row // special row
if (col == 0) { if (col == 0) {
t9_cell = -1; deleteBeforeCursor();
if (cursor_pos > 0) {
int n = kbUtf8LastCharBytes(buf, cursor_pos);
memmove(buf + cursor_pos - n, buf + cursor_pos, len - cursor_pos);
len -= n; cursor_pos -= n;
buf[len] = '\0';
}
} else if (col == 1) { } else if (col == 1) {
pin_mode = false; // ABC switch: the user's own layout pin_mode = false; // ABC switch: the user's own layout
row = col = 0; row = col = 0;
@@ -1304,14 +1291,7 @@ struct KeyboardWidget {
buf[len] = '\0'; buf[len] = '\0';
} }
break; break;
case 2: case 2: deleteBeforeCursor(); break;
if (cursor_pos > 0) {
int n = kbUtf8LastCharBytes(buf, cursor_pos);
memmove(buf + cursor_pos - n, buf + cursor_pos, len - cursor_pos);
len -= n; cursor_pos -= n;
buf[len] = '\0';
}
break;
case 3: case 3:
openPlaceholders(); openPlaceholders();
break; break;
@@ -1063,13 +1063,6 @@ public:
_dm_hist_scroll = 0; _dm_hist_scroll = 0;
_dm_fs.active = false; _dm_fs.active = false;
} }
// The display came back on over a conversation: what was filed while it was
// dark is unread -- read on from its first message, as on opening.
void onDisplayOn() {
if (_ctx_menu.active) return;
if (_phase == CHANNEL_HIST && !_fs.active && _history.chUnread(_sel_channel_idx) > 0) beginChannelView(_sel_channel_idx);
else if (_phase == DM_HIST && !_dm_fs.active && _task->getDMUnread(_sel_contact.id.pub_key) > 0) beginDmView();
}
void beginChannelView(uint8_t channel_idx) { void beginChannelView(uint8_t channel_idx) {
_sel_channel_idx = channel_idx; _sel_channel_idx = channel_idx;
_unread_at_entry = (int)_history.chUnread(channel_idx); _unread_at_entry = (int)_history.chUnread(channel_idx);
@@ -1081,6 +1074,13 @@ public:
_hist_sel = _new_idx >= 0 ? _new_idx : _history.histCountForChannel(channel_idx) > 0 ? 0 : -1; _hist_sel = _new_idx >= 0 ? _new_idx : _history.histCountForChannel(channel_idx) > 0 ? 0 : -1;
_fs.active = false; _fs.active = false;
} }
// The display came back on over a conversation: what was filed while it was
// dark is unread -- read on from its first message, as on opening.
void onDisplayOn() {
if (_ctx_menu.active) return;
if (_phase == CHANNEL_HIST && !_fs.active && _history.chUnread(_sel_channel_idx) > 0) beginChannelView(_sel_channel_idx);
else if (_phase == DM_HIST && !_dm_fs.active && _task->getDMUnread(_sel_contact.id.pub_key) > 0) beginDmView();
}
// On the first unread (sel), opening: the scroll showing it highest with // On the first unread (sel), opening: the scroll showing it highest with
// "New" above it (stack(s) lays out from s, sets div_y when "New" fits). // "New" above it (stack(s) lays out from s, sets div_y when "New" fits).
+7 -6
View File
@@ -2280,13 +2280,14 @@ void UITask::pickBotRoomTarget() {
MyMesh::Listener* UITask::meshListener() { return _core; } MyMesh::Listener* UITask::meshListener() { return _core; }
// On screen: a conversation left open under the lock or a dark display isn't read. // On screen: a conversation left open under the lock or a dark display isn't read.
bool UITask::messagesInView() const {
return curr == messages_screen && _display && _display->isOn() && !_locked;
}
bool UITask::isViewingChannel(uint8_t channel_idx) { bool UITask::isViewingChannel(uint8_t channel_idx) {
return curr == messages_screen && _display && _display->isOn() && !_locked return messagesInView() && ((MessagesScreen*)messages_screen)->isViewingChannel(channel_idx);
&& ((MessagesScreen*)messages_screen)->isViewingChannel(channel_idx);
} }
bool UITask::isViewingDM(const uint8_t* pub_key) { bool UITask::isViewingDM(const uint8_t* pub_key) {
return curr == messages_screen && _display && _display->isOn() && !_locked return messagesInView() && ((MessagesScreen*)messages_screen)->isViewingDM(pub_key);
&& ((MessagesScreen*)messages_screen)->isViewingDM(pub_key);
} }
void UITask::onViewedHistoryGrew(bool channel) { void UITask::onViewedHistoryGrew(bool channel) {
((MessagesScreen*)messages_screen)->onViewedHistoryGrew(channel); ((MessagesScreen*)messages_screen)->onViewedHistoryGrew(channel);
@@ -3170,8 +3171,8 @@ void UITask::loop() {
pollConnection(); // BLE link state -> hasConnection() (see UITaskBase) pollConnection(); // BLE link state -> hasConnection() (see UITaskBase)
drainCoreEvents(); // react to what the Core filed during mesh processing (alerts, wake, sounds) drainCoreEvents(); // react to what the Core filed during mesh processing (alerts, wake, sounds)
{ // a conversation in view again (display on, unlocked): on to what came in meanwhile { // a conversation in view again (display on, unlocked): on to what came in meanwhile
bool on = _display && _display->isOn() && !_locked; bool on = messagesInView();
if (on && !_display_was_on && curr == messages_screen) ((MessagesScreen*)messages_screen)->onDisplayOn(); if (on && !_display_was_on) ((MessagesScreen*)messages_screen)->onDisplayOn();
_display_was_on = on; _display_was_on = on;
} }
if (status_screen) ((StatusScreen*)status_screen)->sample(); // Status history lines, once a minute if (status_screen) ((StatusScreen*)status_screen)->sample(); // Status history lines, once a minute
+2 -1
View File
@@ -60,10 +60,11 @@ class UITask : public UITaskBase, public UiCoreHost {
GenericVibration vibration; GenericVibration vibration;
#endif #endif
unsigned long _next_refresh, _auto_off; unsigned long _next_refresh, _auto_off;
bool _display_was_on = true; // loop(): the display back on and unlocked bool _display_was_on = true; // loop(): messagesInView() last time round
NodePrefs* _node_prefs; NodePrefs* _node_prefs;
bool _locked; bool _locked;
bool _tool_from_home = false; bool _tool_from_home = false;
bool messagesInView() const; // Messages on a lit, unlocked display
unsigned long _lock_wake_until; // when to blank screen again after locked wake (5s) unsigned long _lock_wake_until; // when to blank screen again after locked wake (5s)
int _lock_seq_count; // Enter presses while Back held (lock/unlock sequence) int _lock_seq_count; // Enter presses while Back held (lock/unlock sequence)
unsigned long _lock_seq_ms; // millis() of last lock-sequence press (for timeout) unsigned long _lock_seq_ms; // millis() of last lock-sequence press (for timeout)