diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 6db9a1da..da5a9b36 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -535,6 +535,11 @@ struct NodePrefs { // persisted to file // ui-new: 0 = big clock + date + unread pill, 1 = compact clock with the // clock fields as rows below. The L2 has its own lock screen. uint8_t lock_compact; + // Settings > Contacts > "Channel order": how the channel lists are sorted + // (after favourites, when those go first): 0 = as the slots are (default), + // 1 = latest message first, 2 = by name. See chanctl::order(). + static const uint8_t CH_SORT_COUNT = 3; + uint8_t ch_sort; // Single source of truth for the live-share option tables (shared by the Map // UI labels and the auto-send engine in UITask). diff --git a/examples/companion_radio/ui-core/ChannelControl.h b/examples/companion_radio/ui-core/ChannelControl.h index d7f4b96b..fa4801b7 100644 --- a/examples/companion_radio/ui-core/ChannelControl.h +++ b/examples/companion_radio/ui-core/ChannelControl.h @@ -198,6 +198,62 @@ static void setMelody(NodePrefs* p, uint8_t idx, uint8_t slot) { static bool favourite(const NodePrefs* p, uint8_t idx) { return p && (p->ch_fav_bitmask & (1ULL << idx)); } +// The channels a list shows, in order: out[] gets the slot numbers, the count +// comes back. Favourites first (fav_sort), then by ch_sort -- slot order, the +// channel with the newest message first (channels without one after those, +// in slot order), or by name. Stable, so equal rows keep their slot order. +static int order(const NodePrefs* p, const MessageHistory& hist, uint8_t* out, bool fav_only) { + int n = 0; + for (int i = 0; i < MAX_GROUP_CHANNELS; i++) { + ChannelDetails ch; + if (!the_mesh.getChannel(i, ch) || ch.name[0] == '\0') continue; + if (fav_only && !favourite(p, i)) continue; + out[n++] = (uint8_t)i; + } + const bool fav_first = !p || p->fav_sort; + const uint8_t mode = p && p->ch_sort < NodePrefs::CH_SORT_COUNT ? p->ch_sort : 0; + if (n < 2 || (!fav_first && mode == 0)) return n; + + uint16_t rank[MAX_GROUP_CHANNELS]; // newest message = 0 + for (int i = 0; i < MAX_GROUP_CHANNELS; i++) rank[i] = 0xFFFF; + if (mode == 1) { + int left = n; + for (int j = 0; j < hist.chHistCount() && left > 0; j++) { + uint8_t c = hist.chAtPos(hist.chHistPosNewest(j)).ch_idx; + if (c < MAX_GROUP_CHANNELS && rank[c] == 0xFFFF) { rank[c] = (uint16_t)j; left--; } + } + } + // true when a goes before b + auto before = [&](uint8_t a, uint8_t b) { + if (fav_first) { + bool fa = favourite(p, a), fb = favourite(p, b); + if (fa != fb) return fa; + } + if (mode == 1) return rank[a] < rank[b]; + if (mode == 2) { + ChannelDetails ca, cb; + the_mesh.getChannel(a, ca); + the_mesh.getChannel(b, cb); + const char* x = bareName(ca.name); + const char* y = bareName(cb.name); + for (;; x++, y++) { + char u = *x >= 'A' && *x <= 'Z' ? *x + 32 : *x; + char v = *y >= 'A' && *y <= 'Z' ? *y + 32 : *y; + if (u != v) return (uint8_t)u < (uint8_t)v; + if (!u) return false; + } + } + return false; + }; + for (int i = 1; i < n; i++) { // insertion sort: stable + uint8_t v = out[i]; + int j = i; + while (j > 0 && before(v, out[j - 1])) { out[j] = out[j - 1]; j--; } + out[j] = v; + } + return n; +} + static void setFavourite(NodePrefs* p, uint8_t idx, bool on) { if (!p) return; if (on) p->ch_fav_bitmask |= 1ULL << idx; else p->ch_fav_bitmask &= ~(1ULL << idx); diff --git a/examples/companion_radio/ui-core/SettingsCli.h b/examples/companion_radio/ui-core/SettingsCli.h index e63e49a2..7694266d 100644 --- a/examples/companion_radio/ui-core/SettingsCli.h +++ b/examples/companion_radio/ui-core/SettingsCli.h @@ -21,7 +21,7 @@ static const Key KEYS[] = { K("autoadd", manual_add_contacts), KB("autoadd.chat", autoadd_config, 0x02), KB("autoadd.repeater", autoadd_config, 0x04), KB("autoadd.room", autoadd_config, 0x08), KB("autoadd.sensor", autoadd_config, 0x10), KB("autoadd.overwrite", autoadd_config, 0x01), K("autoadd.max.hops", autoadd_max_hops), - K("contacts.expiry", contact_expiry_idx), K("contacts.fav.first", fav_sort), K("dm.resend", dm_resend_count), + K("contacts.expiry", contact_expiry_idx), K("contacts.fav.first", fav_sort), K("contacts.channel.order", ch_sort), K("dm.resend", dm_resend_count), K("display.brightness", display_brightness), K("display.off", auto_off_secs), K("display.wake", msg_wake), K("display.alert", msg_alert), K("lock", auto_lock), K("lock.clock", lock_compact), K("display.text.size", ui_text_scale), K("display.icon.size", ui_icon_scale), diff --git a/examples/companion_radio/ui-core/SettingsSchema.h b/examples/companion_radio/ui-core/SettingsSchema.h index c406448c..283b542a 100644 --- a/examples/companion_radio/ui-core/SettingsSchema.h +++ b/examples/companion_radio/ui-core/SettingsSchema.h @@ -157,6 +157,10 @@ static void optLockLook(uint8_t v, char* b, int n, const NodePrefs&) { snprintf( static void optExpiry(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(b, n, "%s", NodePrefs::contactExpiryLabel(v)); } +static void optChSort(uint8_t v, char* b, int n, const NodePrefs&) { + static const char* L[NodePrefs::CH_SORT_COUNT] = { "Slots", "Newest", "Name" }; + snprintf(b, n, "%s", L[v < NodePrefs::CH_SORT_COUNT ? v : 0]); +} static void optVolume(uint8_t v, char* b, int n, const NodePrefs&) { static const char* L[5] = { "Quietest", "Quiet", "Medium", "Loud", "Loudest" }; snprintf(b, n, "%s", L[v < 5 ? v : 4]); @@ -281,6 +285,8 @@ static const Setting ALL[] = { IDX("Contact expiry", "Prune after inactivity", SEC_CONTACTS, contact_expiry_idx, NodePrefs::CONTACT_EXPIRY_COUNT, optExpiry, nullptr), SW("Favourites first", "In contact and node lists", SEC_CONTACTS, fav_sort, nullptr), + IDX("Channel order", "Slots, newest message or name", SEC_CONTACTS, ch_sort, + NodePrefs::CH_SORT_COUNT, optChSort, nullptr), // manual_add_contacts bit 0: 0 adds every node heard, 1 only the types below. IDX("Add heard nodes", "Every node, or only these types", SEC_AUTO_ADD, manual_add_contacts, 2, optAutoAdd, nullptr), @@ -333,7 +339,8 @@ static const struct { uint16_t offset; const char* text; } SHORT_LABELS[] = { { NP_OFF(gps_interval), "GPS pwr" }, { NP_OFF(clock_12h), "12h clock" }, { NP_OFF(clock_hide_seconds), "Seconds" }, { NP_OFF(units_imperial), "Imperial" }, { NP_OFF(dm_resend_count), "Resend" }, { NP_OFF(contact_expiry_idx), "Expire" }, - { NP_OFF(fav_sort), "Favs top" }, { NP_OFF(buzzer_volume), "Buzzer vol" }, + { NP_OFF(fav_sort), "Favs top" }, { NP_OFF(ch_sort), "Ch order" }, + { NP_OFF(buzzer_volume), "Buzzer vol" }, { NP_OFF(quiet_hours), "Quiet hrs" }, { NP_OFF(quiet_from), " from" }, { NP_OFF(quiet_to), " until" }, { NP_OFF(notif_melody_dm), "DM sound" }, { NP_OFF(notif_melody_ch), "Ch sound" }, { NP_OFF(notif_melody_ad), "AD sound" }, diff --git a/examples/companion_radio/ui-lvgl/ChatList.h b/examples/companion_radio/ui-lvgl/ChatList.h index 92b2c594..3b468e48 100644 --- a/examples/companion_radio/ui-lvgl/ChatList.h +++ b/examples/companion_radio/ui-lvgl/ChatList.h @@ -224,16 +224,10 @@ void UITask::fillChats() { own(); bool fav_first = !_prefs || _prefs->fav_sort; int ch_rows = 0; - for (int pass = fav_first ? 0 : 1; pass < 2 && !chatFolded(FOLD_CHANNELS); pass++) { - for (int i = 0; i < MAX_GROUP_CHANNELS; i++) { - ChannelDetails ch; - if (!the_mesh.getChannel(i, ch) || ch.name[0] == '\0') continue; - bool fav = chanctl::favourite(_prefs, i); - if (ch_fav_only && !fav) continue; - if (fav_first && fav != (pass == 0)) continue; - ch_rows++; - vlist::add(body, &CHANNEL, i); - } + if (!chatFolded(FOLD_CHANNELS)) { + uint8_t order[MAX_GROUP_CHANNELS]; + ch_rows = chanctl::order(_prefs, _core->history, order, ch_fav_only); + for (int r = 0; r < ch_rows; r++) vlist::add(body, &CHANNEL, order[r]); } if (!chatFolded(FOLD_CHANNELS)) { if (ch_rows == 0 && ch_fav_only) { label(body, "No favourite channels", THEME_FONT_SMALL, theme::TEXT_MUTED); own(); } diff --git a/examples/companion_radio/ui-new/MessagesScreen.h b/examples/companion_radio/ui-new/MessagesScreen.h index 10abed0f..de058860 100644 --- a/examples/companion_radio/ui-new/MessagesScreen.h +++ b/examples/companion_radio/ui-new/MessagesScreen.h @@ -836,23 +836,7 @@ class MessagesScreen : public UIScreen { void buildChannelList() { NodePrefs* p = _task->getNodePrefs(); - bool fav_only = (p && p->ch_fav_only); - _num_channels = 0; - for (int i = 0; i < MAX_GROUP_CHANNELS; i++) { - ChannelDetails ch; - if (!the_mesh.getChannel(i, ch) || ch.name[0] == '\0') continue; - if (fav_only && !(p->ch_fav_bitmask & (1ULL << i))) continue; - _channel_indices[_num_channels++] = (uint8_t)i; - } - // Favourites to the front, everything else keeping channel-slot order. - if (!p || !p->fav_sort) return; - int front = 0; - for (int i = 0; i < _num_channels; i++) { - if (!chIsFav(_channel_indices[i])) continue; - uint8_t v = _channel_indices[i]; - for (int j = i; j > front; j--) _channel_indices[j] = _channel_indices[j - 1]; - _channel_indices[front++] = v; - } + _num_channels = chanctl::order(p, _history, _channel_indices, p && p->ch_fav_only); } // Advance one of the contact menu's value rows. dir is +1 for RIGHT and for