diff --git a/deploy/apps/lang/bg.lang b/deploy/apps/lang/bg.lang index 3b2ee96b..9ddaca64 100644 --- a/deploy/apps/lang/bg.lang +++ b/deploy/apps/lang/bg.lang @@ -2160,3 +2160,4 @@ you вие Flood in / out Flood вход / изход Direct in / out Директни вход / изход Airtime TX / RX Ефирно време TX / RX +Fewest hops Най-малко скокове diff --git a/deploy/apps/lang/cs.lang b/deploy/apps/lang/cs.lang index f152cd29..0ef1e9e5 100644 --- a/deploy/apps/lang/cs.lang +++ b/deploy/apps/lang/cs.lang @@ -2176,3 +2176,4 @@ you vy Flood in / out Flood příchozí / odchozí Direct in / out Přímé příchozí / odchozí Airtime TX / RX Vysílací čas TX / RX +Fewest hops Nejméně skoků diff --git a/deploy/apps/lang/de.lang b/deploy/apps/lang/de.lang index 48ee3dee..626012f8 100644 --- a/deploy/apps/lang/de.lang +++ b/deploy/apps/lang/de.lang @@ -2158,3 +2158,4 @@ you du Flood in / out Flood ein / aus Direct in / out Direkt ein / aus Airtime TX / RX Sendezeit TX / RX +Fewest hops Wenigste Hops diff --git a/deploy/apps/lang/el.lang b/deploy/apps/lang/el.lang index 5d8e0394..d83825c6 100644 --- a/deploy/apps/lang/el.lang +++ b/deploy/apps/lang/el.lang @@ -2160,3 +2160,4 @@ you εσείς Flood in / out Flood εισ. / εξ. Direct in / out Άμεσα εισ. / εξ. Airtime TX / RX Χρόνος εκπομπής TX / RX +Fewest hops Λιγότερα άλματα diff --git a/deploy/apps/lang/es.lang b/deploy/apps/lang/es.lang index c4b53806..34396beb 100644 --- a/deploy/apps/lang/es.lang +++ b/deploy/apps/lang/es.lang @@ -2160,3 +2160,4 @@ you tú Flood in / out Flood entrada / salida Direct in / out Directo entrada / salida Airtime TX / RX Tiempo en aire TX / RX +Fewest hops Menos saltos diff --git a/deploy/apps/lang/fr.lang b/deploy/apps/lang/fr.lang index d394a482..c5232bcf 100644 --- a/deploy/apps/lang/fr.lang +++ b/deploy/apps/lang/fr.lang @@ -2159,3 +2159,4 @@ you vous Flood in / out Flood reçus / émis Direct in / out Direct reçus / émis Airtime TX / RX Temps d'antenne TX / RX +Fewest hops Moins de sauts diff --git a/deploy/apps/lang/hu.lang b/deploy/apps/lang/hu.lang index e361b9dc..e5ebf9d7 100644 --- a/deploy/apps/lang/hu.lang +++ b/deploy/apps/lang/hu.lang @@ -2170,3 +2170,4 @@ you te Flood in / out Flood be / ki Direct in / out Direkt be / ki Airtime TX / RX Adásidő TX / RX +Fewest hops Legkevesebb ugrás diff --git a/deploy/apps/lang/it.lang b/deploy/apps/lang/it.lang index 9b434be7..9ce57ba6 100644 --- a/deploy/apps/lang/it.lang +++ b/deploy/apps/lang/it.lang @@ -2159,3 +2159,4 @@ you tu Flood in / out Flood in / out Direct in / out Diretti in / out Airtime TX / RX Tempo in onda TX / RX +Fewest hops Meno salti diff --git a/deploy/apps/lang/nl.lang b/deploy/apps/lang/nl.lang index 02146a65..1fd942eb 100644 --- a/deploy/apps/lang/nl.lang +++ b/deploy/apps/lang/nl.lang @@ -2158,3 +2158,4 @@ you jij Flood in / out Flood in / uit Direct in / out Direct in / uit Airtime TX / RX Zendtijd TX / RX +Fewest hops Minste hops diff --git a/deploy/apps/lang/pt-br.lang b/deploy/apps/lang/pt-br.lang index 5a36b247..55e6e829 100644 --- a/deploy/apps/lang/pt-br.lang +++ b/deploy/apps/lang/pt-br.lang @@ -2160,3 +2160,4 @@ you você Flood in / out Flood entrada / saída Direct in / out Direto entrada / saída Airtime TX / RX Tempo no ar TX / RX +Fewest hops Menos saltos diff --git a/deploy/apps/lang/ro.lang b/deploy/apps/lang/ro.lang index 06edf2b1..71f439d6 100644 --- a/deploy/apps/lang/ro.lang +++ b/deploy/apps/lang/ro.lang @@ -2161,3 +2161,4 @@ you tu Flood in / out Flood intrare / ieșire Direct in / out Direct intrare / ieșire Airtime TX / RX Timp în aer TX / RX +Fewest hops Cele mai puține salturi diff --git a/deploy/apps/lang/ru.lang b/deploy/apps/lang/ru.lang index a9e06973..a6ff9f81 100644 --- a/deploy/apps/lang/ru.lang +++ b/deploy/apps/lang/ru.lang @@ -2160,3 +2160,4 @@ you вы Flood in / out Flood вход / выход Direct in / out Прямые вход / выход Airtime TX / RX Эфирное время TX / RX +Fewest hops Меньше всего хопов diff --git a/deploy/apps/lang/sr.lang b/deploy/apps/lang/sr.lang index e27a4daf..38b1b54e 100644 --- a/deploy/apps/lang/sr.lang +++ b/deploy/apps/lang/sr.lang @@ -2160,3 +2160,4 @@ you ви Flood in / out Flood улаз / излаз Direct in / out Директни улаз / излаз Airtime TX / RX Време емитовања TX / RX +Fewest hops Најмање скокова diff --git a/deploy/apps/lang/uk.lang b/deploy/apps/lang/uk.lang index 2bf5194b..1bc9df04 100644 --- a/deploy/apps/lang/uk.lang +++ b/deploy/apps/lang/uk.lang @@ -2160,3 +2160,4 @@ you ви Flood in / out Flood вхід / вихід Direct in / out Прямі вхід / вихід Airtime TX / RX Ефірний час TX / RX +Fewest hops Найменше хопів diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index f2aa606e..5a4cf85c 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -2962,14 +2962,15 @@ enum ContactsSortMode : uint8_t { CONTACTS_SORT_LAST_HEARD = 1, // most recent advert first (default) CONTACTS_SORT_LAST_MSG = 2, // most recent message first (falls back to last-heard) CONTACTS_SORT_DISTANCE = 3, // nearest first; rows with no shared location are excluded - CONTACTS_SORT_COUNT = 4, + CONTACTS_SORT_HOPS = 4, // fewest hops first; path-unknown rows sink to the end (#718) + CONTACTS_SORT_COUNT = 5, }; // Default to most-recently-heard at the top — operators care who's on the // air now far more than alphabetical order. Favorites still pin above all // (see the qsort comparator in refreshContactsList). static uint8_t g_contacts_sort = CONTACTS_SORT_LAST_HEARD; // Reverse the active sort's natural order (the asc/desc toggle). Natural order is -// A→Z / newest-heard / newest-message / nearest; `desc` flips just the primary key +// A→Z / newest-heard / newest-message / nearest / fewest hops; `desc` flips just the primary key // (favourites stay pinned on top, "?"-heard rows stay sunk). Reset when the mode // changes; re-tapping the active mode in the Sort sheet flips it. static bool g_contacts_sort_desc = false; @@ -2980,6 +2981,7 @@ static const char* contactsSortLabel(uint8_t m) { case CONTACTS_SORT_LAST_HEARD: return "RECENT"; case CONTACTS_SORT_LAST_MSG: return "MSG"; case CONTACTS_SORT_DISTANCE: return "DIST"; + case CONTACTS_SORT_HOPS: return "HOPS"; } return "?"; } @@ -2990,6 +2992,7 @@ static const char* contactsSortOptName(uint8_t m) { case CONTACTS_SORT_LAST_HEARD: return "Recently heard"; case CONTACTS_SORT_LAST_MSG: return "Recent message"; case CONTACTS_SORT_DISTANCE: return "Nearest first"; + case CONTACTS_SORT_HOPS: return "Fewest hops"; } return "?"; } @@ -14154,14 +14157,15 @@ static DiscoveredAddCtx s_disc_add_ctx[DISCOVERED_MAX]; static void openDiscoveredModalCb(lv_event_t* e); // ---- Discovered-list sort (mirrors the Contacts sort) ------------------------- -// Recently heard / Name / Distance, each with an ascending/descending toggle. +// Recently heard / Name / Distance / Hops, each with an ascending/descending toggle. // "Recent message" is contacts-only (discovered nodes have no message history) so // it's omitted. Distance excludes entries that have shared no location. enum DiscSortMode : uint8_t { DISC_SORT_RECENT = 0, // newest advert first (default) DISC_SORT_AZ = 1, // name A-Z DISC_SORT_DISTANCE = 2, // nearest first; no-location entries excluded - DISC_SORT_COUNT = 3, + DISC_SORT_HOPS = 3, // fewest hops first; path-unknown entries sink to the end (#718) + DISC_SORT_COUNT = 4, }; static uint8_t g_disc_sort = DISC_SORT_RECENT; static bool g_disc_sort_desc = false; @@ -14170,6 +14174,7 @@ static const char* discSortOptName(uint8_t m) { case DISC_SORT_RECENT: return "Recently heard"; case DISC_SORT_AZ: return "Name (A-Z)"; case DISC_SORT_DISTANCE: return "Nearest first"; + case DISC_SORT_HOPS: return "Fewest hops"; } return "?"; } @@ -14186,6 +14191,14 @@ static bool discSortBefore(int ia, int ib, double self_lat, double self_lon) { if (da < db) prim = -1; else if (da > db) prim = 1; break; } + case DISC_SORT_HOPS: { + // Unknown path sinks to the end whichever way the list is flipped. + const bool ua = (A.path_len == OUT_PATH_UNKNOWN), ub = (B.path_len == OUT_PATH_UNKNOWN); + if (ua != ub) return ub; + const uint8_t ha = pathHops(A.path_len), hb = pathHops(B.path_len); + if (ha != hb) prim = (ha < hb) ? -1 : 1; + break; + } case DISC_SORT_AZ: prim = strcasecmp(A.ci.name, B.ci.name); break; @@ -32663,7 +32676,7 @@ static lv_obj_t* s_ct_sort_sheet = nullptr; // Sort & filter overlay static lv_obj_t* s_ct_normal_bar = nullptr; // header toolbar shown in normal mode static lv_obj_t* s_ct_select_bar = nullptr; // header toolbar shown in select mode static lv_obj_t* s_ct_del_btn = nullptr; // "Delete (N)" button in the select bar -static lv_obj_t* s_ct_sort_btns[CONTACTS_SORT_COUNT] = { nullptr, nullptr, nullptr, nullptr }; +static lv_obj_t* s_ct_sort_btns[CONTACTS_SORT_COUNT] = {}; static lv_obj_t* s_ct_filter_btns[6] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; static lv_obj_t* s_ct_filter_btn_lbl = nullptr; // header Filter button label (shows the active filter) static lv_obj_t* s_ct_disc_badge = nullptr; // count badge on the header Discovered button @@ -33059,6 +33072,7 @@ static void openContactsSortSheet(){ s_ct_sort_btns[1] = ctSheetOption(card, contactsSortOptName(CONTACTS_SORT_LAST_HEARD), ctSortOptCb, CONTACTS_SORT_LAST_HEARD, false); s_ct_sort_btns[2] = ctSheetOption(card, contactsSortOptName(CONTACTS_SORT_LAST_MSG), ctSortOptCb, CONTACTS_SORT_LAST_MSG, false); s_ct_sort_btns[3] = ctSheetOption(card, contactsSortOptName(CONTACTS_SORT_DISTANCE), ctSortOptCb, CONTACTS_SORT_DISTANCE, false); + s_ct_sort_btns[4] = ctSheetOption(card, contactsSortOptName(CONTACTS_SORT_HOPS), ctSortOptCb, CONTACTS_SORT_HOPS, false); ctSortRowsRepaint(); // tap the active row again to flip ascending/descending lv_obj_t* sp = lv_obj_create(card); lv_obj_remove_style_all(sp); lv_obj_set_size(sp, 1, 4); ctSheetOption(card, LV_SYMBOL_TRASH " Select to delete", ctSelectFromSheetCb, 0, false); @@ -45795,6 +45809,13 @@ static void refreshContactsList() { const double db = contactDistanceKm(s_ct_sort_self_lat, s_ct_sort_self_lon, eb->gps_lat / 1.0e6, eb->gps_lon / 1.0e6); if (da < db) prim = -1; else if (da > db) prim = 1; + } else if (g_contacts_sort == CONTACTS_SORT_HOPS) { + // Fewest hops first; a row with no known path (shows no hop count) sinks to + // the end whichever way the list is flipped. + const bool ua = (ea->out_path_len == OUT_PATH_UNKNOWN), ub = (eb->out_path_len == OUT_PATH_UNKNOWN); + if (ua != ub) return ua ? 1 : -1; + const uint8_t ha = pathHops(ea->out_path_len), hb = pathHops(eb->out_path_len); + if (ha != hb) prim = (ha < hb) ? -1 : 1; } else if (g_contacts_sort == CONTACTS_SORT_LAST_HEARD || g_contacts_sort == CONTACTS_SORT_LAST_MSG) { // Contacts whose last-heard renders as "?" sink to the end of the time-based