feat: home screen ordering — reorder all screens from Settings

NodePrefs: add page_order[11] (1-indexed bit-index, 0=end/legacy).
DataStore: persist page_order appended after use_lemon_font.

UITask/HomeScreen:
- bitToPage(): maps stable bit indices 0-10 to HomePage enum values
- buildVisibleOrder(): respects page_order when initialised, else
  falls back to default enum sequence
- navPage() and navigation dots use buildVisibleOrder()

SettingsScreen Home Pages section:
- Add Settings (bit 9) and Messages (bit 10) — show as "always"
- LEFT/RIGHT on any home page item moves it earlier/later in order
- ENTER toggles ON/OFF for pages that can be disabled
- Position number shown before label once custom order is active
- ensurePageOrderInit() seeds default order on first reorder action

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-05-22 23:33:06 +02:00
parent 7519740d72
commit bb94a6f81f
4 changed files with 191 additions and 39 deletions

View File

@@ -289,6 +289,9 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
file.read((uint8_t *)&_prefs.clock_12h, sizeof(_prefs.clock_12h));
if (file.available()) {
file.read((uint8_t *)&_prefs.use_lemon_font, sizeof(_prefs.use_lemon_font));
if (file.available()) {
file.read((uint8_t *)_prefs.page_order, sizeof(_prefs.page_order));
}
}
}
}
@@ -383,6 +386,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
file.write((uint8_t *)&_prefs.auto_lock, sizeof(_prefs.auto_lock));
file.write((uint8_t *)&_prefs.clock_12h, sizeof(_prefs.clock_12h));
file.write((uint8_t *)&_prefs.use_lemon_font, sizeof(_prefs.use_lemon_font));
file.write((uint8_t *)_prefs.page_order, sizeof(_prefs.page_order));
file.close();
}

View File

@@ -79,4 +79,7 @@ struct NodePrefs { // persisted to file
static const int DM_MELODY_TABLE_MAX = 16;
DmMelodyEntry dm_melody[DM_MELODY_TABLE_MAX];
uint8_t use_lemon_font; // 0=default Adafruit font, 1=Lemon font (Unicode, pixel-accurate wrap)
// Home screen page order: each byte = bit-index+1 (see HP_* bit positions + 9=Settings, 10=Messages).
// 0 = end of list (also uninitialized legacy). hasCustomOrder iff page_order[0] in 1..11.
uint8_t page_order[11];
};

View File

@@ -30,6 +30,7 @@ class SettingsScreen : public UIScreen {
#if UI_SENSORS_PAGE == 1
HOME_SENSORS,
#endif
HOME_SETTINGS, HOME_QUICK_MSG,
HOME_TOOLS, HOME_SHUTDOWN,
// Radio section
SECTION_RADIO,
@@ -137,9 +138,9 @@ class SettingsScreen : public UIScreen {
}
bool isHomePage(int item) const {
return item == HOME_CLOCK || item == HOME_RECENT || item == HOME_RADIO ||
item == HOME_BT || item == HOME_ADVERT || item == HOME_TOOLS ||
item == HOME_SHUTDOWN
return item == HOME_CLOCK || item == HOME_RECENT || item == HOME_RADIO ||
item == HOME_BT || item == HOME_ADVERT || item == HOME_TOOLS ||
item == HOME_SHUTDOWN || item == HOME_SETTINGS || item == HOME_QUICK_MSG
#if ENV_INCLUDE_GPS == 1
|| item == HOME_GPS
#endif
@@ -155,41 +156,126 @@ class SettingsScreen : public UIScreen {
if (item == HOME_RADIO) return HP_RADIO;
if (item == HOME_BT) return HP_BLUETOOTH;
if (item == HOME_ADVERT) return HP_ADVERT;
if (item == HOME_TOOLS) return HP_TOOLS;
if (item == HOME_SHUTDOWN) return HP_SHUTDOWN;
if (item == HOME_TOOLS) return HP_TOOLS;
if (item == HOME_SHUTDOWN) return HP_SHUTDOWN;
// HOME_SETTINGS and HOME_QUICK_MSG have no mask bit — always visible
#if ENV_INCLUDE_GPS == 1
if (item == HOME_GPS) return HP_GPS;
if (item == HOME_GPS) return HP_GPS;
#endif
#if UI_SENSORS_PAGE == 1
if (item == HOME_SENSORS) return HP_SENSORS;
if (item == HOME_SENSORS) return HP_SENSORS;
#endif
return 0;
}
const char* homePageLabel(int item) const {
if (item == HOME_CLOCK) return "Clock";
if (item == HOME_RECENT) return "Recent";
if (item == HOME_RADIO) return "Radio";
if (item == HOME_BT) return "Bluetooth";
if (item == HOME_ADVERT) return "Advert";
if (item == HOME_TOOLS) return "Tools";
if (item == HOME_SHUTDOWN) return "Shutdown";
if (item == HOME_CLOCK) return "Clock";
if (item == HOME_RECENT) return "Recent";
if (item == HOME_RADIO) return "Radio";
if (item == HOME_BT) return "Bluetooth";
if (item == HOME_ADVERT) return "Advert";
if (item == HOME_TOOLS) return "Tools";
if (item == HOME_SHUTDOWN) return "Shutdown";
if (item == HOME_SETTINGS) return "Settings";
if (item == HOME_QUICK_MSG) return "Messages";
#if ENV_INCLUDE_GPS == 1
if (item == HOME_GPS) return "GPS";
if (item == HOME_GPS) return "GPS";
#endif
#if UI_SENSORS_PAGE == 1
if (item == HOME_SENSORS) return "Sensors";
if (item == HOME_SENSORS) return "Sensors";
#endif
return "";
}
bool homePageVisible(int item, const NodePrefs* p) const {
if (item == HOME_SETTINGS || item == HOME_QUICK_MSG) return true;
uint16_t bit = homePageBit(item);
if (!bit) return false;
uint16_t mask = (p && p->home_pages_mask) ? p->home_pages_mask : HP_ALL;
return (mask & bit) != 0;
}
bool homePageToggleable(int item) const {
return item != HOME_SETTINGS && item != HOME_QUICK_MSG;
}
// Returns the bit-index (0-10) used in page_order for this SettingItem, or -1.
int homePageBitIndex(int item) const {
if (item == HOME_CLOCK) return 0;
if (item == HOME_RECENT) return 1;
if (item == HOME_RADIO) return 2;
if (item == HOME_BT) return 3;
if (item == HOME_ADVERT) return 4;
#if ENV_INCLUDE_GPS == 1
if (item == HOME_GPS) return 5;
#endif
#if UI_SENSORS_PAGE == 1
if (item == HOME_SENSORS) return 6;
#endif
if (item == HOME_TOOLS) return 7;
if (item == HOME_SHUTDOWN) return 8;
if (item == HOME_SETTINGS) return 9;
if (item == HOME_QUICK_MSG) return 10;
return -1;
}
// Returns 1-based position of item in page_order, or 0 if no custom order / not found.
int homePagePosition(int item, const NodePrefs* p) const {
if (!p || p->page_order[0] < 1 || p->page_order[0] > 11) return 0;
int bit = homePageBitIndex(item);
if (bit < 0) return 0;
for (int i = 0; i < 11; i++) {
uint8_t v = p->page_order[i];
if (v < 1 || v > 11) break;
if ((int)(v - 1) == bit) return i + 1;
}
return 0;
}
// Initialises page_order to the default display sequence if not already set.
void ensurePageOrderInit(NodePrefs* p) const {
if (!p) return;
if (p->page_order[0] >= 1 && p->page_order[0] <= 11) return;
// Default: CLOCK RECENT RADIO BT ADVERT [GPS] [SENSORS] SETTINGS TOOLS MESSAGES SHUTDOWN
int j = 0;
p->page_order[j++] = 0 + 1; // CLOCK
p->page_order[j++] = 1 + 1; // RECENT
p->page_order[j++] = 2 + 1; // RADIO
p->page_order[j++] = 3 + 1; // BLUETOOTH
p->page_order[j++] = 4 + 1; // ADVERT
#if ENV_INCLUDE_GPS == 1
p->page_order[j++] = 5 + 1; // GPS
#endif
#if UI_SENSORS_PAGE == 1
p->page_order[j++] = 6 + 1; // SENSORS
#endif
p->page_order[j++] = 9 + 1; // SETTINGS
p->page_order[j++] = 7 + 1; // TOOLS
p->page_order[j++] = 10 + 1; // MESSAGES (QUICK_MSG)
p->page_order[j++] = 8 + 1; // SHUTDOWN
while (j < 11) p->page_order[j++] = 0;
}
// Swaps item's page_order slot with its neighbour in the given direction (-1=earlier, +1=later).
void movePageInOrder(int item, int delta, NodePrefs* p) {
ensurePageOrderInit(p);
int bit = homePageBitIndex(item);
if (bit < 0) return;
int cur = -1, total = 0;
for (int i = 0; i < 11; i++) {
uint8_t v = p->page_order[i];
if (v < 1 || v > 11) break;
if ((int)(v - 1) == bit) cur = i;
total++;
}
if (cur < 0) return;
int next = cur + delta;
if (next < 0 || next >= total) return;
uint8_t tmp = p->page_order[cur];
p->page_order[cur] = p->page_order[next];
p->page_order[next] = tmp;
}
bool isMsgSlot(int item) const {
return item >= MSG_SLOT_0 && item <= MSG_SLOT_9;
}
@@ -269,9 +355,17 @@ class SettingsScreen : public UIScreen {
uint8_t v = p ? p->notif_melody_ch : 0;
display.print(L[v < 3 ? v : 0]); }
} else if (isHomePage(item)) {
int pos = homePagePosition(item, p);
if (pos > 0) {
char pb[4]; snprintf(pb, sizeof(pb), "%d ", pos);
display.print(pb);
}
display.print(homePageLabel(item));
display.setCursor(VAL_X, y);
display.print(homePageVisible(item, p) ? "ON" : "OFF");
if (!homePageToggleable(item))
display.print("always");
else
display.print(homePageVisible(item, p) ? "ON" : "OFF");
} else if (item == TX_POWER) {
display.print("TX Pwr");
char buf[8];
@@ -452,10 +546,18 @@ public:
p->notif_melody_ch = (p->notif_melody_ch + (left ? 2 : 1)) % 3;
_dirty = true; return true;
}
if (isHomePage(_selected) && p && (left || right || enter)) {
p->home_pages_mask ^= homePageBit(_selected);
_dirty = true;
return true;
if (isHomePage(_selected) && p) {
if (left || right) {
movePageInOrder(_selected, left ? -1 : 1, p);
_dirty = true;
return true;
}
if (enter && homePageToggleable(_selected)) {
p->home_pages_mask ^= homePageBit(_selected);
_dirty = true;
return true;
}
return enter;
}
if (_selected == TX_POWER && p) {
if (right && p->tx_power_dbm < 22) { p->tx_power_dbm++; _task->applyTxPower(); _dirty = true; return true; }

View File

@@ -174,6 +174,35 @@ class HomeScreen : public UIScreen {
return -1; // SETTINGS, QUICK_MSG always visible
}
// Maps page_order bit-index (0-10) back to the HomePage enum value for this build.
// Returns -1 if the page is not compiled in.
int bitToPage(int bit) const {
switch (bit) {
case 0: return CLOCK;
case 1: return RECENT;
case 2: return RADIO;
case 3: return BLUETOOTH;
case 4: return ADVERT;
case 5:
#if ENV_INCLUDE_GPS == 1
return GPS;
#else
return -1;
#endif
case 6:
#if UI_SENSORS_PAGE == 1
return SENSORS;
#else
return -1;
#endif
case 7: return TOOLS;
case 8: return SHUTDOWN;
case 9: return SETTINGS;
case 10: return QUICK_MSG;
default: return -1;
}
}
bool isPageVisible(int page) const {
int bit = pageBit(page);
if (bit < 0) return true;
@@ -181,12 +210,31 @@ class HomeScreen : public UIScreen {
return (mask >> bit) & 1;
}
int navPage(int from, int dir) const {
for (int i = 1; i < (int)Count; i++) {
int next = ((from + dir * i) % (int)Count + (int)Count) % (int)Count;
if (isPageVisible(next)) return next;
// Build ordered list of all visible pages, respecting page_order when set.
// Returns count; out[] receives HomePage enum values.
int buildVisibleOrder(int* out) const {
int n = 0;
bool custom = _node_prefs && _node_prefs->page_order[0] >= 1 && _node_prefs->page_order[0] <= 11;
if (custom) {
for (int i = 0; i < 11; i++) {
uint8_t v = _node_prefs->page_order[i];
if (v < 1 || v > 11) break;
int pg = bitToPage(v - 1);
if (pg >= 0 && pg < (int)Count && isPageVisible(pg)) out[n++] = pg;
}
} else {
for (int pg = 0; pg < (int)Count; pg++)
if (isPageVisible(pg)) out[n++] = pg;
}
return from;
return n;
}
int navPage(int from, int dir) const {
int order[11]; int n = buildVisibleOrder(order);
if (n == 0) return from;
int cur = 0;
for (int i = 0; i < n; i++) if (order[i] == from) { cur = i; break; }
return order[((cur + dir) % n + n) % n];
}
int renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
@@ -354,20 +402,15 @@ public:
// curr page indicator — hidden on CLOCK page (full screen used for dashboard)
if (_page != CLOCK) {
int vis_count = 0, curr_vis = 0;
for (int i = 0; i < (int)Count; i++) {
if (!isPageVisible(i)) continue;
if (i == _page) curr_vis = vis_count;
vis_count++;
}
int order[11]; int n = buildVisibleOrder(order);
int curr_vis = 0;
for (int i = 0; i < n; i++) if (order[i] == _page) { curr_vis = i; break; }
int y = 14;
int x = display.width() / 2 - 5 * (vis_count - 1);
int vi = 0;
for (int i = 0; i < (int)Count; i++) {
if (!isPageVisible(i)) continue;
if (vi == curr_vis) display.fillRect(x-1, y-1, 3, 3);
else display.fillRect(x, y, 1, 1);
x += 10; vi++;
int x = display.width() / 2 - 5 * (n - 1);
for (int i = 0; i < n; i++) {
if (i == curr_vis) display.fillRect(x-1, y-1, 3, 3);
else display.fillRect(x, y, 1, 1);
x += 10;
}
}