fix(nomadnet): keep partial refresh status in chrome

This commit is contained in:
torlando-agent[bot]
2026-08-18 01:30:14 +00:00
parent 1027667f5a
commit 26de7c3e41
6 changed files with 120 additions and 22 deletions
+15
View File
@@ -399,6 +399,21 @@ void NomadNetScreen::status_timer_cb(lv_timer_t* timer){
screen->_status_timer=nullptr;
screen->apply_browser_layout(false);
}
void NomadNetScreen::clear_status(){
cancel_status_timer();
if(_status)lv_label_set_text(_status,"");
apply_browser_layout(false);
}
void NomadNetScreen::set_partial_activity(bool active){
if(!_reload_button)return;
lv_obj_set_style_bg_color(_reload_button,
active?Theme::primary():Theme::surfaceContainer(),LV_STATE_DEFAULT);
lv_obj_set_style_bg_color(_reload_button,
active?Theme::primary():Theme::primaryPressed(),LV_STATE_FOCUSED);
if(lv_obj_t* icon=lv_obj_get_child(_reload_button,0))
lv_obj_set_style_text_color(icon,active?Theme::warning():Theme::textPrimary(),0);
lv_obj_invalidate(_reload_button);
}
void NomadNetScreen::set_status(const char* value){
cancel_status_timer();
const char* status=value?value:"";
+2
View File
@@ -34,6 +34,8 @@ public:
bool set_local_address(const std::string& address);
std::string address() const;
void set_status(const char* status);
void clear_status();
void set_partial_activity(bool active);
bool set_page(const NomadNet::Document& document);
bool prepare_submission(uint16_t link_id, uint32_t generation,
std::string& target,
+29 -16
View File
@@ -2174,7 +2174,10 @@ bool UIManager::nomad_refresh_path_after_link_failure() {
_nomad_state = NomadState::PATH;
_nomad_deadline_ms = millis() + NomadNet::RequestPolicy::PATH_WAIT_MS;
LVGL_LOCK();
_nomadnet_screen->set_status("Refreshing stale path...");
if (_nomad_partial_controller.active())
_nomadnet_screen->set_partial_activity(true);
else
_nomadnet_screen->set_status("Refreshing stale path...");
return true;
}
@@ -2182,6 +2185,10 @@ uint32_t UIManager::nomad_advance_navigation_generation() {
_nomad_partial_scheduler.cancel(_nomad_navigation_generation);
_nomad_partial_controller.cancel();
_nomad_partial_request = NomadNet::PartialRequest{};
{
LVGL_LOCK();
_nomadnet_screen->set_partial_activity(false);
}
++_nomad_navigation_generation;
if (_nomad_navigation_generation == 0) ++_nomad_navigation_generation;
return _nomad_navigation_generation;
@@ -2388,8 +2395,8 @@ bool UIManager::nomad_schedule_partial_ids(const std::string& address,
start = end + 1;
}
LVGL_LOCK();
_nomadnet_screen->set_status(matched
? "Dynamic content queued" : "No matching dynamic content");
if (!matched)
_nomadnet_screen->set_status("No matching dynamic content");
return matched;
}
@@ -2453,6 +2460,10 @@ void UIManager::nomad_poll_partials(uint32_t now_ms) {
_nomad_partial_request = request;
_nomad_partial_url = std::move(target);
_nomad_state = NomadState::PARTIAL_PENDING;
{
LVGL_LOCK();
_nomadnet_screen->set_partial_activity(true);
}
nomad_begin_partial_transport();
}
@@ -2488,10 +2499,6 @@ void UIManager::nomad_begin_partial_transport() {
_nomad_request_policy.reset();
_nomad_destination_hash = Bytes();
_nomad_destination_hash.assignHex(_nomad_partial_url.destination_hex.c_str());
{
LVGL_LOCK();
_nomadnet_screen->set_status("Discovering dynamic-content path...");
}
if (Transport::has_path(_nomad_destination_hash)) nomad_start_link();
else {
Transport::request_path(_nomad_destination_hash);
@@ -2536,7 +2543,9 @@ void UIManager::nomad_release_partial(bool success, bool deferred, const char* s
_nomad_partial_request = NomadNet::PartialRequest{};
_nomad_partial_url = NomadNet::Url{};
LVGL_LOCK();
_nomadnet_screen->set_status(status);
_nomadnet_screen->set_partial_activity(false);
if (success) _nomadnet_screen->clear_status();
else if (status) _nomadnet_screen->set_status(status);
}
void UIManager::nomad_begin_live_transport() {
@@ -2639,7 +2648,10 @@ void UIManager::nomad_start_link() {
_nomad_state = NomadState::LINK;
_nomad_deadline_ms = millis() + NomadNet::RequestPolicy::LINK_WAIT_MS;
LVGL_LOCK();
_nomadnet_screen->set_status("Establishing encrypted link...");
if (_nomad_partial_controller.active())
_nomadnet_screen->set_partial_activity(true);
else
_nomadnet_screen->set_status("Establishing encrypted link...");
}
void UIManager::nomad_identify_link_if_configured() {
@@ -2727,8 +2739,9 @@ void UIManager::nomad_send_request() {
_nomad_state = NomadState::REQUEST;
_nomad_deadline_ms = millis() + 30000;
LVGL_LOCK();
_nomadnet_screen->set_status(_nomad_partial_controller.active()
? "Updating dynamic content..." : "Requesting page...");
if (_nomad_partial_controller.active())
_nomadnet_screen->set_partial_activity(true);
else _nomadnet_screen->set_status("Requesting page...");
}
bool UIManager::nomad_apply_page_bytes(const uint8_t* data, std::size_t size, bool cached) {
@@ -2904,11 +2917,10 @@ void UIManager::nomad_update() {
}
break;
case NomadNet::AsyncMailbox::Kind::PROGRESS:
if (_nomad_state == NomadState::REQUEST) {
if (_nomad_state == NomadState::REQUEST &&
!_nomad_partial_controller.active()) {
LVGL_LOCK();
if (_nomad_partial_controller.active())
_nomadnet_screen->set_status("Receiving dynamic content...");
else _nomadnet_screen->set_status("Receiving page...");
_nomadnet_screen->set_status("Receiving page...");
}
break;
case NomadNet::AsyncMailbox::Kind::OVERSIZED:
@@ -2936,6 +2948,7 @@ void UIManager::nomad_update() {
_nomad_partial_controller.cancel();
nomad_stop_transport();
LVGL_LOCK();
_nomadnet_screen->set_partial_activity(false);
_nomadnet_screen->set_status("Discarded stale dynamic content");
break;
}
@@ -2987,7 +3000,7 @@ void UIManager::nomad_update() {
: "Dynamic content exceeds page limits");
break;
}
nomad_finish_partial(true, "Dynamic content updated");
nomad_finish_partial(true, nullptr);
break;
}
NomadNet::Document document;
@@ -199,7 +199,8 @@ int main() {
bool ready = false, cancel = false, enter = false, escape = false, focus_restore = false;
bool table_pixels = false, form_pixels = false, focus_pixels = false, glyph_pixels = false;
bool background_pixels = false, teardown = false, cached_status_transient = false;
bool cached_status_oom_collapses = false;
bool cached_status_oom_collapses = false, partial_activity_no_layout = false;
bool partial_failure_visible_during_retry = false;
bool partial_replace = false, partial_forms = false, partial_empty = false;
bool partial_link_focus = false, partial_focus_fallback = false;
bool partial_scroll_anchor = false;
@@ -239,8 +240,46 @@ int main() {
fit_tier = fit.valid && fit.tier == UI::LXMF::NomadNet::TableLayoutTier::FIT;
fit_columns = fit.columns == 2 && fit.cards == 0 && fit.x >= 0 && fit.width > 0 &&
fit.x + fit.width <= 304 && fit.y >= 0 && fit.height > 0;
std::string activity_source;
for (int i = 0; i < 40; ++i)
activity_source += "Activity row " + std::to_string(i) + "\n\n";
assert(screen.set_page(parser.parse(activity_source)));
screen.clear_status();
assert(screen.scroll_to_logical(80, LV_ANIM_OFF));
lv_obj_update_layout(screen._screen);
const int16_t full_content_height = lv_obj_get_height(screen._content);
const int32_t scroll_before_activity = screen._logical_scroll;
const int32_t widget_scroll_before_activity = lv_obj_get_scroll_y(screen._content);
lv_obj_add_state(screen._reload_button, LV_STATE_FOCUSED);
lv_obj_t* reload_icon = lv_obj_get_child(screen._reload_button, 0);
assert(reload_icon);
const lv_color_t focused_idle = lv_obj_get_style_text_color(
reload_icon, LV_PART_MAIN);
screen.set_partial_activity(true);
lv_obj_update_layout(screen._screen);
const lv_color_t focused_active = lv_obj_get_style_text_color(
reload_icon, LV_PART_MAIN);
screen.set_partial_activity(false);
lv_obj_clear_state(screen._reload_button, LV_STATE_FOCUSED);
lv_obj_update_layout(screen._screen);
partial_activity_no_layout =
lv_obj_has_flag(screen._status, LV_OBJ_FLAG_HIDDEN) &&
lv_obj_get_height(screen._content) == full_content_height &&
screen._logical_scroll == scroll_before_activity &&
lv_obj_get_scroll_y(screen._content) == widget_scroll_before_activity &&
lv_color_to32(focused_active) != lv_color_to32(focused_idle);
auto reflow_doc = parser.parse(
screen.set_status("Dynamic refresh failed");
lv_obj_update_layout(screen._screen);
const int16_t failure_content_height = lv_obj_get_height(screen._content);
screen.set_partial_activity(true);
lv_obj_update_layout(screen._screen);
partial_failure_visible_during_retry =
!lv_obj_has_flag(screen._status, LV_OBJ_FLAG_HIDDEN) &&
lv_obj_get_height(screen._content) == failure_content_height;
screen.set_partial_activity(false);
screen.clear_status();
const auto reflow_doc = parser.parse(
"`tc304\nFirst very wide heading|Second very wide heading|Third very wide heading\n"
"---|---|---\nA long value that cannot fit beside peers|"
"Another long value that wraps repeatedly|Final long value\n`t");
@@ -541,12 +580,13 @@ int main() {
"LVGL ACCEPT 320x240 fit_tier=%d fit_columns=%d reflow_tier=%d reflow_cards=%d "
"eight_column_tier=%d eight_column_preserved=%d eight_column_pixels=%d table_link_focus=%d eight_column_objects=%d "
"focus_events=%d edge_scroll=%d ready=%d cancel=%d enter=%d escape=%d focus_restore=%d "
"teardown=%d cached_status_transient=%d cached_status_oom_collapses=%d stale_group=%d background_pixels=%d table_pixels=%d form_pixels=%d "
"teardown=%d cached_status_transient=%d cached_status_oom_collapses=%d partial_activity_no_layout=%d partial_failure_visible_during_retry=%d stale_group=%d background_pixels=%d table_pixels=%d form_pixels=%d "
"focus_pixels=%d glyph_pixels=%d partial_replace=%d partial_forms=%d partial_link_focus=%d partial_focus_fallback=%d partial_scroll_anchor=%d partial_second_scroll_rollback=%d partial_region_top_fallback=%d partial_empty=%d exact_fonts=1 objects=%u\n",
fit_tier, fit_columns, reflow_tier, reflow_cards, eight_column_tier, eight_column_preserved,
eight_column_pixels, table_link_focus, eight_column_objects, focus_events, edge_scroll,
ready, cancel, enter, escape, focus_restore, teardown, cached_status_transient,
cached_status_oom_collapses, 0, background_pixels,
cached_status_oom_collapses, partial_activity_no_layout,
partial_failure_visible_during_retry, 0, background_pixels,
table_pixels, form_pixels, focus_pixels, glyph_pixels,
partial_replace, partial_forms, partial_link_focus, partial_focus_fallback,
partial_scroll_anchor, partial_second_scroll_rollback,
@@ -555,7 +595,8 @@ int main() {
eight_column_preserved && eight_column_pixels && table_link_focus && eight_column_objects &&
focus_events && edge_scroll &&
ready && cancel && enter && escape && focus_restore && teardown && cached_status_transient &&
cached_status_oom_collapses && background_pixels &&
cached_status_oom_collapses && partial_activity_no_layout &&
partial_failure_visible_during_retry && background_pixels &&
table_pixels && form_pixels && focus_pixels && glyph_pixels &&
partial_replace && partial_forms && partial_link_focus && partial_focus_fallback &&
partial_scroll_anchor && partial_second_scroll_rollback &&
@@ -482,6 +482,33 @@ def test_nomadnet_page_body_uses_one_compact_custom_viewport():
assert "lv_group_set_editing(group,false)" in screen
def test_partial_success_uses_top_chrome_activity_without_status_banner():
manager = (INCLUDE / "UIManager.cpp").read_text()
screen = (INCLUDE / "NomadNetScreen.cpp").read_text()
assert '"Dynamic content updated"' not in manager
assert "nomad_finish_partial(true, nullptr);" in manager
assert "_nomadnet_screen->set_partial_activity(true);" in manager
assert "_nomadnet_screen->set_partial_activity(false);" in manager
assert "void NomadNetScreen::set_partial_activity(bool active)" in screen
activity = screen[screen.index("void NomadNetScreen::set_partial_activity(bool active)"):
screen.index("void NomadNetScreen::set_status(")]
assert "apply_browser_layout" not in activity
assert "lv_timer" not in activity
assert "LV_STATE_FOCUSED" in activity
assert "_reload_button" in activity
release = manager[manager.index("void UIManager::nomad_release_partial("):
manager.index("void UIManager::nomad_begin_live_transport()")]
assert "if (success) _nomadnet_screen->clear_status();" in release
start_link = manager[manager.index("void UIManager::nomad_start_link()"):
manager.index("void UIManager::nomad_identify_link_if_configured()")]
assert "if (_nomad_partial_controller.active())" in start_link
assert "else\n _nomadnet_screen->set_status(\"Establishing encrypted link...\");" in start_link
refresh_path = manager[manager.index("bool UIManager::nomad_refresh_path_after_link_failure()"):
manager.index("uint32_t UIManager::nomad_advance_navigation_generation()")]
assert "if (_nomad_partial_controller.active())" in refresh_path
assert "else\n _nomadnet_screen->set_status(\"Refreshing stale path...\");" in refresh_path
def test_successful_page_application_releases_normalized_response():
manager = (INCLUDE / "UIManager.cpp").read_text()
response = manager[manager.index("case NomadNet::AsyncMailbox::Kind::RESPONSE:"):
@@ -41,7 +41,7 @@ def test_actual_nomadnet_screen_320x240_acceptance(tmp_path):
assert "eight_column_tier=1 eight_column_preserved=1 eight_column_pixels=1 table_link_focus=1 eight_column_objects=1" in output
assert "focus_events=1 edge_scroll=1" in output
assert "ready=1 cancel=1 enter=1 escape=1 focus_restore=1" in output
assert "teardown=1 cached_status_transient=1 cached_status_oom_collapses=1 stale_group=0" in output
assert "teardown=1 cached_status_transient=1 cached_status_oom_collapses=1 partial_activity_no_layout=1 partial_failure_visible_during_retry=1 stale_group=0" in output
assert "background_pixels=1 table_pixels=1 form_pixels=1 focus_pixels=1 glyph_pixels=1" in output
assert "partial_focus_fallback=1 partial_scroll_anchor=1" in output
assert "partial_second_scroll_rollback=1" in output