From d72b5ed0b1179b8e010acdc911e42d57b9b19a7d Mon Sep 17 00:00:00 2001 From: Mo <83806223+Xmoo26@users.noreply.github.com> Date: Mon, 14 Sep 2026 20:53:51 +0200 Subject: [PATCH] Ext Module more than 5 items (#3314) * Refactor app display to use scrollable menu Removed specific app name text fields and replaced them with a scrollable menu for an unlimited app list. * Refactor app display logic in ExternalModuleView Refactor ExternalModuleView to manage app display more efficiently by using a menu instead of individual text fields. * Update menu item color to use theme foreground color * Ext Module: self-heal app list on transient read failures Only skip rebuilding the scrollable app list when the reported app count is unchanged AND the current menu already holds that many items. If some getStandaloneAppInfo() calls failed transiently (e.g. an I2C read glitch), the menu is incomplete; rebuilding on a later tick lets it self-heal instead of staying permanently truncated. Addresses Copilot review feedback on #3314. --- .../apps/ui_external_module_view.cpp | 72 +++++++++---------- .../apps/ui_external_module_view.hpp | 22 +++--- 2 files changed, 42 insertions(+), 52 deletions(-) diff --git a/firmware/application/apps/ui_external_module_view.cpp b/firmware/application/apps/ui_external_module_view.cpp index b3c0e0559..b8557a426 100644 --- a/firmware/application/apps/ui_external_module_view.cpp +++ b/firmware/application/apps/ui_external_module_view.cpp @@ -22,6 +22,7 @@ #include "ui_external_module_view.hpp" #include "portapack.hpp" #include "ui_standalone_view.hpp" +#include "theme.hpp" #include "i2cdevmanager.hpp" #include "i2cdev_ppmod.hpp" @@ -31,7 +32,7 @@ namespace ui { void ExternalModuleView::focus() { - dummy.focus(); + menu_apps.focus(); } void ExternalModuleView::on_tick_second() { @@ -44,11 +45,10 @@ void ExternalModuleView::on_tick_second() { text_name.set(""); text_version.set(""); text_number_apps.set(""); - text_app1_name.set(""); - text_app2_name.set(""); - text_app3_name.set(""); - text_app4_name.set(""); - text_app5_name.set(""); + if (shown_count_ != -1) { + menu_apps.clear(); + shown_count_ = -1; + } return; } @@ -59,11 +59,10 @@ void ExternalModuleView::on_tick_second() { text_name.set(""); text_version.set(""); text_number_apps.set(""); - text_app1_name.set(""); - text_app2_name.set(""); - text_app3_name.set(""); - text_app4_name.set(""); - text_app5_name.set(""); + if (shown_count_ != -1) { + menu_apps.clear(); + shown_count_ = -1; + } return; } @@ -74,59 +73,56 @@ void ExternalModuleView::on_tick_second() { text_version.set("Version: " + std::to_string(device_info->module_version)); text_number_apps.set("No# Apps: " + std::to_string(device_info->application_count)); - for (uint32_t i = 0; i < device_info->application_count && i < 5; i++) { + // Only skip the rebuild when the app count is unchanged AND the current + // list is complete. If some getStandaloneAppInfo() calls failed transiently + // (e.g. an I2C read glitch) the menu holds fewer items than reported, so we + // rebuild to let the list self-heal on a later tick. + if ((int32_t)device_info->application_count == shown_count_ && + menu_apps.item_count() == (size_t)device_info->application_count) { + return; + } + + menu_apps.clear(); + + for (uint32_t i = 0; i < device_info->application_count; i++) { auto appInfo = dev->getStandaloneAppInfo(i); if (appInfo.has_value() == false) { continue; } - std::string btnText = (std::string) "App " + std::to_string(i + 1) + ": " + (const char*)appInfo->app_name; + std::string itemText = (std::string) "App " + std::to_string(i + 1) + ": " + (const char*)appInfo->app_name; switch (appInfo->menu_location) { case app_location_t::UTILITIES: - btnText += " (Utilities)"; + itemText += " (Utilities)"; break; case app_location_t::RX: - btnText += " (RX)"; + itemText += " (RX)"; break; case app_location_t::TX: - btnText += " (TX)"; + itemText += " (TX)"; break; case app_location_t::TRX: - btnText += " (TRX)"; + itemText += " (TRX)"; break; case app_location_t::SETTINGS: - btnText += " (Settings)"; + itemText += " (Settings)"; break; case app_location_t::DEBUG: - btnText += " (Debug)"; + itemText += " (Debug)"; break; case app_location_t::HOME: - btnText += " (Home)"; + itemText += " (Home)"; break; case app_location_t::GAMES: - btnText += " (Games)"; + itemText += " (Games)"; break; } - switch (i) { - case 0: - text_app1_name.set(btnText); - break; - case 1: - text_app2_name.set(btnText); - break; - case 2: - text_app3_name.set(btnText); - break; - case 3: - text_app4_name.set(btnText); - break; - case 4: - text_app5_name.set(btnText); - break; - } + menu_apps.add_item({itemText, ui::Theme::getInstance()->fg_light->foreground, nullptr, [](KeyEvent) {}}); } + + shown_count_ = (int32_t)device_info->application_count; } } // namespace ui diff --git a/firmware/application/apps/ui_external_module_view.hpp b/firmware/application/apps/ui_external_module_view.hpp index f91cd4541..5c053cd2c 100644 --- a/firmware/application/apps/ui_external_module_view.hpp +++ b/firmware/application/apps/ui_external_module_view.hpp @@ -49,12 +49,7 @@ class ExternalModuleView : public View { &text_name, &text_version, &text_number_apps, - &text_app1_name, - &text_app2_name, - &text_app3_name, - &text_app4_name, - &text_app5_name, - &dummy}); + &menu_apps}); text_header.set("No module connected"); @@ -77,15 +72,14 @@ class ExternalModuleView : public View { Text text_version{{24, 48, 200, 16}}; Text text_number_apps{{24, 64, 200, 16}}; - Text text_app1_name{{24, 96, 200, 16}}; - Text text_app2_name{{24, 112, 200, 16}}; - Text text_app3_name{{24, 128, 200, 16}}; - Text text_app4_name{{24, 144, 200, 16}}; - Text text_app5_name{{24, 160, 200, 16}}; + // Scrollable, unlimited app list. + MenuView menu_apps{ + {0, 84, screen_width, screen_height - 84}, + true}; - Button dummy{ - {screen_width, 0, 0, 0}, - ""}; + // Rebuild the list only when the reported app count changes, so the user + // can scroll without it resetting every second. + int32_t shown_count_{-1}; SignalToken signal_token_tick_second{};